1use alloc::{borrow::ToOwned, string::String, sync::Arc, vec::Vec};
2
3use miden_debug_types::{SourceFile, SourceId, SourceLanguage, SourceSpan, Uri};
4use rowan::{GreenNodeBuilder, NodeOrToken, TextRange};
5
6use crate::{
7 MasmLanguage,
8 ast::AstNode,
9 diagnostics::{LabeledSpan, Severity, diagnostic, miette::MietteDiagnostic as Diagnostic},
10 lexer::{Token, tokenize},
11 syntax::{SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken},
12};
13
14#[derive(Debug, Clone)]
19pub struct Parse {
20 source: Arc<SourceFile>,
21 green_node: rowan::GreenNode,
22 diagnostics: Vec<Diagnostic>,
23}
24
25impl Parse {
26 pub fn syntax(&self) -> SyntaxNode {
28 SyntaxNode::new_root(self.green_node.clone())
29 }
30
31 pub fn root(&self) -> crate::ast::SourceFile {
33 crate::ast::SourceFile::cast(self.syntax())
34 .expect("parse root kind should always be SourceFile")
35 }
36
37 pub fn diagnostics(&self) -> &[Diagnostic] {
39 &self.diagnostics
40 }
41
42 pub fn take_diagnostics(&mut self) -> Vec<Diagnostic> {
44 core::mem::take(&mut self.diagnostics)
45 }
46
47 pub fn source_file(&self) -> Arc<SourceFile> {
49 Arc::clone(&self.source)
50 }
51
52 pub fn source(&self) -> &SourceFile {
54 self.source.as_ref()
55 }
56
57 pub fn has_errors(&self) -> bool {
59 !self.diagnostics.is_empty()
60 }
61
62 pub fn span_for_ast_node<T>(&self, node: &T) -> SourceSpan
64 where
65 T: AstNode<Language = MasmLanguage>,
66 {
67 self.span_for_node(node.syntax())
68 }
69
70 pub fn span_for_node(&self, node: &SyntaxNode) -> SourceSpan {
72 self.span_for_range(node.text_range())
73 }
74
75 pub fn span_for_token(&self, token: &SyntaxToken) -> SourceSpan {
77 self.span_for_range(token.text_range())
78 }
79
80 pub fn span_for_element(&self, element: &SyntaxElement) -> SourceSpan {
82 match element {
83 NodeOrToken::Node(node) => self.span_for_node(node),
84 NodeOrToken::Token(token) => self.span_for_token(token),
85 }
86 }
87
88 pub fn span_for_range(&self, range: TextRange) -> SourceSpan {
90 source_span_from_text_range(self.source.id(), range)
91 }
92}
93
94pub fn parse_source_file(source: Arc<SourceFile>) -> Parse {
96 let parser_source = Arc::clone(&source);
97 Parser::new(parser_source.as_ref()).parse(source)
98}
99
100pub fn parse_text(input: &str) -> Parse {
105 parse_source_file(detached_source_file(input))
106}
107
108pub fn parse_inline_masm(source: Arc<SourceFile>, bounds: Option<SourceSpan>) -> Parse {
113 let parser_source = Arc::clone(&source);
114 if let Some(bounds) = bounds {
115 Parser::new_bounded(parser_source.as_ref(), bounds).parse_inline_masm(source)
116 } else {
117 Parser::new(parser_source.as_ref()).parse_inline_masm(source)
118 }
119}
120
121pub fn parse_inline_masm_text(input: &str, bounds: Option<core::ops::Range<usize>>) -> Parse {
126 let file = detached_source_file(input);
127 let bounds = bounds.map(|range| {
128 SourceSpan::try_from_range(file.id(), range).expect("invalid inline masm bounds")
129 });
130 parse_inline_masm(file, bounds)
131}
132
133struct Parser<'input> {
134 tokens: Vec<Token<'input>>,
135 pos: usize,
136 builder: GreenNodeBuilder<'static>,
137 diagnostics: Vec<Diagnostic>,
138 eof_span: SourceSpan,
139}
140
141#[derive(Default, Debug, Clone, Copy)]
142struct Nesting {
143 parens: usize,
144 brackets: usize,
145 braces: usize,
146}
147
148impl Nesting {
149 fn is_root(self) -> bool {
150 self.parens == 0 && self.brackets == 0 && self.braces == 0
151 }
152
153 fn bump(self, kind: SyntaxKind) -> Result<Self, SyntaxKind> {
154 let mut next = self;
155 match kind {
156 SyntaxKind::LParen => next.parens += 1,
157 SyntaxKind::LBracket => next.brackets += 1,
158 SyntaxKind::LBrace => next.braces += 1,
159 SyntaxKind::RParen => {
160 next.parens = next.parens.checked_sub(1).ok_or(kind)?;
161 },
162 SyntaxKind::RBracket => {
163 next.brackets = next.brackets.checked_sub(1).ok_or(kind)?;
164 },
165 SyntaxKind::RBrace => {
166 next.braces = next.braces.checked_sub(1).ok_or(kind)?;
167 },
168 _ => (),
169 }
170 Ok(next)
171 }
172}
173
174#[derive(Debug, Clone, Copy, PartialEq, Eq)]
175enum BlockOwner {
176 Begin,
177 Procedure,
178 If,
179 While,
180 DoWhileBody,
181 DoWhile,
182 Repeat,
183}
184
185impl BlockOwner {
186 fn missing_end_message(self) -> &'static str {
187 match self {
188 Self::Begin => "expected `end` to close `begin` block",
189 Self::Procedure => "expected `end` to close procedure",
190 Self::If => "expected `end` to close `if`",
191 Self::While => "expected `end` to close `while`",
192 Self::DoWhileBody => "expected `while` to close `do` block",
193 Self::DoWhile => "expected `end` to close `do`..`while` loop",
194 Self::Repeat => "expected `end` to close `repeat`",
195 }
196 }
197
198 fn recovery_message(self, boundary: BlockRecoveryBoundary) -> String {
199 match boundary {
200 BlockRecoveryBoundary::Else => {
201 format!("{} before `else`", self.missing_end_message())
202 },
203 BlockRecoveryBoundary::TopLevelItem => {
204 format!("{} before top-level item", self.missing_end_message())
205 },
206 }
207 }
208}
209
210#[derive(Debug, Clone, Copy, PartialEq, Eq)]
211enum BlockRecoveryBoundary {
212 Else,
213 TopLevelItem,
214}
215
216#[derive(Debug, Clone, Copy, PartialEq, Eq)]
217enum BlockParseOutcome {
218 FoundTerminator,
219 RecoveredImplicitEnd,
220 ReachedEof,
221}
222
223impl<'input> Parser<'input> {
224 fn new(source: &'input SourceFile) -> Self {
225 let eof_span = eof_anchor_span(source, None);
226 Self {
227 tokens: tokenize(source),
228 pos: 0,
229 builder: GreenNodeBuilder::new(),
230 diagnostics: Vec::new(),
231 eof_span,
232 }
233 }
234
235 fn new_bounded(source: &'input SourceFile, bounds: SourceSpan) -> Self {
236 assert_eq!(source.id(), bounds.source_id());
237
238 let eof_span = eof_anchor_span(source, Some(bounds.into_slice_index()));
239 Self {
240 tokens: tokenize(source),
241 pos: 0,
242 builder: GreenNodeBuilder::new(),
243 diagnostics: Vec::new(),
244 eof_span,
245 }
246 }
247
248 fn parse(mut self, source: Arc<SourceFile>) -> Parse {
249 self.start_node(SyntaxKind::SourceFile);
250 while !self.eof() {
251 self.parse_source_item();
252 }
253 self.finish_node();
254
255 Parse {
256 source,
257 green_node: self.builder.finish(),
258 diagnostics: self.diagnostics,
259 }
260 }
261
262 fn parse_inline_masm(mut self, source: Arc<SourceFile>) -> Parse {
263 match self.parse_block_unterminated() {
264 BlockParseOutcome::ReachedEof => (),
265 BlockParseOutcome::FoundTerminator => self.error_here("unexpected 'end'"),
266 BlockParseOutcome::RecoveredImplicitEnd => {
267 self.error_here("unclosed nested block: expected 'end' but reached eof")
268 },
269 }
270 Parse {
271 source,
272 green_node: self.builder.finish(),
273 diagnostics: self.diagnostics,
274 }
275 }
276
277 fn parse_source_item(&mut self) {
278 if self.at_kind(SyntaxKind::DocComment) {
279 self.parse_doc_form();
280 return;
281 }
282
283 if self.at_regular_trivia() {
284 self.bump();
285 return;
286 }
287
288 if self.at_keyword("namespace") {
289 self.parse_namespace();
290 return;
291 }
292
293 if self.at_prefixed_keyword("extern", "package") {
294 self.parse_extern_package();
295 return;
296 }
297
298 if self.at_keyword("mod") || self.at_prefixed_keyword("pub", "mod") {
299 self.parse_submodule();
300 return;
301 }
302
303 if self.at_kind(SyntaxKind::At)
304 || self.at_keyword("proc")
305 || self.at_prefixed_keyword("pub", "proc")
306 {
307 self.parse_procedure();
308 return;
309 }
310
311 if self.at_keyword("begin") {
312 self.parse_begin_block();
313 return;
314 }
315
316 if self.at_keyword("use") || self.at_prefixed_keyword("pub", "use") {
317 self.parse_import();
318 return;
319 }
320
321 if self.at_keyword("const") || self.at_prefixed_keyword("pub", "const") {
322 self.parse_constant();
323 return;
324 }
325
326 if self.at_keyword("type")
327 || self.at_keyword("enum")
328 || self.at_prefixed_keyword("pub", "type")
329 || self.at_prefixed_keyword("pub", "enum")
330 {
331 self.parse_type_decl();
332 return;
333 }
334
335 if self.at_keyword("adv_map") {
336 self.parse_advice_map();
337 return;
338 }
339
340 self.start_node(SyntaxKind::Error);
341 self.error_here("unexpected top-level token");
342 self.bump();
343 self.finish_node();
344 }
345
346 fn parse_doc_form(&mut self) {
347 self.start_node(SyntaxKind::Doc);
348 self.bump();
349 self.finish_node();
350 }
351
352 fn parse_namespace(&mut self) {
353 self.start_node(SyntaxKind::Namespace);
354 self.expect_keyword("namespace", "expected `namespace` in namespace declaration");
355 self.bump_regular_trivia();
356 self.parse_path_with_message("expected a namespace path");
357 self.parse_line_tail();
358 self.finish_node();
359 }
360
361 fn parse_extern_package(&mut self) {
362 self.start_node(SyntaxKind::ExternPackage);
363 self.expect_keyword("extern", "expected `extern` in extern package declaration");
364 self.expect_keyword("package", "expected `package` in extern package declaration");
365 self.bump_regular_trivia();
366
367 if self.at_package_name_like() {
368 self.bump();
369 } else {
370 self.error_here("expected a package name");
371 }
372
373 self.parse_line_tail();
374 self.finish_node();
375 }
376
377 fn parse_submodule(&mut self) {
378 self.start_node(SyntaxKind::Submodule);
379
380 if self.at_keyword("pub") {
381 self.parse_visibility();
382 }
383
384 self.expect_keyword("mod", "expected `mod` in submodule declaration");
385 self.bump_regular_trivia();
386 if self.at_name_like() {
387 self.bump();
388 } else {
389 self.error_here("expected a submodule name");
390 }
391
392 self.parse_line_tail();
393 self.finish_node();
394 }
395
396 fn parse_import(&mut self) {
397 self.start_node(SyntaxKind::Import);
398
399 let is_public = if self.at_keyword("pub") {
400 self.parse_visibility();
401 true
402 } else {
403 false
404 };
405
406 self.expect_keyword("use", "expected `use` in import declaration");
407 self.bump_inline_whitespace();
408
409 if self.at_kind(SyntaxKind::LBrace) {
410 self.parse_import_list();
411 self.parse_item_import_module_path();
412 self.parse_rejected_old_import_alias();
413 } else if self.at_kind(SyntaxKind::Star) {
414 self.parse_rejected_wildcard_import();
415 } else if self.at_kind(SyntaxKind::Number) {
416 self.parse_rejected_digest_import_target();
417 } else {
418 self.parse_module_import(is_public);
419 }
420
421 self.parse_line_tail();
422 self.finish_node();
423 }
424
425 fn parse_module_import(&mut self, is_public: bool) {
426 let path_start = self.current().map(Token::span).unwrap_or(self.eof_span);
427 if self.at_kind(SyntaxKind::Newline) || self.eof() {
428 self.error_here("expected an import path");
429 return;
430 }
431
432 self.parse_path_with_message("expected an import path");
433
434 if is_public {
435 self.error_at_span(path_start, "`pub use` is only supported for braced item imports");
436 }
437
438 self.parse_optional_module_alias();
439 self.parse_rejected_old_import_alias();
440 }
441
442 fn parse_optional_module_alias(&mut self) {
443 if !self.peek_contextual_keyword_after_non_comment_trivia("as") {
444 return;
445 }
446
447 self.bump_non_comment_trivia();
448 self.bump();
449 self.bump_inline_whitespace();
450 if self.at_name_like() {
451 self.bump();
452 } else {
453 self.error_here("expected an alias name after `as`");
454 }
455 }
456
457 fn parse_import_list(&mut self) {
458 self.start_node(SyntaxKind::ImportList);
459 let _ = self.expect_kind(SyntaxKind::LBrace, "expected `{` to start import list");
460
461 let mut saw_specifier = false;
462 loop {
463 self.bump_regular_trivia();
464
465 if self.eof() {
466 self.error_at_eof("expected `}` to close import list");
467 break;
468 }
469
470 if self.at_kind(SyntaxKind::RBrace) {
471 if !saw_specifier {
472 self.error_here("import lists must contain at least one item");
473 }
474 self.bump();
475 break;
476 }
477
478 self.parse_import_specifier();
479 saw_specifier = true;
480 self.bump_regular_trivia();
481
482 if self.at_kind(SyntaxKind::Comma) {
483 self.bump();
484 continue;
485 }
486
487 if self.at_kind(SyntaxKind::RBrace) {
488 self.bump();
489 break;
490 }
491
492 self.error_here("expected `,` or `}` in import list");
493 if !self.at_import_list_recovery_boundary() {
494 self.bump();
495 }
496 }
497
498 self.finish_node();
499 }
500
501 fn parse_import_specifier(&mut self) {
502 self.start_node(SyntaxKind::ImportSpecifier);
503 self.bump_regular_trivia();
504
505 if self.at_kind(SyntaxKind::Star) {
506 self.error_here("wildcard imports are not supported");
507 self.bump();
508 self.finish_node();
509 return;
510 }
511
512 if self.at_name_like() {
513 self.bump();
514 } else {
515 self.error_here("expected an imported item name");
516 if !self.at_import_list_recovery_boundary() {
517 self.bump();
518 }
519 self.finish_node();
520 return;
521 }
522
523 if self.peek_contextual_keyword_after_non_comment_trivia("as") {
524 self.bump_non_comment_trivia();
525 self.bump();
526 self.bump_inline_whitespace();
527 if self.at_name_like() {
528 self.bump();
529 } else {
530 self.error_here("expected an alias name after `as`");
531 }
532 }
533
534 self.parse_rejected_old_import_alias();
535 self.finish_node();
536 }
537
538 fn parse_item_import_module_path(&mut self) {
539 if !self.peek_contextual_keyword_after_non_comment_trivia("from") {
540 self.error_here("expected `from` after import list");
541 return;
542 }
543
544 self.bump_non_comment_trivia();
545 self.bump();
546 self.bump_inline_whitespace();
547
548 if self.at_kind(SyntaxKind::Newline) || self.eof() {
549 self.error_here("expected a module path after `from`");
550 return;
551 }
552
553 if self.at_kind(SyntaxKind::Number) {
554 self.parse_rejected_digest_import_target();
555 } else {
556 self.parse_path_with_message("expected a module path after `from`");
557 }
558 }
559
560 fn parse_rejected_old_import_alias(&mut self) {
561 if self.peek_after_non_comment_trivia() != Some(SyntaxKind::RArrow) {
562 return;
563 }
564
565 self.start_node(SyntaxKind::Error);
566 self.bump_non_comment_trivia();
567 self.error_here("import aliases use `as`; `->` is no longer supported");
568 self.bump();
569 self.bump_non_comment_trivia();
570 if self.at_name_like() {
571 self.bump();
572 } else {
573 self.error_here("expected an alias name after `->`");
574 }
575 self.finish_node();
576 }
577
578 fn parse_rejected_digest_import_target(&mut self) {
579 self.start_node(SyntaxKind::Error);
580 self.error_here("digest imports are not supported in source `use` declarations");
581 self.bump();
582 self.parse_optional_module_alias();
583 self.parse_rejected_old_import_alias();
584 self.finish_node();
585 }
586
587 fn parse_rejected_wildcard_import(&mut self) {
588 self.start_node(SyntaxKind::Error);
589 self.error_here("wildcard imports are not supported");
590 self.bump();
591
592 if self.peek_contextual_keyword_after_non_comment_trivia("from") {
593 self.bump_non_comment_trivia();
594 self.bump();
595 self.bump_regular_trivia();
596 self.parse_path_with_message("expected a module path after `from`");
597 }
598
599 self.finish_node();
600 }
601
602 fn parse_constant(&mut self) {
603 self.start_node(SyntaxKind::Constant);
604
605 if self.at_keyword("pub") {
606 self.parse_visibility();
607 }
608
609 self.expect_keyword("const", "expected `const` in constant declaration");
610 self.bump_regular_trivia();
611 if self.at_name_like() {
612 self.bump();
613 } else {
614 self.error_here("expected a constant name");
615 }
616
617 self.expect_kind(SyntaxKind::Equal, "expected `=` in constant declaration");
618 self.parse_expr_until_line_end();
619 self.parse_line_tail();
620 self.finish_node();
621 }
622
623 fn parse_type_decl(&mut self) {
624 self.start_node(SyntaxKind::TypeDecl);
625
626 if self.at_keyword("pub") {
627 self.parse_visibility();
628 }
629
630 self.bump_regular_trivia();
631 if self.at_keyword("type") || self.at_keyword("enum") {
632 self.bump();
633 } else {
634 self.error_here("expected `type` or `enum`");
635 self.finish_node();
636 return;
637 }
638
639 self.bump_regular_trivia();
640 if self.at_name_like() {
641 self.bump();
642 } else {
643 self.error_here("expected a type name");
644 }
645
646 self.bump_regular_trivia();
647 if self.at_kind(SyntaxKind::Equal) || self.at_kind(SyntaxKind::Colon) {
648 self.parse_type_body();
649 } else {
650 self.error_here("expected `=` or `:` in type declaration");
651 }
652
653 self.finish_node();
654 }
655
656 fn parse_advice_map(&mut self) {
657 self.start_node(SyntaxKind::AdviceMap);
658 self.expect_keyword("adv_map", "expected `adv_map`");
659 self.bump_regular_trivia();
660 if self.at_name_like() {
661 self.bump();
662 } else {
663 self.error_here("expected an advice-map name");
664 }
665
666 self.bump_inline_whitespace();
667 if self.at_kind(SyntaxKind::LParen) {
668 self.parse_balanced_group(
669 SyntaxKind::LParen,
670 SyntaxKind::RParen,
671 "expected `)` to close advice-map key",
672 );
673 }
674
675 self.expect_kind(SyntaxKind::Equal, "expected `=` in advice-map declaration");
676 self.parse_expr_until_line_end();
677 self.parse_line_tail();
678 self.finish_node();
679 }
680
681 fn parse_path_with_message(&mut self, message: &'static str) {
682 self.start_node(SyntaxKind::Path);
683 if self.at_kind(SyntaxKind::ColonColon) {
684 self.bump();
685 }
686
687 self.bump_non_comment_trivia();
688 if self.at_name_like() {
689 self.bump();
690 } else {
691 self.error_here(message);
692 self.finish_node();
693 return;
694 }
695
696 loop {
697 if self.peek_after_non_comment_trivia() != Some(SyntaxKind::ColonColon) {
698 break;
699 }
700
701 self.bump_non_comment_trivia();
702 self.bump();
703 self.bump_non_comment_trivia();
704 if self.at_name_like() {
705 self.bump();
706 } else {
707 self.error_here("expected a path segment after `::`");
708 break;
709 }
710 }
711
712 self.finish_node();
713 }
714
715 fn parse_expr_until_line_end(&mut self) {
716 self.bump_regular_trivia();
717 self.start_node(SyntaxKind::Expr);
718
719 let mut nesting = Nesting::default();
720 let mut saw_significant = false;
721 while !self.eof() {
722 let kind = self.current_kind().expect("not eof");
723 if nesting.is_root() && matches!(kind, SyntaxKind::Comment | SyntaxKind::DocComment) {
724 break;
725 }
726 if nesting.is_root() && kind == SyntaxKind::Newline {
727 break;
728 }
729 if nesting.is_root()
730 && saw_significant
731 && kind == SyntaxKind::Whitespace
732 && self
733 .next_relevant_top_level_token(self.pos + 1)
734 .is_some_and(|index| self.is_top_level_starter(index))
735 {
736 break;
737 }
738
739 saw_significant |= !kind.is_trivia();
740 self.bump_nesting(&mut nesting, kind);
741 self.bump();
742 }
743
744 self.finish_node();
745 }
746
747 fn parse_type_body(&mut self) {
748 self.start_node(SyntaxKind::TypeBody);
749
750 let mut nesting = Nesting::default();
751 while !self.eof() {
752 if self.at_kind(SyntaxKind::Newline)
753 && nesting.is_root()
754 && self.line_break_starts_new_top_level_item()
755 {
756 break;
757 }
758
759 let current = self.current_kind().expect("not eof");
760 self.bump_nesting(&mut nesting, current);
761 self.bump();
762 }
763
764 self.finish_node();
765 }
766
767 fn parse_line_tail(&mut self) {
768 loop {
769 match self.current_kind() {
770 Some(SyntaxKind::Whitespace) => self.bump(),
771 Some(SyntaxKind::Comment) => {
772 self.bump();
773 break;
774 },
775 _ => break,
776 }
777 }
778 }
779
780 fn parse_begin_block(&mut self) {
781 self.start_node(SyntaxKind::BeginBlock);
782 self.expect_keyword("begin", "expected `begin`");
783 self.parse_line_tail();
784 if self.parse_block(BlockOwner::Begin, &["end"]) == BlockParseOutcome::FoundTerminator {
785 self.expect_keyword("end", BlockOwner::Begin.missing_end_message());
786 }
787 self.finish_node();
788 }
789
790 fn parse_procedure(&mut self) {
791 self.start_node(SyntaxKind::Procedure);
792
793 loop {
794 self.bump_regular_trivia();
795 if !self.at_kind(SyntaxKind::At) {
796 break;
797 }
798 self.parse_attribute();
799 }
800
801 self.bump_regular_trivia();
802 if self.at_keyword("pub") {
803 self.parse_visibility();
804 }
805
806 self.bump_regular_trivia();
807 if !self.expect_keyword("proc", "expected `proc` in procedure declaration") {
808 self.finish_node();
809 return;
810 }
811
812 self.bump_regular_trivia();
813 if self.at_name_like() {
814 self.bump();
815 } else {
816 self.error_here("expected a procedure name");
817 }
818
819 self.bump_regular_trivia();
820 if self.at_kind(SyntaxKind::LParen) {
821 self.parse_signature();
822 }
823
824 self.parse_line_tail();
825 if self.parse_block(BlockOwner::Procedure, &["end"]) == BlockParseOutcome::FoundTerminator {
826 self.expect_keyword("end", BlockOwner::Procedure.missing_end_message());
827 }
828 self.finish_node();
829 }
830
831 fn parse_attribute(&mut self) {
832 self.start_node(SyntaxKind::Attribute);
833 let _ = self.expect_kind(SyntaxKind::At, "expected `@`");
834 self.bump_regular_trivia();
835 if self.at_name_like() {
836 self.bump();
837 } else {
838 self.error_here("expected an attribute name");
839 }
840
841 self.bump_inline_whitespace();
842 if self.at_kind(SyntaxKind::LParen) {
843 self.parse_balanced_group(
844 SyntaxKind::LParen,
845 SyntaxKind::RParen,
846 "expected `)` to close attribute arguments",
847 );
848 }
849 self.finish_node();
850 }
851
852 fn parse_visibility(&mut self) {
853 self.start_node(SyntaxKind::Visibility);
854 let _ = self.expect_keyword("pub", "expected `pub`");
855 self.finish_node();
856 }
857
858 fn parse_signature(&mut self) {
859 self.start_node(SyntaxKind::Signature);
860 self.parse_balanced_group(
861 SyntaxKind::LParen,
862 SyntaxKind::RParen,
863 "expected `)` to close procedure parameters",
864 );
865
866 if self.peek_after_non_comment_trivia() == Some(SyntaxKind::RArrow) {
867 self.bump_non_comment_trivia();
868 self.bump();
869 self.bump_non_comment_trivia();
870 if self.at_kind(SyntaxKind::LParen) {
871 self.parse_balanced_group(
872 SyntaxKind::LParen,
873 SyntaxKind::RParen,
874 "expected `)` to close procedure results",
875 );
876 } else {
877 self.parse_signature_result_until_line_end();
878 }
879 }
880 self.finish_node();
881 }
882
883 fn parse_signature_result_until_line_end(&mut self) {
884 let start = self.pos;
885 let mut nesting = Nesting::default();
886 while !self.eof() {
887 let kind = self.current_kind().expect("not eof");
888 if nesting.is_root() && matches!(kind, SyntaxKind::Comment | SyntaxKind::DocComment) {
889 break;
890 }
891 if nesting.is_root() && kind == SyntaxKind::Newline {
892 break;
893 }
894
895 self.bump_nesting(&mut nesting, kind);
896 self.bump();
897 }
898
899 if self.pos == start {
900 self.error_here("expected a result type after `->` in procedure signature");
901 }
902 }
903
904 fn parse_block_unterminated(&mut self) -> BlockParseOutcome {
905 self.start_node(SyntaxKind::Block);
906 while !self.eof() {
907 if self.at_regular_trivia() {
908 self.bump();
909 continue;
910 }
911
912 if self.at_kind(SyntaxKind::DocComment) {
913 self.start_node(SyntaxKind::Error);
914 self.error_here("doc comments are not allowed in inline MASM blocks");
915 self.bump();
916 self.finish_node();
917 continue;
918 }
919
920 if self.at_keyword("if") {
921 if self.parse_if() {
922 self.finish_node();
923 return BlockParseOutcome::ReachedEof;
924 }
925 } else if self.at_keyword("do") {
926 if self.parse_do_while() {
927 self.finish_node();
928 return BlockParseOutcome::ReachedEof;
929 }
930 } else if self.at_keyword("while") {
931 if self.parse_while() {
932 self.finish_node();
933 return BlockParseOutcome::ReachedEof;
934 }
935 } else if self.at_keyword("repeat") {
936 if self.parse_repeat() {
937 self.finish_node();
938 return BlockParseOutcome::ReachedEof;
939 }
940 } else if self.can_start_instruction() {
941 self.parse_instruction();
942 } else {
943 self.start_node(SyntaxKind::Error);
944 self.error_here("unexpected token in block");
945 self.bump();
946 self.finish_node();
947 }
948 }
949
950 self.finish_node();
951 BlockParseOutcome::ReachedEof
952 }
953
954 fn parse_block(&mut self, owner: BlockOwner, terminators: &[&str]) -> BlockParseOutcome {
955 self.start_node(SyntaxKind::Block);
956 while !self.eof() {
957 if self.at_terminator(terminators) {
958 self.finish_node();
959 return BlockParseOutcome::FoundTerminator;
960 }
961
962 if self.at_regular_trivia() {
963 self.bump();
964 continue;
965 }
966
967 if let Some(boundary) = self.block_recovery_boundary(terminators) {
968 self.start_node(SyntaxKind::Error);
969 self.error_here(owner.recovery_message(boundary));
970 self.finish_node();
971 self.finish_node();
972 return BlockParseOutcome::RecoveredImplicitEnd;
973 }
974
975 if self.at_kind(SyntaxKind::DocComment) {
976 self.start_node(SyntaxKind::Error);
977 self.error_here("doc comments are only allowed before module-level items");
978 self.bump();
979 self.finish_node();
980 continue;
981 }
982
983 if self.at_keyword("if") {
984 if self.parse_if() {
985 self.finish_node();
986 return BlockParseOutcome::ReachedEof;
987 }
988 } else if self.at_keyword("do") {
989 if self.parse_do_while() {
990 self.finish_node();
991 return BlockParseOutcome::ReachedEof;
992 }
993 } else if self.at_keyword("while") {
994 if self.parse_while() {
995 self.finish_node();
996 return BlockParseOutcome::ReachedEof;
997 }
998 } else if self.at_keyword("repeat") {
999 if self.parse_repeat() {
1000 self.finish_node();
1001 return BlockParseOutcome::ReachedEof;
1002 }
1003 } else if self.can_start_instruction() {
1004 self.parse_instruction();
1005 } else {
1006 self.start_node(SyntaxKind::Error);
1007 self.error_here("unexpected token in block");
1008 self.bump();
1009 self.finish_node();
1010 }
1011 }
1012
1013 self.error_at_eof(owner.missing_end_message());
1014 self.finish_node();
1015 BlockParseOutcome::ReachedEof
1016 }
1017
1018 fn parse_if(&mut self) -> bool {
1019 self.start_node(SyntaxKind::IfOp);
1020 self.expect_keyword("if", "expected `if`");
1021 self.parse_structured_header_suffixes();
1022 self.parse_line_tail();
1023 let then_outcome = self.parse_block(BlockOwner::If, &["else", "end"]);
1024 if then_outcome == BlockParseOutcome::ReachedEof {
1025 self.finish_node();
1026 return true;
1027 }
1028 let mut needs_end = then_outcome == BlockParseOutcome::FoundTerminator;
1029 if self.at_keyword("else") {
1030 self.bump();
1031 self.parse_line_tail();
1032 let else_outcome = self.parse_block(BlockOwner::If, &["end"]);
1033 if else_outcome == BlockParseOutcome::ReachedEof {
1034 self.finish_node();
1035 return true;
1036 }
1037 needs_end = else_outcome == BlockParseOutcome::FoundTerminator;
1038 }
1039 if needs_end {
1040 self.expect_keyword("end", BlockOwner::If.missing_end_message());
1041 }
1042 self.finish_node();
1043 false
1044 }
1045
1046 fn parse_while(&mut self) -> bool {
1047 self.start_node(SyntaxKind::WhileOp);
1048 self.expect_keyword("while", "expected `while`");
1049 self.parse_structured_header_suffixes();
1050 self.parse_line_tail();
1051 let outcome = self.parse_block(BlockOwner::While, &["end"]);
1052 if outcome == BlockParseOutcome::ReachedEof {
1053 self.finish_node();
1054 return true;
1055 }
1056 if outcome == BlockParseOutcome::FoundTerminator {
1057 self.expect_keyword("end", BlockOwner::While.missing_end_message());
1058 }
1059 self.finish_node();
1060 false
1061 }
1062
1063 fn parse_do_while(&mut self) -> bool {
1064 self.start_node(SyntaxKind::DoWhileOp);
1065 self.expect_keyword("do", "expected `do`");
1066 self.parse_line_tail();
1067 let body_outcome = self.parse_block(BlockOwner::DoWhileBody, &["while"]);
1071 if body_outcome == BlockParseOutcome::ReachedEof {
1072 self.finish_node();
1073 return true;
1074 }
1075 if body_outcome == BlockParseOutcome::FoundTerminator {
1076 self.expect_keyword("while", "expected `while`");
1077 self.parse_line_tail();
1078 let cond_outcome = self.parse_block(BlockOwner::DoWhile, &["end"]);
1079 if cond_outcome == BlockParseOutcome::ReachedEof {
1080 self.finish_node();
1081 return true;
1082 }
1083 if cond_outcome == BlockParseOutcome::FoundTerminator {
1084 self.expect_keyword("end", BlockOwner::DoWhile.missing_end_message());
1085 }
1086 }
1087 self.finish_node();
1088 false
1089 }
1090
1091 fn parse_repeat(&mut self) -> bool {
1092 self.start_node(SyntaxKind::RepeatOp);
1093 self.expect_keyword("repeat", "expected `repeat`");
1094 self.parse_structured_header_suffixes();
1095 self.parse_line_tail();
1096 let outcome = self.parse_block(BlockOwner::Repeat, &["end"]);
1097 if outcome == BlockParseOutcome::ReachedEof {
1098 self.finish_node();
1099 return true;
1100 }
1101 if outcome == BlockParseOutcome::FoundTerminator {
1102 self.expect_keyword("end", BlockOwner::Repeat.missing_end_message());
1103 }
1104 self.finish_node();
1105 false
1106 }
1107
1108 fn parse_structured_header_suffixes(&mut self) {
1109 loop {
1110 self.bump_inline_whitespace();
1111 if !self.at_kind(SyntaxKind::Dot) {
1112 break;
1113 }
1114 self.bump();
1115 self.bump_inline_whitespace();
1116
1117 if self.at_kind(SyntaxKind::LBracket) {
1118 self.parse_balanced_group(
1119 SyntaxKind::LBracket,
1120 SyntaxKind::RBracket,
1121 "expected `]` to close structured operation suffix",
1122 );
1123 } else if self.at_name_like()
1124 || self.at_kind(SyntaxKind::Number)
1125 || self.at_kind(SyntaxKind::QuotedString)
1126 {
1127 self.bump();
1128 } else {
1129 self.error_here("expected a structured operation suffix");
1130 break;
1131 }
1132 }
1133 }
1134
1135 fn parse_instruction(&mut self) {
1136 self.start_node(SyntaxKind::Instruction);
1137
1138 let mut nesting = Nesting::default();
1139 let mut previous_significant = None;
1140 while !self.eof() {
1141 let kind = self.current_kind().expect("not eof");
1142 if kind == SyntaxKind::Whitespace {
1143 if self.should_continue_instruction_after_whitespace(previous_significant, nesting)
1144 {
1145 self.bump();
1146 continue;
1147 }
1148 break;
1149 }
1150
1151 if matches!(kind, SyntaxKind::Newline | SyntaxKind::Comment | SyntaxKind::DocComment) {
1152 if nesting.is_root() {
1153 break;
1154 }
1155 self.bump();
1156 continue;
1157 }
1158
1159 if previous_significant.is_some()
1160 && self.should_stop_instruction_before(kind, previous_significant, nesting)
1161 {
1162 break;
1163 }
1164
1165 previous_significant = Some(kind);
1166 self.bump_nesting(&mut nesting, kind);
1167 self.bump();
1168 }
1169
1170 self.finish_node();
1171 }
1172
1173 fn parse_balanced_group(
1174 &mut self,
1175 open: SyntaxKind,
1176 close: SyntaxKind,
1177 missing_message: &'static str,
1178 ) {
1179 self.bump_regular_trivia();
1180 if !self.expect_kind(open, missing_message) {
1181 return;
1182 }
1183
1184 let mut depth = 1usize;
1185 while !self.eof() {
1186 let kind = self.current_kind().expect("not eof");
1187 if kind == open {
1188 depth += 1;
1189 } else if kind == close {
1190 depth -= 1;
1191 }
1192 self.bump();
1193 if depth == 0 {
1194 return;
1195 }
1196 }
1197
1198 self.error_at_eof(missing_message);
1199 }
1200
1201 fn should_continue_instruction_after_whitespace(
1202 &self,
1203 previous_significant: Option<SyntaxKind>,
1204 nesting: Nesting,
1205 ) -> bool {
1206 if !nesting.is_root() {
1207 return true;
1208 }
1209
1210 let Some(previous_significant) = previous_significant else {
1211 return false;
1212 };
1213 if expects_continuation_operand(previous_significant) {
1214 return true;
1215 }
1216
1217 matches!(
1218 self.peek_after_inline_whitespace(),
1219 Some(
1220 SyntaxKind::Dot
1221 | SyntaxKind::Equal
1222 | SyntaxKind::Comma
1223 | SyntaxKind::DotDot
1224 | SyntaxKind::Colon
1225 | SyntaxKind::ColonColon
1226 | SyntaxKind::RArrow
1227 | SyntaxKind::Plus
1228 | SyntaxKind::Minus
1229 | SyntaxKind::Star
1230 | SyntaxKind::Slash
1231 | SyntaxKind::SlashSlash
1232 | SyntaxKind::RBracket
1233 | SyntaxKind::RParen
1234 | SyntaxKind::RBrace
1235 )
1236 )
1237 }
1238
1239 fn should_stop_instruction_before(
1240 &self,
1241 current: SyntaxKind,
1242 previous_significant: Option<SyntaxKind>,
1243 nesting: Nesting,
1244 ) -> bool {
1245 if !nesting.is_root() {
1246 return false;
1247 }
1248
1249 if let Some(previous_significant) = previous_significant
1250 && expects_continuation_operand(previous_significant)
1251 {
1252 return false;
1253 }
1254
1255 if punctuation_continues_instruction(current) {
1256 return false;
1257 }
1258
1259 self.at_terminator(&["else", "end"])
1260 || self.can_start_operation()
1261 || self.at_block_recovery_boundary()
1262 }
1263
1264 fn line_break_starts_new_top_level_item(&self) -> bool {
1265 match self.next_relevant_top_level_token(self.pos + 1) {
1266 Some(index) => self.is_top_level_starter(index),
1267 None => true,
1268 }
1269 }
1270
1271 fn next_relevant_top_level_token(&self, mut index: usize) -> Option<usize> {
1272 while let Some(token) = self.tokens.get(index) {
1273 match token.kind() {
1274 SyntaxKind::Whitespace | SyntaxKind::Newline | SyntaxKind::Comment => index += 1,
1275 _ => return Some(index),
1276 }
1277 }
1278 None
1279 }
1280
1281 fn next_relevant_block_token(&self, mut index: usize) -> Option<usize> {
1282 while let Some(token) = self.tokens.get(index) {
1283 match token.kind() {
1284 SyntaxKind::Whitespace
1285 | SyntaxKind::Newline
1286 | SyntaxKind::Comment
1287 | SyntaxKind::DocComment => index += 1,
1288 _ => return Some(index),
1289 }
1290 }
1291 None
1292 }
1293
1294 fn peek_after_inline_whitespace(&self) -> Option<SyntaxKind> {
1295 let mut index = self.pos;
1296 while let Some(token) = self.tokens.get(index) {
1297 match token.kind() {
1298 SyntaxKind::Whitespace => index += 1,
1299 SyntaxKind::Newline | SyntaxKind::Comment | SyntaxKind::DocComment => return None,
1300 kind => return Some(kind),
1301 }
1302 }
1303 None
1304 }
1305
1306 fn peek_after_non_comment_trivia(&self) -> Option<SyntaxKind> {
1307 self.peek_token_after_non_comment_trivia().map(Token::kind)
1308 }
1309
1310 fn peek_token_after_non_comment_trivia(&self) -> Option<&Token<'input>> {
1311 let mut index = self.pos;
1312 while let Some(token) = self.tokens.get(index) {
1313 match token.kind() {
1314 SyntaxKind::Whitespace | SyntaxKind::Newline => index += 1,
1315 _ => return Some(token),
1316 }
1317 }
1318 None
1319 }
1320
1321 fn peek_contextual_keyword_after_non_comment_trivia(&self, keyword: &str) -> bool {
1322 matches!(
1323 self.peek_token_after_non_comment_trivia(),
1324 Some(token) if token.kind() == SyntaxKind::Ident && token.text() == keyword
1325 )
1326 }
1327
1328 fn is_top_level_starter(&self, index: usize) -> bool {
1329 let Some(token) = self.tokens.get(index) else {
1330 return false;
1331 };
1332
1333 token.kind() == SyntaxKind::DocComment
1334 || token.kind() == SyntaxKind::At
1335 || (token.kind() == SyntaxKind::Ident
1336 && match token.text() {
1337 "adv_map" | "begin" | "const" | "enum" | "mod" | "namespace" | "proc"
1338 | "type" | "use" => true,
1339 "extern" => matches!(
1340 self.next_relevant_top_level_token(index + 1)
1341 .and_then(|next| self.tokens.get(next)),
1342 Some(next) if next.kind() == SyntaxKind::Ident && next.text() == "package"
1343 ),
1344 "pub" => matches!(
1345 self.next_relevant_top_level_token(index + 1)
1346 .and_then(|next| self.tokens.get(next)),
1347 Some(next)
1348 if next.kind() == SyntaxKind::Ident
1349 && matches!(next.text(), "const" | "enum" | "mod" | "proc" | "type" | "use")
1350 ),
1351 _ => false,
1352 })
1353 }
1354
1355 fn can_start_operation(&self) -> bool {
1356 self.can_start_instruction()
1357 || self.at_keyword("if")
1358 || self.at_keyword("do")
1359 || self.at_keyword("while")
1360 || self.at_keyword("repeat")
1361 }
1362
1363 fn can_start_instruction(&self) -> bool {
1364 matches!(
1365 self.current(),
1366 Some(token)
1367 if matches!(
1368 token.kind(),
1369 SyntaxKind::Ident | SyntaxKind::SpecialIdent | SyntaxKind::QuotedIdent
1370 ) && (token.kind() != SyntaxKind::Ident
1371 || !is_reserved_block_keyword(token.text()))
1372 )
1373 }
1374
1375 fn block_recovery_boundary(&self, terminators: &[&str]) -> Option<BlockRecoveryBoundary> {
1376 if self.at_keyword("else") && !terminators.contains(&"else") {
1377 return Some(BlockRecoveryBoundary::Else);
1378 }
1379
1380 if self.at_top_level_form_starter_in_block() {
1381 return Some(BlockRecoveryBoundary::TopLevelItem);
1382 }
1383
1384 None
1385 }
1386
1387 fn at_block_recovery_boundary(&self) -> bool {
1388 self.at_keyword("else") || self.at_top_level_form_starter_in_block()
1389 }
1390
1391 fn at_top_level_form_starter(&self) -> bool {
1392 self.is_top_level_starter(self.pos)
1393 }
1394
1395 fn at_top_level_form_starter_in_block(&self) -> bool {
1396 match self.current() {
1397 Some(token) if token.kind() == SyntaxKind::DocComment => self
1398 .next_relevant_block_token(self.pos + 1)
1399 .is_some_and(|index| self.is_top_level_starter(index)),
1400 _ => self.at_top_level_form_starter(),
1401 }
1402 }
1403
1404 fn at_terminator(&self, terminators: &[&str]) -> bool {
1405 terminators.iter().any(|terminator| {
1406 if *terminator == "while" {
1410 self.at_bare_while()
1411 } else {
1412 self.at_keyword(terminator)
1413 }
1414 })
1415 }
1416
1417 fn at_bare_while(&self) -> bool {
1425 self.at_keyword("while")
1426 && !matches!(
1427 self.tokens.get(self.pos + 1),
1428 Some(token) if token.kind() == SyntaxKind::Dot
1429 )
1430 }
1431
1432 fn at_name_like(&self) -> bool {
1433 matches!(
1434 self.current_kind(),
1435 Some(SyntaxKind::Ident | SyntaxKind::SpecialIdent | SyntaxKind::QuotedIdent)
1436 )
1437 }
1438
1439 fn at_package_name_like(&self) -> bool {
1440 self.at_name_like() || self.at_kind(SyntaxKind::QuotedString)
1441 }
1442
1443 fn at_import_list_recovery_boundary(&self) -> bool {
1444 self.eof() || matches!(self.current_kind(), Some(SyntaxKind::Comma | SyntaxKind::RBrace))
1445 }
1446
1447 fn at_keyword(&self, keyword: &str) -> bool {
1448 matches!(self.current(), Some(token) if token.kind() == SyntaxKind::Ident && token.text() == keyword)
1449 }
1450
1451 fn at_prefixed_keyword(&self, prefix: &str, keyword: &str) -> bool {
1452 if !self.at_keyword(prefix) {
1453 return false;
1454 }
1455
1456 matches!(
1457 self.next_relevant_top_level_token(self.pos + 1).and_then(|index| self.tokens.get(index)),
1458 Some(token) if token.kind() == SyntaxKind::Ident && token.text() == keyword
1459 )
1460 }
1461
1462 fn at_regular_trivia(&self) -> bool {
1463 matches!(
1464 self.current_kind(),
1465 Some(SyntaxKind::Whitespace | SyntaxKind::Newline | SyntaxKind::Comment)
1466 )
1467 }
1468
1469 fn at_kind(&self, kind: SyntaxKind) -> bool {
1470 self.current_kind() == Some(kind)
1471 }
1472
1473 fn current(&self) -> Option<&Token<'input>> {
1474 self.tokens.get(self.pos)
1475 }
1476
1477 fn current_kind(&self) -> Option<SyntaxKind> {
1478 self.current().map(Token::kind)
1479 }
1480
1481 fn eof(&self) -> bool {
1482 self.pos >= self.tokens.len()
1483 }
1484
1485 fn bump_regular_trivia(&mut self) {
1486 while self.at_regular_trivia() {
1487 self.bump();
1488 }
1489 }
1490
1491 fn bump_non_comment_trivia(&mut self) {
1492 while matches!(self.current_kind(), Some(SyntaxKind::Whitespace | SyntaxKind::Newline)) {
1493 self.bump();
1494 }
1495 }
1496
1497 fn bump_inline_whitespace(&mut self) {
1498 while self.at_kind(SyntaxKind::Whitespace) {
1499 self.bump();
1500 }
1501 }
1502
1503 fn expect_keyword(&mut self, keyword: &str, message: &'static str) -> bool {
1504 self.bump_regular_trivia();
1505 if self.at_keyword(keyword) {
1506 self.bump();
1507 true
1508 } else {
1509 self.error_here(message);
1510 false
1511 }
1512 }
1513
1514 fn expect_kind(&mut self, kind: SyntaxKind, message: &'static str) -> bool {
1515 self.bump_regular_trivia();
1516 if self.at_kind(kind) {
1517 self.bump();
1518 true
1519 } else {
1520 self.error_here(message);
1521 false
1522 }
1523 }
1524
1525 fn start_node(&mut self, kind: SyntaxKind) {
1526 self.builder.start_node(kind.into());
1527 }
1528
1529 fn finish_node(&mut self) {
1530 self.builder.finish_node();
1531 }
1532
1533 fn bump(&mut self) {
1534 if let Some(token) = self.current() {
1535 let kind = token.kind();
1536 let span = token.span();
1537 let text = token.text();
1538 if kind == SyntaxKind::Error {
1539 self.diagnostics.push(diagnostic!(
1540 severity = Severity::Error,
1541 labels = vec![LabeledSpan::at(span, format!("unrecognized token `{text}`"))],
1542 "syntax error"
1543 ));
1544 }
1545 self.builder.token(kind.into(), text);
1546 self.pos += 1;
1547 }
1548 }
1549
1550 fn bump_nesting(&mut self, nesting: &mut Nesting, kind: SyntaxKind) {
1551 match nesting.bump(kind) {
1552 Ok(next) => *nesting = next,
1553 Err(closing) => {
1554 self.error_here(format!(
1555 "unexpected closing delimiter `{}`",
1556 closing_delimiter_text(closing)
1557 ));
1558 },
1559 }
1560 }
1561
1562 fn error_here(&mut self, message: impl Into<String>) {
1563 let span = self.current().map(Token::span).unwrap_or(self.eof_span);
1564 self.error_at_span(span, message);
1565 }
1566
1567 fn error_at_span(&mut self, span: SourceSpan, message: impl Into<String>) {
1568 self.diagnostics.push(diagnostic!(
1569 severity = Severity::Error,
1570 labels = vec![LabeledSpan::at(span, message.into())],
1571 "syntax error"
1572 ));
1573 }
1574
1575 fn error_at_eof(&mut self, message: impl Into<String>) {
1576 self.diagnostics.push(diagnostic!(
1577 severity = Severity::Error,
1578 labels = vec![LabeledSpan::at(self.eof_span, message.into())],
1579 "syntax error"
1580 ));
1581 }
1582}
1583
1584fn detached_source_file(input: &str) -> Arc<SourceFile> {
1585 Arc::new(SourceFile::new(
1586 SourceId::UNKNOWN,
1587 SourceLanguage::Masm,
1588 Uri::new("memory:///inline.masm"),
1589 input.to_owned().into_boxed_str(),
1590 ))
1591}
1592
1593fn eof_anchor_span(source: &SourceFile, bounds: Option<core::ops::Range<usize>>) -> SourceSpan {
1594 let content = source.as_str();
1595 let (content, start) = match bounds {
1596 Some(range) => (&content[range.start..range.end], range.start),
1597 None => (content, 0),
1598 };
1599 content
1600 .char_indices()
1601 .last()
1602 .map(|(offset, _)| {
1603 SourceSpan::at(
1604 source.id(),
1605 u32::try_from(offset).expect("source files larger than 4GiB are not supported"),
1606 )
1607 })
1608 .unwrap_or_else(|| {
1609 SourceSpan::try_from_range(source.id(), start..start)
1610 .expect("source files larger than 4GiB are not supported")
1611 })
1612}
1613
1614fn source_span_from_text_range(source_id: SourceId, range: TextRange) -> SourceSpan {
1615 SourceSpan::new(source_id, u32::from(range.start())..u32::from(range.end()))
1616}
1617
1618fn closing_delimiter_text(kind: SyntaxKind) -> &'static str {
1619 match kind {
1620 SyntaxKind::RParen => ")",
1621 SyntaxKind::RBracket => "]",
1622 SyntaxKind::RBrace => "}",
1623 _ => "delimiter",
1624 }
1625}
1626
1627fn expects_continuation_operand(kind: SyntaxKind) -> bool {
1628 matches!(
1629 kind,
1630 SyntaxKind::Dot
1631 | SyntaxKind::Equal
1632 | SyntaxKind::Comma
1633 | SyntaxKind::DotDot
1634 | SyntaxKind::Colon
1635 | SyntaxKind::ColonColon
1636 | SyntaxKind::RArrow
1637 | SyntaxKind::Plus
1638 | SyntaxKind::Minus
1639 | SyntaxKind::Star
1640 | SyntaxKind::Slash
1641 | SyntaxKind::SlashSlash
1642 | SyntaxKind::LBracket
1643 | SyntaxKind::LParen
1644 | SyntaxKind::LBrace
1645 )
1646}
1647
1648fn punctuation_continues_instruction(kind: SyntaxKind) -> bool {
1649 matches!(
1650 kind,
1651 SyntaxKind::Dot
1652 | SyntaxKind::Equal
1653 | SyntaxKind::Comma
1654 | SyntaxKind::DotDot
1655 | SyntaxKind::Colon
1656 | SyntaxKind::ColonColon
1657 | SyntaxKind::RArrow
1658 | SyntaxKind::Plus
1659 | SyntaxKind::Minus
1660 | SyntaxKind::Star
1661 | SyntaxKind::Slash
1662 | SyntaxKind::SlashSlash
1663 | SyntaxKind::RBracket
1664 | SyntaxKind::RParen
1665 | SyntaxKind::RBrace
1666 )
1667}
1668
1669fn is_reserved_block_keyword(text: &str) -> bool {
1670 matches!(
1671 text,
1672 "adv_map"
1673 | "begin"
1674 | "const"
1675 | "do"
1676 | "else"
1677 | "end"
1678 | "enum"
1679 | "extern"
1680 | "if"
1681 | "mod"
1682 | "namespace"
1683 | "proc"
1684 | "pub"
1685 | "repeat"
1686 | "type"
1687 | "use"
1688 | "while"
1689 )
1690}
1691
1692#[cfg(test)]
1693mod tests {
1694 use std::{
1695 fs,
1696 path::{Path, PathBuf},
1697 string::{String, ToString},
1698 sync::Arc,
1699 vec::Vec,
1700 };
1701
1702 use miden_debug_types::{
1703 SourceFile as ManagedSourceFile, SourceId, SourceLanguage, SourceSpan, Uri,
1704 };
1705 use rowan::ast::AstNode;
1706
1707 use crate::{
1708 ast::{ImportKind, Item, SourceFile as AstSourceFile},
1709 parse_source_file, parse_text,
1710 parser::parse_inline_masm_text,
1711 syntax::SyntaxKind,
1712 };
1713
1714 fn repo_root() -> PathBuf {
1715 Path::new(env!("CARGO_MANIFEST_DIR"))
1716 .parent()
1717 .and_then(Path::parent)
1718 .expect("workspace root should be two levels above crates/assembly-syntax-cst")
1719 .to_path_buf()
1720 }
1721
1722 fn checked_in_masm_corpus() -> Vec<PathBuf> {
1723 let root = repo_root();
1724 let mut files = Vec::new();
1725 for relative in [
1726 "crates/lib/core/asm",
1727 "miden-vm/masm-examples",
1728 "miden-vm/tests/integration/cli/data",
1729 ] {
1730 collect_masm_files(&root.join(relative), &mut files);
1731 }
1732 files.sort();
1733 files
1734 }
1735
1736 fn collect_masm_files(dir: &Path, files: &mut Vec<PathBuf>) {
1737 let entries = fs::read_dir(dir)
1738 .unwrap_or_else(|error| panic!("failed to read {}: {error}", dir.display()));
1739 for entry in entries {
1740 let entry = entry.unwrap_or_else(|error| {
1741 panic!("failed to read a directory entry under {}: {error}", dir.display())
1742 });
1743 let path = entry.path();
1744 if path.is_dir() {
1745 collect_masm_files(&path, files);
1746 } else if path.extension().is_some_and(|ext| ext == "masm") {
1747 files.push(path);
1748 }
1749 }
1750 }
1751
1752 fn representative_round_trip_sources() -> &'static [&'static str] {
1753 &[
1754 "",
1755 "# leading comment\n#! module docs\npub const X = [0x01, 0x02]\n",
1756 "\
1757namespace std::math
1758extern package \"miden/base@0.1.0\"
1759mod internal
1760pub mod u64
1761",
1762 "\
1763@inline
1764# keep standalone
1765@locals(1)
1766pub proc foo(a: felt)
1767 # body
1768 push.1
1769end
1770",
1771 "\
1772begin
1773 if.true
1774 repeat.4
1775 swap dup.1 add
1776 end
1777 else
1778 while.true
1779 nop
1780 end
1781 end
1782end
1783",
1784 "\
1785pub type Account = struct { id: felt, vault: ptr<u8, addrspace(byte)> }
1786adv_map TABLE = [
1787 [1, 2],
1788 event(foo(bar, baz)),
1789]
1790",
1791 ]
1792 }
1793
1794 fn assert_lossless_parse(input: &str, label: impl core::fmt::Display) {
1795 let parse = parse_text(input);
1796 assert_eq!(
1797 parse.syntax().text().to_string(),
1798 input,
1799 "CST parse was not lossless for {label}"
1800 );
1801 }
1802
1803 fn diagnostic_labels(parse: &super::Parse) -> Vec<String> {
1804 parse
1805 .diagnostics()
1806 .iter()
1807 .flat_map(|diag| diag.labels.as_deref().unwrap_or(&[]).iter())
1808 .filter_map(|label| label.label())
1809 .map(ToString::to_string)
1810 .collect()
1811 }
1812
1813 fn assert_import_rejected(source: &str, expected_label: &str) {
1814 let parse = parse_text(source);
1815 assert!(parse.has_errors(), "expected {source:?} to be rejected");
1816 let labels = diagnostic_labels(&parse);
1817 assert!(
1818 labels.iter().any(|label| label.contains(expected_label)),
1819 "expected {source:?} to report {expected_label:?}, got {:?}",
1820 parse.diagnostics()
1821 );
1822 }
1823
1824 #[test]
1825 fn parse_text_is_lossless_for_representative_sources() {
1826 for (index, source) in representative_round_trip_sources().iter().enumerate() {
1827 assert_lossless_parse(source, format_args!("representative source {index}"));
1828 }
1829 }
1830
1831 #[test]
1832 fn parse_text_is_lossless_for_checked_in_masm_corpus() {
1833 let files = checked_in_masm_corpus();
1834 assert!(
1835 !files.is_empty(),
1836 "expected the checked-in MASM corpus to contain at least one source file"
1837 );
1838
1839 for path in files {
1840 let source = fs::read_to_string(&path)
1841 .unwrap_or_else(|error| panic!("failed to read {}: {error}", path.display()));
1842 assert_lossless_parse(&source, path.display());
1843 }
1844 }
1845
1846 #[test]
1847 fn parse_text_as_inline_masm() {
1848 let source = "\
1849 if.true
1850 repeat.4
1851 swap dup.1 add
1852 end
1853 else
1854 while.true
1855 nop
1856 end
1857 end
1858 ";
1859 let parse = parse_inline_masm_text(source, None);
1860 assert!(!parse.has_errors());
1861 let root = parse.syntax();
1862 assert_eq!(root.kind(), SyntaxKind::Block);
1863
1864 let child_kinds = root.children().map(|child| child.kind()).collect::<Vec<_>>();
1865 assert_eq!(child_kinds, vec![SyntaxKind::IfOp,]);
1866
1867 assert!(root.descendants().any(|node| node.kind() == SyntaxKind::IfOp));
1868 assert!(root.descendants().any(|node| node.kind() == SyntaxKind::RepeatOp));
1869 assert!(root.descendants().any(|node| node.kind() == SyntaxKind::WhileOp));
1870 }
1871
1872 #[test]
1873 fn parse_do_while_loop() {
1874 let source = "\
1875 do
1876 push.1
1877 dup.0 neq.0
1878 while
1879 dup.0 lt.10
1880 end
1881 ";
1882 let parse = parse_inline_masm_text(source, None);
1883 let mut parse = parse;
1884 let diagnostics = parse.take_diagnostics();
1885 assert!(diagnostics.is_empty(), "unexpected parse errors: {diagnostics:?}");
1886 let root = parse.syntax();
1887 let child_kinds = root.children().map(|child| child.kind()).collect::<Vec<_>>();
1888 assert_eq!(child_kinds, vec![SyntaxKind::DoWhileOp]);
1889
1890 let do_while = root.children().find(|n| n.kind() == SyntaxKind::DoWhileOp).unwrap();
1892 let block_count = do_while.children().filter(|n| n.kind() == SyntaxKind::Block).count();
1893 assert_eq!(block_count, 2, "expected a body block and a condition block");
1894 }
1895
1896 #[test]
1897 fn parse_do_while_with_nested_while_true_in_body() {
1898 let source = "\
1901 do
1902 while.true
1903 nop
1904 end
1905 push.1
1906 while
1907 eq.0
1908 end
1909 ";
1910 let parse = parse_inline_masm_text(source, None);
1911 let mut parse = parse;
1912 let diagnostics = parse.take_diagnostics();
1913 assert!(diagnostics.is_empty(), "unexpected parse errors: {diagnostics:?}");
1914 let root = parse.syntax();
1915 let child_kinds = root.children().map(|child| child.kind()).collect::<Vec<_>>();
1916 assert_eq!(child_kinds, vec![SyntaxKind::DoWhileOp]);
1917
1918 let do_while = root.children().find(|n| n.kind() == SyntaxKind::DoWhileOp).unwrap();
1920 assert!(do_while.descendants().any(|node| node.kind() == SyntaxKind::WhileOp));
1921 }
1922
1923 #[test]
1924 fn do_while_terminator_requires_dot_adjacent_to_while() {
1925 let source = "\
1929 do
1930 push.1
1931 while .true
1932 end
1933 ";
1934 let mut parse = parse_inline_masm_text(source, None);
1935 let diagnostics = parse.take_diagnostics();
1936 assert!(!diagnostics.is_empty(), "expected a syntax error for the stray `.true`");
1937
1938 let root = parse.syntax();
1940 assert!(root.descendants().any(|node| node.kind() == SyntaxKind::DoWhileOp));
1941 }
1942
1943 #[test]
1944 fn parses_top_level_forms_and_nested_structured_ops() {
1945 let source = "\
1946#! docs
1947namespace std::math
1948extern package \"miden/base@0.1.0\"
1949mod internal
1950pub mod u64
1951pub use {bar as baz} from foo
1952pub const X = 1
1953pub type FeltAlias = felt
1954adv_map TABLE = [0x01, 0x02]
1955begin
1956 if.true
1957 repeat.4
1958 swap dup.1 add
1959 end
1960 else
1961 while.true
1962 nop
1963 end
1964 end
1965end
1966pub proc foo(a) -> (b)
1967 exec.bar
1968end
1969";
1970 let parse = parse_text(source);
1971 assert!(!parse.has_errors());
1972 let root = parse.syntax();
1973 assert_eq!(root.kind(), SyntaxKind::SourceFile);
1974
1975 let child_kinds = root.children().map(|child| child.kind()).collect::<Vec<_>>();
1976 assert_eq!(
1977 child_kinds,
1978 vec![
1979 SyntaxKind::Doc,
1980 SyntaxKind::Namespace,
1981 SyntaxKind::ExternPackage,
1982 SyntaxKind::Submodule,
1983 SyntaxKind::Submodule,
1984 SyntaxKind::Import,
1985 SyntaxKind::Constant,
1986 SyntaxKind::TypeDecl,
1987 SyntaxKind::AdviceMap,
1988 SyntaxKind::BeginBlock,
1989 SyntaxKind::Procedure,
1990 ]
1991 );
1992
1993 assert!(root.descendants().any(|node| node.kind() == SyntaxKind::IfOp));
1994 assert!(root.descendants().any(|node| node.kind() == SyntaxKind::RepeatOp));
1995 assert!(root.descendants().any(|node| node.kind() == SyntaxKind::WhileOp));
1996 }
1997
1998 #[test]
1999 fn exposes_typed_wrappers_for_structured_top_level_forms() {
2000 let source = "\
2001namespace app::accounts
2002extern package \"miden/base@0.1.0\"
2003mod internal
2004pub mod api
2005use miden::core::mem as memory
2006pub const EVENT = event(\"miden::event\")
2007pub enum Bool : u8 {
2008 FALSE,
2009 TRUE = 1,
2010}
2011adv_map TABLE(0x0200000000000000020000000000000002000000000000000200000000000000) = [0x01, 0x02]
2012";
2013
2014 let parse = parse_text(source);
2015 assert!(!parse.has_errors(), "{:?}", parse.diagnostics());
2016
2017 let source_file = AstSourceFile::cast(parse.syntax()).expect("source file");
2018 let items = source_file.items().collect::<Vec<_>>();
2019 assert_eq!(items.len(), 8);
2020
2021 let Item::Namespace(namespace) = &items[0] else {
2022 panic!("expected namespace, got {:?}", items[0]);
2023 };
2024 assert_eq!(
2025 namespace
2026 .path()
2027 .expect("namespace path")
2028 .segments()
2029 .map(|segment| segment.text().to_string())
2030 .collect::<Vec<_>>(),
2031 vec!["app", "accounts"]
2032 );
2033
2034 let Item::ExternPackage(package) = &items[1] else {
2035 panic!("expected extern package, got {:?}", items[1]);
2036 };
2037 assert_eq!(package.package_token().expect("package name").text(), "\"miden/base@0.1.0\"");
2038
2039 let Item::Submodule(submodule) = &items[2] else {
2040 panic!("expected submodule, got {:?}", items[2]);
2041 };
2042 assert!(submodule.visibility().is_none());
2043 assert_eq!(submodule.name_token().expect("submodule name").text(), "internal");
2044
2045 let Item::Submodule(submodule) = &items[3] else {
2046 panic!("expected public submodule, got {:?}", items[3]);
2047 };
2048 assert!(submodule.visibility().is_some());
2049 assert_eq!(submodule.name_token().expect("submodule name").text(), "api");
2050
2051 let Item::Import(import) = &items[4] else {
2052 panic!("expected import, got {:?}", items[0]);
2053 };
2054 assert_eq!(import.kind(), ImportKind::Module);
2055 assert_eq!(
2056 import
2057 .module_path()
2058 .expect("import path")
2059 .segments()
2060 .map(|segment| segment.text().to_string())
2061 .collect::<Vec<_>>(),
2062 vec!["miden", "core", "mem"]
2063 );
2064 assert_eq!(import.module_alias_token().expect("alias").text(), "memory");
2065
2066 let Item::Constant(constant) = &items[5] else {
2067 panic!("expected constant, got {:?}", items[1]);
2068 };
2069 assert_eq!(constant.name_token().expect("constant name").text(), "EVENT");
2070 assert_eq!(
2071 constant
2072 .expr()
2073 .expect("constant expr")
2074 .significant_tokens()
2075 .map(|token| token.text().to_string())
2076 .collect::<Vec<_>>(),
2077 vec!["event", "(", "\"miden::event\"", ")"]
2078 );
2079
2080 let Item::TypeDecl(type_decl) = &items[6] else {
2081 panic!("expected type declaration, got {:?}", items[2]);
2082 };
2083 assert_eq!(type_decl.keyword_token().expect("type keyword").text(), "enum");
2084 assert_eq!(type_decl.name_token().expect("type name").text(), "Bool");
2085 assert!(
2086 type_decl.body().is_some(),
2087 "expected enum declaration to expose a structured type body"
2088 );
2089
2090 let Item::AdviceMap(advice_map) = &items[7] else {
2091 panic!("expected advice map, got {:?}", items[3]);
2092 };
2093 assert_eq!(advice_map.name_token().expect("advice map name").text(), "TABLE");
2094 assert_eq!(
2095 advice_map
2096 .value_expr()
2097 .expect("advice map value")
2098 .significant_tokens()
2099 .map(|token| token.text().to_string())
2100 .collect::<Vec<_>>(),
2101 vec!["[", "0x01", ",", "0x02", "]"]
2102 );
2103 }
2104
2105 #[test]
2106 fn parses_unparenthesized_procedure_result_types() {
2107 let source = "\
2108pub proc println(message: ptr<u8, addrspace(byte)>) -> ptr<u8, addrspace(byte)>
2109 nop
2110end
2111";
2112
2113 let parse = parse_text(source);
2114 assert!(!parse.has_errors(), "{:?}", parse.diagnostics());
2115
2116 let root = parse.syntax();
2117 let source_file = AstSourceFile::cast(root).expect("source file");
2118 let items = source_file.items().collect::<Vec<_>>();
2119 assert_eq!(items.len(), 1);
2120
2121 let Item::Procedure(procedure) = &items[0] else {
2122 panic!("expected procedure, got {:?}", items[0]);
2123 };
2124 assert!(
2125 procedure.signature().is_some(),
2126 "expected procedure to retain its signature node"
2127 );
2128 }
2129
2130 #[test]
2131 fn no_result_signature_does_not_absorb_body_leading_trivia() {
2132 let source = "\
2133pub proc foo()
2134 # body
2135 nop
2136end
2137";
2138
2139 let parse = parse_text(source);
2140 assert!(!parse.has_errors(), "{:?}", parse.diagnostics());
2141
2142 let source_file = AstSourceFile::cast(parse.syntax()).expect("source file");
2143 let items = source_file.items().collect::<Vec<_>>();
2144 let Item::Procedure(procedure) = &items[0] else {
2145 panic!("expected procedure, got {:?}", items[0]);
2146 };
2147
2148 let signature = procedure.signature().expect("procedure signature");
2149 assert_eq!(signature.syntax().text().to_string(), "()");
2150
2151 let block = procedure.block().expect("procedure block");
2152 assert!(
2153 block.syntax().text().to_string().contains("# body"),
2154 "expected body-leading comment to remain in the block"
2155 );
2156 }
2157
2158 #[test]
2159 fn multiline_result_arrow_stays_in_signature() {
2160 let source = "\
2161pub proc foo()
2162 -> felt
2163 nop
2164end
2165";
2166
2167 let parse = parse_text(source);
2168 assert!(!parse.has_errors(), "{:?}", parse.diagnostics());
2169
2170 let source_file = AstSourceFile::cast(parse.syntax()).expect("source file");
2171 let items = source_file.items().collect::<Vec<_>>();
2172 let Item::Procedure(procedure) = &items[0] else {
2173 panic!("expected procedure, got {:?}", items[0]);
2174 };
2175
2176 let signature = procedure.signature().expect("procedure signature");
2177 assert_eq!(signature.syntax().text().to_string(), "()\n -> felt");
2178 }
2179
2180 #[test]
2181 fn cst_import_parses_multiline_module_aliases() {
2182 let source = "\
2183use ::miden::core::collections::sorted_array::lowerbound_key_value
2184 as lowerbound_key_value
2185";
2186
2187 let parse = parse_text(source);
2188 assert!(!parse.has_errors(), "{:?}", parse.diagnostics());
2189
2190 let source_file = AstSourceFile::cast(parse.syntax()).expect("source file");
2191 let items = source_file.items().collect::<Vec<_>>();
2192 assert_eq!(items.len(), 1);
2193
2194 let Item::Import(import) = &items[0] else {
2195 panic!("expected import, got {:?}", items[0]);
2196 };
2197 assert_eq!(import.kind(), ImportKind::Module);
2198 assert_eq!(import.module_alias_token().expect("alias").text(), "lowerbound_key_value");
2199 }
2200
2201 #[test]
2202 fn cst_import_parses_module_imports() {
2203 let source = "\
2204use foo
2205use some::module
2206use some::module as sm
2207";
2208
2209 let parse = parse_text(source);
2210 assert!(!parse.has_errors(), "{:?}", parse.diagnostics());
2211
2212 let source_file = AstSourceFile::cast(parse.syntax()).expect("source file");
2213 let imports = source_file
2214 .items()
2215 .map(|item| match item {
2216 Item::Import(import) => import,
2217 other => panic!("expected import, got {other:?}"),
2218 })
2219 .collect::<Vec<_>>();
2220 assert_eq!(imports.len(), 3);
2221
2222 let first = &imports[0];
2223 assert_eq!(first.kind(), ImportKind::Module);
2224 assert!(first.visibility().is_none());
2225 assert_eq!(
2226 first
2227 .module_path()
2228 .expect("module path")
2229 .segments()
2230 .map(|segment| segment.text().to_string())
2231 .collect::<Vec<_>>(),
2232 vec!["foo"]
2233 );
2234 assert!(first.module_alias_token().is_none());
2235
2236 let second = &imports[1];
2237 assert_eq!(second.kind(), ImportKind::Module);
2238 assert_eq!(
2239 second
2240 .module_path()
2241 .expect("module path")
2242 .segments()
2243 .map(|segment| segment.text().to_string())
2244 .collect::<Vec<_>>(),
2245 vec!["some", "module"]
2246 );
2247 assert!(second.module_alias_token().is_none());
2248
2249 let third = &imports[2];
2250 assert_eq!(third.kind(), ImportKind::Module);
2251 assert_eq!(
2252 third
2253 .module_path()
2254 .expect("module path")
2255 .segments()
2256 .map(|segment| segment.text().to_string())
2257 .collect::<Vec<_>>(),
2258 vec!["some", "module"]
2259 );
2260 assert_eq!(third.module_alias_token().expect("alias").text(), "sm");
2261 }
2262
2263 #[test]
2264 fn cst_import_parses_item_imports() {
2265 let source = "\
2266use {foo, bar as baz} from some::module
2267pub use {alpha} from core
2268";
2269
2270 let parse = parse_text(source);
2271 assert!(!parse.has_errors(), "{:?}", parse.diagnostics());
2272
2273 let source_file = AstSourceFile::cast(parse.syntax()).expect("source file");
2274 let imports = source_file
2275 .items()
2276 .map(|item| match item {
2277 Item::Import(import) => import,
2278 other => panic!("expected import, got {other:?}"),
2279 })
2280 .collect::<Vec<_>>();
2281 assert_eq!(imports.len(), 2);
2282
2283 let first = &imports[0];
2284 assert_eq!(first.kind(), ImportKind::Items);
2285 assert!(first.visibility().is_none());
2286 assert!(
2287 first.path().is_none(),
2288 "item imports must not masquerade as legacy path imports"
2289 );
2290 assert!(
2291 first.module_alias_token().is_none(),
2292 "item aliases must not masquerade as module aliases"
2293 );
2294 assert_eq!(
2295 first
2296 .module_path()
2297 .expect("module path")
2298 .segments()
2299 .map(|segment| segment.text().to_string())
2300 .collect::<Vec<_>>(),
2301 vec!["some", "module"]
2302 );
2303 let specs = first.item_specs().collect::<Vec<_>>();
2304 assert_eq!(specs.len(), 2);
2305 assert_eq!(specs[0].name_token().expect("item name").text(), "foo");
2306 assert!(specs[0].alias_token().is_none());
2307 assert_eq!(specs[1].name_token().expect("item name").text(), "bar");
2308 assert_eq!(specs[1].alias_token().expect("item alias").text(), "baz");
2309
2310 let second = &imports[1];
2311 assert_eq!(second.kind(), ImportKind::Items);
2312 assert!(second.visibility().is_some());
2313 assert_eq!(
2314 second
2315 .module_path()
2316 .expect("module path")
2317 .segments()
2318 .map(|segment| segment.text().to_string())
2319 .collect::<Vec<_>>(),
2320 vec!["core"]
2321 );
2322 let specs = second.item_specs().collect::<Vec<_>>();
2323 assert_eq!(specs.len(), 1);
2324 assert_eq!(specs[0].name_token().expect("item name").text(), "alpha");
2325 }
2326
2327 #[test]
2328 fn cst_import_accepts_quoted_special_and_contextual_names() {
2329 let source = "\
2330use \"as\"::\"from\" as \"module\"
2331use {as, from as as, \"as\" as \"from\", $kernel} from \"from\"::\"as\"
2332";
2333
2334 let parse = parse_text(source);
2335 assert!(!parse.has_errors(), "{:?}", parse.diagnostics());
2336
2337 let source_file = AstSourceFile::cast(parse.syntax()).expect("source file");
2338 let imports = source_file
2339 .items()
2340 .map(|item| match item {
2341 Item::Import(import) => import,
2342 other => panic!("expected import, got {other:?}"),
2343 })
2344 .collect::<Vec<_>>();
2345 assert_eq!(imports.len(), 2);
2346
2347 let module = &imports[0];
2348 assert_eq!(
2349 module
2350 .module_path()
2351 .expect("module path")
2352 .segments()
2353 .map(|segment| segment.text().to_string())
2354 .collect::<Vec<_>>(),
2355 vec!["\"as\"", "\"from\""]
2356 );
2357 assert_eq!(module.module_alias_token().expect("module alias").text(), "\"module\"");
2358
2359 let items = &imports[1];
2360 assert_eq!(
2361 items
2362 .module_path()
2363 .expect("module path")
2364 .segments()
2365 .map(|segment| segment.text().to_string())
2366 .collect::<Vec<_>>(),
2367 vec!["\"from\"", "\"as\""]
2368 );
2369 let specs = items.item_specs().collect::<Vec<_>>();
2370 assert_eq!(specs.len(), 4);
2371 assert_eq!(specs[0].name_token().expect("item name").text(), "as");
2372 assert!(specs[0].alias_token().is_none());
2373 assert_eq!(specs[1].name_token().expect("item name").text(), "from");
2374 assert_eq!(specs[1].alias_token().expect("item alias").text(), "as");
2375 assert_eq!(specs[2].name_token().expect("item name").text(), "\"as\"");
2376 assert_eq!(specs[2].alias_token().expect("item alias").text(), "\"from\"");
2377 assert_eq!(specs[3].name_token().expect("item name").text(), "$kernel");
2378 }
2379
2380 #[test]
2381 fn cst_import_rejects_public_module_imports_and_removed_forms() {
2382 let cases = [
2383 ("pub use some::module\n", "`pub use` is only supported for braced item imports"),
2384 ("use foo->bar\n", "import aliases use `as`; `->` is no longer supported"),
2385 ("pub use foo->bar\n", "import aliases use `as`; `->` is no longer supported"),
2386 (
2387 "use {foo->bar} from m\n",
2388 "import aliases use `as`; `->` is no longer supported",
2389 ),
2390 (
2391 "use 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef->foo\n",
2392 "digest imports are not supported",
2393 ),
2394 (
2395 "pub use 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef->foo\n",
2396 "digest imports are not supported",
2397 ),
2398 (
2399 "use {foo} from 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef\n",
2400 "digest imports are not supported",
2401 ),
2402 ];
2403
2404 for (source, expected) in cases {
2405 assert_import_rejected(source, expected);
2406 }
2407 }
2408
2409 #[test]
2410 fn cst_import_does_not_consume_next_item_after_missing_alias_or_from_path() {
2411 let cases = [
2412 ("use\npub const X = 1\n", "expected an import path"),
2413 ("use foo as\npub const X = 1\n", "expected an alias name after `as`"),
2414 ("use {foo} from\npub const X = 1\n", "expected a module path after `from`"),
2415 ];
2416
2417 for (source, expected) in cases {
2418 let parse = parse_text(source);
2419 assert!(parse.has_errors(), "expected {source:?} to be rejected");
2420 let labels = diagnostic_labels(&parse);
2421 assert!(
2422 labels.iter().any(|label| label.contains(expected)),
2423 "expected {source:?} to report {expected:?}, got {:?}",
2424 parse.diagnostics()
2425 );
2426
2427 let source_file = AstSourceFile::cast(parse.syntax()).expect("source file");
2428 let items = source_file.items().collect::<Vec<_>>();
2429 assert_eq!(items.len(), 2, "next top-level item should remain separate");
2430 assert!(matches!(items[0], Item::Import(_)));
2431 let Item::Constant(constant) = &items[1] else {
2432 panic!("expected second item to remain a constant, got {:?}", items[1]);
2433 };
2434 assert!(constant.visibility().is_some(), "`pub` must remain attached to const");
2435 assert_eq!(constant.name_token().expect("constant name").text(), "X");
2436 }
2437 }
2438
2439 #[test]
2440 fn cst_import_rejects_malformed_item_imports() {
2441 let cases = [
2442 ("use {} from m\n", "import lists must contain at least one item"),
2443 ("use {foo} m\n", "expected `from` after import list"),
2444 ("use foo as\n", "expected an alias name after `as`"),
2445 ("use {foo as} from m\n", "expected an alias name after `as`"),
2446 ];
2447
2448 for (source, expected) in cases {
2449 assert_import_rejected(source, expected);
2450 }
2451 }
2452
2453 #[test]
2454 fn cst_import_rejects_wildcard_imports() {
2455 for source in ["use * from m\n", "use {*} from m\n", "use {foo, *} from m\n"] {
2456 assert_import_rejected(source, "wildcard imports are not supported");
2457 }
2458 }
2459
2460 #[test]
2461 fn keeps_header_comments_on_structured_nodes() {
2462 let source = "\
2463use {panic} from ::miden::utils # import
2464pub proc long_name(arg: felt) # proc
2465 nop
2466end
2467begin # begin
2468 if.true # if
2469 nop
2470 else # else
2471 nop
2472 end
2473end
2474";
2475
2476 let parse = parse_text(source);
2477 assert!(!parse.has_errors(), "{:?}", parse.diagnostics());
2478
2479 let root = parse.syntax();
2480 let procedure = root
2481 .descendants()
2482 .find(|node| node.kind() == SyntaxKind::Procedure)
2483 .expect("procedure");
2484 assert!(
2485 procedure
2486 .children_with_tokens()
2487 .filter_map(rowan::NodeOrToken::into_token)
2488 .any(|token| token.kind() == SyntaxKind::Comment && token.text().contains("proc"))
2489 );
2490
2491 let if_node = root
2492 .descendants()
2493 .find(|node| node.kind() == SyntaxKind::IfOp)
2494 .expect("if node");
2495 let if_comments = if_node
2496 .children_with_tokens()
2497 .filter_map(rowan::NodeOrToken::into_token)
2498 .filter(|token| token.kind() == SyntaxKind::Comment)
2499 .map(|token| token.text().to_string())
2500 .collect::<Vec<_>>();
2501 assert!(
2502 if_comments.iter().any(|comment| comment.contains("if")),
2503 "expected header comment on if node, got {if_comments:?}"
2504 );
2505 assert!(
2506 if_comments.iter().any(|comment| comment.contains("else")),
2507 "expected else comment on if node, got {if_comments:?}"
2508 );
2509 }
2510
2511 #[test]
2512 fn rejects_block_local_doc_comments_before_block_keywords() {
2513 let source = "\
2514proc foo
2515 #! mistaken doc comment before if
2516 if.true
2517 nop
2518 #! mistaken doc comment before else
2519 else
2520 #! mistaken doc comment before while
2521 while.true
2522 add
2523 end
2524 end
2525end
2526";
2527
2528 let parse = parse_text(source);
2529 assert!(parse.has_errors());
2530 assert!(
2531 parse
2532 .diagnostics()
2533 .iter()
2534 .flat_map(|diag| diag.labels.as_deref().unwrap_or(&[]).iter())
2535 .filter_map(|label| label.label())
2536 .any(|label| label.contains("doc comments are only allowed")),
2537 "expected block-local doc comments before block keywords to be rejected, got {:?}",
2538 parse.diagnostics()
2539 );
2540
2541 let root = parse.syntax();
2542 assert!(root.descendants().any(|node| node.kind() == SyntaxKind::Procedure));
2543 assert!(root.descendants().any(|node| node.kind() == SyntaxKind::IfOp));
2544 assert!(
2545 root.descendants().filter(|node| node.kind() == SyntaxKind::Instruction).count() >= 2
2546 );
2547 }
2548
2549 #[test]
2550 fn block_local_doc_comments_before_instructions_still_error() {
2551 let source = "\
2552proc foo
2553 #! malformed doc comment before instruction
2554 loc_load.0
2555end
2556";
2557
2558 let parse = parse_text(source);
2559 assert!(parse.has_errors());
2560 assert!(
2561 parse
2562 .diagnostics()
2563 .iter()
2564 .flat_map(|diag| diag.labels.as_deref().unwrap_or(&[]).iter())
2565 .filter_map(|label| label.label())
2566 .any(|label| label.contains("doc comments are only allowed")),
2567 "expected block-local doc comments before instructions to remain invalid, got {:?}",
2568 parse.diagnostics()
2569 );
2570 }
2571
2572 #[test]
2573 fn block_local_doc_comments_still_recover_before_true_top_level_items() {
2574 let source = "\
2575proc foo
2576 #! actual misplaced doc comment
2577 pub const X = 1
2578";
2579
2580 let parse = parse_text(source);
2581 assert!(parse.has_errors());
2582 assert!(
2583 parse
2584 .diagnostics()
2585 .iter()
2586 .flat_map(|diag| diag.labels.as_deref().unwrap_or(&[]).iter())
2587 .filter_map(|label| label.label())
2588 .any(|label| label.contains("before top-level item")),
2589 "expected block recovery before a top-level item, got {:?}",
2590 parse.diagnostics()
2591 );
2592
2593 let root = parse.syntax();
2594 assert!(root.descendants().any(|node| node.kind() == SyntaxKind::Doc));
2595
2596 let source_file = AstSourceFile::cast(root).expect("source file");
2597 let items = source_file.items().collect::<Vec<_>>();
2598 assert_eq!(
2599 items.len(),
2600 3,
2601 "expected recovery to preserve the recovered doc comment and top-level constant"
2602 );
2603 assert!(matches!(items[0], Item::Procedure(_)));
2604 assert!(matches!(items[1], Item::Doc(_)));
2605 assert!(matches!(items[2], Item::Constant(_)));
2606 }
2607
2608 #[test]
2609 fn rejects_stray_closing_delimiters() {
2610 let cases = [
2611 ("const X = )\n", ")"),
2612 ("begin foo ] end\n", "]"),
2613 ("begin\n foo}\nend\n", "}"),
2614 ];
2615
2616 for (source, delimiter) in cases {
2617 let parse = parse_text(source);
2618 assert!(parse.has_errors(), "expected {source:?} to be rejected");
2619 let expected = format!("unexpected closing delimiter `{delimiter}`");
2620 assert!(
2621 parse
2622 .diagnostics()
2623 .iter()
2624 .flat_map(|diag| diag.labels.as_deref().unwrap_or(&[]).iter())
2625 .filter_map(|label| label.label())
2626 .any(|label| label.contains(&expected)),
2627 "expected {source:?} to report {expected:?}, got {:?}",
2628 parse.diagnostics()
2629 );
2630 }
2631 }
2632
2633 #[test]
2634 fn recovers_from_missing_end_tokens() {
2635 let parse = parse_text("begin\n if.true\n add\n");
2636 assert!(parse.has_errors());
2637 let end_labels = parse
2638 .diagnostics()
2639 .iter()
2640 .flat_map(|diag| diag.labels.as_deref().unwrap_or(&[]).iter())
2641 .filter_map(|label| label.label())
2642 .filter(|label| label.contains("expected `end`"))
2643 .collect::<Vec<_>>();
2644 assert_eq!(
2645 end_labels.len(),
2646 1,
2647 "expected exactly one missing-`end` diagnostic, got {:?}",
2648 parse.diagnostics()
2649 );
2650 assert!(
2651 end_labels[0].contains("`if`"),
2652 "expected the innermost unterminated block to own the diagnostic, got {end_labels:?}"
2653 );
2654
2655 let root = parse.syntax();
2656 assert!(root.descendants().any(|node| node.kind() == SyntaxKind::BeginBlock));
2657 assert!(root.descendants().any(|node| node.kind() == SyntaxKind::IfOp));
2658 }
2659
2660 #[test]
2661 fn recovers_before_top_level_items_inside_blocks() {
2662 let source = "\
2663proc foo
2664 if.true
2665 add
2666pub const X = 1
2667";
2668 let parse = parse_text(source);
2669 assert!(parse.has_errors());
2670 assert!(
2671 parse
2672 .diagnostics()
2673 .iter()
2674 .flat_map(|diag| diag.labels.as_deref().unwrap_or(&[]).iter())
2675 .filter_map(|label| label.label())
2676 .any(|label| label.contains("before top-level item")),
2677 "expected block recovery before a top-level item, got {:?}",
2678 parse.diagnostics()
2679 );
2680
2681 let source_file = AstSourceFile::cast(parse.syntax()).expect("source file");
2682 let items = source_file.items().collect::<Vec<_>>();
2683 assert_eq!(items.len(), 2, "expected recovery to preserve the top-level constant");
2684 assert!(matches!(items[0], Item::Procedure(_)));
2685 assert!(matches!(items[1], Item::Constant(_)));
2686 assert!(parse.syntax().descendants().any(|node| node.kind() == SyntaxKind::Error));
2687 }
2688
2689 #[test]
2690 fn else_synchronizes_unterminated_nested_blocks() {
2691 let source = "\
2692proc foo
2693 if.true
2694 while.true
2695 nop
2696 else
2697 nop
2698 end
2699end
2700";
2701 let parse = parse_text(source);
2702 assert!(parse.has_errors());
2703 assert!(
2704 parse
2705 .diagnostics()
2706 .iter()
2707 .flat_map(|diag| diag.labels.as_deref().unwrap_or(&[]).iter())
2708 .filter_map(|label| label.label())
2709 .any(|label| label.contains("close `while` before `else`")),
2710 "expected the nested `while` to recover before `else`, got {:?}",
2711 parse.diagnostics()
2712 );
2713
2714 let if_node = parse
2715 .syntax()
2716 .descendants()
2717 .find(|node| node.kind() == SyntaxKind::IfOp)
2718 .expect("if node");
2719 assert_eq!(
2720 if_node.children().filter(|child| child.kind() == SyntaxKind::Block).count(),
2721 2,
2722 "expected `else` to remain attached to the enclosing `if` after recovery"
2723 );
2724 }
2725
2726 #[test]
2727 fn surfaces_invalid_tokens_as_diagnostics() {
2728 let parse = parse_text("proc foo\n §\nend\n");
2729 assert!(parse.has_errors());
2730 assert!(parse.diagnostics().iter().any(|diag| diag.labels.as_ref().is_some_and(
2731 |labels| {
2732 labels
2733 .iter()
2734 .any(|l| l.label().is_some_and(|label| label.contains("unrecognized token")))
2735 }
2736 )));
2737 }
2738
2739 #[test]
2740 fn rejects_unknown_special_identifiers() {
2741 for source in [
2742 "use $foo::bar\n",
2743 "begin\n exec.$foo::bar\nend\n",
2744 "begin\n exec.$execFoo::bar\nend\n",
2745 "begin\n exec.$kernelFoo::bar\nend\n",
2746 ] {
2747 let parse = parse_text(source);
2748 assert!(parse.has_errors(), "expected {source:?} to reject unknown special ident");
2749 assert!(parse.diagnostics().iter().any(|diag| diag.labels.as_ref().is_some_and(
2750 |labels| {
2751 labels.iter().any(|l| {
2752 l.label().is_some_and(|label| label.contains("unrecognized token"))
2753 })
2754 }
2755 )));
2756 }
2757 }
2758
2759 #[test]
2760 fn parse_source_file_tracks_source_aware_spans() {
2761 let source = Arc::new(ManagedSourceFile::new(
2762 SourceId::new(11),
2763 SourceLanguage::Masm,
2764 Uri::new("memory:///parser-span-test.masm"),
2765 "begin\n nop\nend\n".to_string().into_boxed_str(),
2766 ));
2767
2768 let parse = parse_source_file(source.clone());
2769 assert!(!parse.has_errors(), "{:?}", parse.diagnostics());
2770 assert_eq!(parse.source_file().id(), source.id());
2771
2772 let nop = parse
2773 .syntax()
2774 .descendants_with_tokens()
2775 .filter_map(rowan::NodeOrToken::into_token)
2776 .find(|token| token.text() == "nop")
2777 .expect("nop token");
2778 let offset = source.as_str().find("nop").expect("nop offset");
2779 let expected = SourceSpan::try_from_range(source.id(), offset..offset + 3).unwrap();
2780 assert_eq!(parse.span_for_token(&nop), expected);
2781 }
2782
2783 #[test]
2784 fn diagnostics_keep_source_ids_from_managed_source_files() {
2785 let source = Arc::new(ManagedSourceFile::new(
2786 SourceId::new(12),
2787 SourceLanguage::Masm,
2788 Uri::new("memory:///parser-diagnostic-span-test.masm"),
2789 "proc foo\n §\nend\n".to_string().into_boxed_str(),
2790 ));
2791
2792 let parse = parse_source_file(source.clone());
2793 assert!(parse.has_errors());
2794
2795 let diagnostic = parse
2796 .diagnostics()
2797 .iter()
2798 .find(|diag| {
2799 diag.labels.as_ref().is_some_and(|labels| {
2800 labels.iter().any(|l| {
2801 l.label().is_some_and(|label| label.contains("unrecognized token"))
2802 })
2803 })
2804 })
2805 .expect("invalid-token diagnostic");
2806 let offset = source.as_str().find('§').expect("invalid token offset");
2807 let expected =
2808 SourceSpan::try_from_range(source.id(), offset..offset + '§'.len_utf8()).unwrap();
2809 let label_span = diagnostic.labels.as_deref().unwrap()[0].inner();
2810 let actual = SourceSpan::new(
2811 source.id(),
2812 (label_span.offset() as u32)..((label_span.offset() + label_span.len()) as u32),
2813 );
2814 assert_eq!(actual, expected);
2815 }
2816
2817 #[test]
2818 fn eof_diagnostics_anchor_to_the_last_character_offset() {
2819 let source = Arc::new(ManagedSourceFile::new(
2820 SourceId::new(13),
2821 SourceLanguage::Masm,
2822 Uri::new("memory:///parser-eof-span-test.masm"),
2823 "begin\n if.true\n add\n".to_string().into_boxed_str(),
2824 ));
2825
2826 let parse = parse_source_file(source.clone());
2827 assert!(parse.has_errors());
2828
2829 let diagnostic = parse
2830 .diagnostics()
2831 .iter()
2832 .find(|diag| {
2833 diag.labels
2834 .as_deref()
2835 .unwrap_or(&[])
2836 .iter()
2837 .filter_map(|label| label.label())
2838 .any(|label| label.contains("expected `end`"))
2839 })
2840 .expect("missing-end diagnostic");
2841
2842 let last_char_offset = source
2843 .as_str()
2844 .char_indices()
2845 .last()
2846 .map(|(offset, _)| offset)
2847 .expect("source should be non-empty");
2848 let label_span = diagnostic.labels.as_deref().unwrap()[0].inner();
2849 assert_eq!(label_span.offset(), last_char_offset);
2850 assert_eq!(label_span.len(), 0);
2851 }
2852
2853 #[test]
2854 fn cst_import_spans_do_not_consume_trailing_newlines() {
2855 let source = "\
2856use lib::a
2857use {foo} from lib::b
2858begin end
2859";
2860 let parse = parse_text(source);
2861 let source_file = AstSourceFile::cast(parse.syntax()).expect("source file");
2862 let items = source_file.items().collect::<Vec<_>>();
2863 let Item::Import(module_import) = &items[0] else {
2864 panic!("expected first item to be an import");
2865 };
2866 let module_path = module_import.module_path().expect("module path");
2867 let start = source.find("lib::a").expect("path start") as u32;
2868 let end = start + "lib::a".len() as u32;
2869 let expected = SourceSpan::new(parse.source().id(), start..end);
2870 assert_eq!(parse.span_for_node(module_path.syntax()), expected);
2871
2872 let Item::Import(item_import) = &items[1] else {
2873 panic!("expected second item to be an import");
2874 };
2875 let item_path = item_import.module_path().expect("item import module path");
2876 let start = source.find("lib::b").expect("path start") as u32;
2877 let end = start + "lib::b".len() as u32;
2878 let expected = SourceSpan::new(parse.source().id(), start..end);
2879 assert_eq!(parse.span_for_node(item_path.syntax()), expected);
2880
2881 let spec = item_import.item_specs().next().expect("item specifier");
2882 let name = spec.name_token().expect("item name");
2883 let start = source.find("foo").expect("item start") as u32;
2884 let end = start + "foo".len() as u32;
2885 let expected = SourceSpan::new(parse.source().id(), start..end);
2886 assert_eq!(parse.span_for_token(&name), expected);
2887 }
2888}