1use crate::cst::{
11 Annotation, AnnotationArg, CallArg, Decl, DictEntry, Event, Expr, IfBranch, Program, Rule,
12 RuleEntry, Stmt, SwitchArm, TopLevel,
13};
14use crate::diag::{OpyError, Position, Span};
15use crate::lexer::{Token, TokenKind};
16
17#[derive(Debug, Default)]
19pub struct ParseOutput {
20 pub program: Option<Program>,
22 pub errors: Vec<OpyError>,
24}
25
26pub fn parse(tokens: &[Token]) -> ParseOutput {
28 parse_with_options(tokens, false)
29}
30
31pub fn parse_with_options(tokens: &[Token], allow_macro_redeclaration: bool) -> ParseOutput {
33 let mut parser = Parser {
34 tokens,
35 pos: 0,
36 errors: Vec::new(),
37 allow_macro_redeclaration,
38 };
39 let program = parser.parse_program();
40 if parser.errors.is_empty() {
41 ParseOutput {
42 program: Some(program),
43 errors: Vec::new(),
44 }
45 } else {
46 ParseOutput {
47 program: None,
48 errors: parser.errors,
49 }
50 }
51}
52
53struct Parser<'a> {
54 tokens: &'a [Token],
55 pos: usize,
56 errors: Vec<OpyError>,
57 allow_macro_redeclaration: bool,
58}
59
60fn is_identifier(text: &str) -> bool {
61 !text.is_empty()
62 && text.chars().enumerate().all(|(index, ch)| {
63 if index == 0 {
64 ch.is_ascii_alphabetic() || ch == '_'
65 } else {
66 ch.is_ascii_alphanumeric() || ch == '_'
67 }
68 })
69}
70
71fn unquote_annotation_arg(text: &str) -> String {
72 text.strip_prefix('"')
73 .and_then(|value| value.strip_suffix('"'))
74 .unwrap_or(text)
75 .to_string()
76}
77
78impl Parser<'_> {
79 fn peek(&self) -> &Token {
80 &self.tokens[self.pos.min(self.tokens.len() - 1)]
81 }
82
83 fn peek_kind(&self) -> TokenKind {
84 self.peek().kind
85 }
86
87 fn peek_at(&self, offset: usize) -> &Token {
88 &self.tokens[(self.pos + offset).min(self.tokens.len() - 1)]
89 }
90
91 fn advance(&mut self) -> Token {
92 let token = self.tokens[self.pos.min(self.tokens.len() - 1)].clone();
93 if self.pos < self.tokens.len() - 1 {
94 self.pos += 1;
95 }
96 token
97 }
98
99 fn skip_newlines(&mut self) {
100 while self.peek_kind() == TokenKind::Newline {
101 self.advance();
102 }
103 }
104
105 fn skip_expression_newlines(&mut self) {
106 if self.inside_delimiter_group() {
107 self.skip_newlines();
108 }
109 }
110
111 fn is_ident(&self, text: &str) -> bool {
112 self.peek_kind() == TokenKind::Ident && self.peek().text == text
113 }
114
115 fn expect_ident(&mut self, what: &str) -> Result<String, ()> {
116 if self.peek_kind() == TokenKind::Ident {
117 Ok(self.advance().text)
118 } else {
119 self.error_at_current(format!("expected {what}"));
120 Err(())
121 }
122 }
123
124 fn expect(&mut self, kind: TokenKind, what: &str) -> Result<Token, ()> {
125 if self.peek_kind() == kind {
126 Ok(self.advance())
127 } else {
128 self.error_at_current(format!("expected {what}"));
129 Err(())
130 }
131 }
132
133 fn error_at_current(&mut self, message: String) {
134 let span = self.peek().span;
135 self.errors.push(OpyError::at("parse-error", message, span));
136 }
137
138 fn parse_program(&mut self) -> Program {
141 let mut declarations = Vec::new();
142 let mut rules = Vec::new();
143 let mut top_level = Vec::new();
144 loop {
145 self.skip_newlines();
146 if self.peek_kind() == TokenKind::Eof {
147 break;
148 }
149 let rule_prefix = if self.peek_kind() == TokenKind::RulePrefixMarker {
150 Some(self.advance().text)
151 } else {
152 None
153 };
154 let declaration_count = declarations.len();
155 let rule_count = rules.len();
156 let ok = self.parse_top_level(&mut declarations, &mut rules, rule_prefix);
157 if ok {
158 if declarations.len() > declaration_count {
159 top_level.push(TopLevel::Declaration(
160 declarations
161 .last()
162 .expect("declaration was appended")
163 .clone(),
164 ));
165 } else if rules.len() > rule_count {
166 top_level.push(TopLevel::Rule(
167 rules.last().expect("rule was appended").clone(),
168 ));
169 }
170 }
171 if !ok {
172 self.recover_line();
173 }
174 }
175 Program {
176 declarations,
177 rules,
178 top_level,
179 settings: None,
180 }
181 }
182
183 fn parse_top_level(
184 &mut self,
185 declarations: &mut Vec<Decl>,
186 rules: &mut Vec<RuleEntry>,
187 rule_prefix: Option<String>,
188 ) -> bool {
189 let token = self.peek();
190 if token.kind == TokenKind::Ident {
191 match token.text.as_str() {
192 "rule" => return self.parse_rule(rules, rule_prefix),
193 "def" => return self.parse_def(rules, rule_prefix),
194 "globalvar" => return self.parse_variable(declarations, true),
195 "playervar" => return self.parse_variable(declarations, false),
196 "subroutine" => return self.parse_subroutine(declarations),
197 "enum" => return self.parse_enum(declarations),
198 "macro" => return self.parse_macro(declarations),
199 _ => {}
200 }
201 }
202 self.error_at_current(format!(
203 "expected a top-level declaration (rule/def/globalvar/playervar/subroutine/enum/macro) but found '{}'",
204 token.text
205 ));
206 false
207 }
208
209 fn recover_line(&mut self) {
211 while self.peek_kind() != TokenKind::Newline && self.peek_kind() != TokenKind::Eof {
212 self.advance();
213 }
214 }
215
216 fn expect_statement_end(&mut self, what: &str) -> Result<(), ()> {
217 if matches!(self.peek_kind(), TokenKind::Newline | TokenKind::Eof) {
218 Ok(())
219 } else {
220 self.error_at_current(format!("expected the end of {what}"));
221 Err(())
222 }
223 }
224
225 fn parse_variable(&mut self, declarations: &mut Vec<Decl>, global: bool) -> bool {
228 let start = self.advance(); let name_token = self.peek().clone();
232 let name = match self.expect_ident("a variable name after the keyword") {
233 Ok(name) => name,
234 Err(()) => return false,
235 };
236 let name_span = if name_token.kind == TokenKind::Ident {
237 name_token.span
238 } else {
239 start.span
240 };
241 let mut index = None;
242 let mut initializer = None;
243 if self.peek_kind() == TokenKind::Assign {
244 self.advance();
245 match self.parse_expr() {
246 Ok(expr) => initializer = Some(expr),
247 Err(()) => return false,
248 }
249 } else if self.peek_kind() == TokenKind::Number {
250 let token = self.advance();
252 index = token.text.parse::<u32>().ok();
253 if index.is_none() {
254 self.errors.push(OpyError::at(
255 "parse-error",
256 format!(
257 "invalid variable index '{}' (expected an integer)",
258 token.text
259 ),
260 token.span,
261 ));
262 return false;
263 }
264 } else if self.peek_kind() != TokenKind::Newline && self.peek_kind() != TokenKind::Eof {
265 self.error_at_current(
266 "expected '=', an integer index, or end of line after the variable name"
267 .to_string(),
268 );
269 return false;
270 }
271 let end = self.peek().span.start;
272 let span = Span::new(start.span.file, start.span.start, end);
273 let decl = if global {
274 Decl::GlobalVariable {
275 name,
276 index,
277 span,
278 name_span,
279 initializer,
280 }
281 } else {
282 Decl::PlayerVariable {
283 name,
284 index,
285 span,
286 name_span,
287 initializer,
288 }
289 };
290 declarations.push(decl);
291 true
292 }
293
294 fn parse_subroutine(&mut self, declarations: &mut Vec<Decl>) -> bool {
295 let start = self.advance();
296 let name_token = self.peek().clone();
299 let name = match self.expect_ident("a subroutine name") {
300 Ok(name) => name,
301 Err(()) => return false,
302 };
303 let name_span = if name_token.kind == TokenKind::Ident {
304 name_token.span
305 } else {
306 start.span
307 };
308 let end = self.peek().span.start;
309 declarations.push(Decl::Subroutine {
310 name,
311 span: Span::new(start.span.file, start.span.start, end),
312 name_span,
313 });
314 true
315 }
316
317 fn parse_enum(&mut self, declarations: &mut Vec<Decl>) -> bool {
318 let start = self.advance();
319 let name = match self.expect_ident("an enum name") {
320 Ok(name) => name,
321 Err(()) => return false,
322 };
323 if self
324 .expect(TokenKind::Colon, "':' after the enum name")
325 .is_err()
326 {
327 return false;
328 }
329 let line_indent = start.span.start.col;
330 let body_indent = match self.block_indent(line_indent) {
331 Some(indent) => indent,
332 None => return false,
333 };
334 let mut members = Vec::new();
335 loop {
336 self.skip_newlines();
337 if self.peek_kind() == TokenKind::Eof || self.peek().span.start.col < body_indent {
338 break;
339 }
340 if self.peek_kind() == TokenKind::Ident {
341 let member = self.advance();
342 let member_span = member.span;
343 if !self.allow_macro_redeclaration
344 && members.iter().any(|(name, _)| name == &member.text)
345 {
346 self.errors.push(OpyError::at(
347 "macro-redeclaration",
348 format!("enum member '{name}.{}' is already defined", member.text),
349 member_span,
350 ));
351 }
352 members.push((member.text, member_span));
353 } else {
354 self.error_at_current("expected an enum member name".to_string());
355 self.recover_line();
356 continue;
357 }
358 if self.peek_kind() == TokenKind::Comma {
359 self.advance();
360 } else {
361 if self.peek_kind() != TokenKind::Newline && self.peek_kind() != TokenKind::Eof {
363 self.error_at_current("expected ',' after the enum member".to_string());
364 self.recover_line();
365 continue;
366 }
367 }
368 }
369 declarations.push(Decl::Enum {
370 name,
371 members,
372 span: start.span,
373 });
374 true
375 }
376
377 fn parse_macro(&mut self, declarations: &mut Vec<Decl>) -> bool {
378 let start = self.advance();
379 let name_token = self.peek().clone();
380 let name = match self.expect_ident("a macro name") {
381 Ok(name) => name,
382 Err(()) => return false,
383 };
384 let args = match self.parse_param_list() {
385 Some(args) => args,
386 None => return false,
387 };
388 if self
389 .expect(TokenKind::Colon, "':' after the macro signature")
390 .is_err()
391 {
392 return false;
393 }
394 let line_indent = start.span.start.col;
395 let body_indent = match self.block_indent(line_indent) {
396 Some(indent) => indent,
397 None => return false,
398 };
399 let body = self.parse_block(body_indent);
400 if !self.allow_macro_redeclaration
401 && declarations.iter().any(|declaration| {
402 matches!(declaration, Decl::Macro { name: existing, .. } if existing == &name)
403 })
404 {
405 self.errors.push(OpyError::at(
406 "macro-redeclaration",
407 format!("macro '{name}' is already defined"),
408 name_token.span,
409 ));
410 }
411 declarations.push(Decl::Macro {
412 name,
413 args,
414 body,
415 span: start.span,
416 });
417 true
418 }
419
420 fn parse_param_list(&mut self) -> Option<Vec<String>> {
421 if self.expect(TokenKind::LParen, "'('").is_err() {
422 return None;
423 }
424 let mut params = Vec::new();
425 self.skip_newlines();
426 if self.peek_kind() == TokenKind::RParen {
427 self.advance();
428 return Some(params);
429 }
430 loop {
431 match self.expect_ident("a parameter name") {
432 Ok(name) => params.push(name),
433 Err(()) => return None,
434 }
435 self.skip_newlines();
436 if self.peek_kind() == TokenKind::Comma {
437 self.advance();
438 self.skip_newlines();
439 } else {
440 break;
441 }
442 }
443 if self.expect(TokenKind::RParen, "')'").is_err() {
444 return None;
445 }
446 Some(params)
447 }
448
449 fn parse_rule(&mut self, rules: &mut Vec<RuleEntry>, rule_prefix: Option<String>) -> bool {
452 let start = self.advance();
453 let name = match self.peek_kind() {
454 TokenKind::String => self.advance().text,
455 _ => {
456 self.error_at_current("expected a rule name string after `rule`".to_string());
457 return false;
458 }
459 };
460 let name_token_span = self.tokens[self.pos.saturating_sub(1)].span;
461 let name_span = Span::new(
464 name_token_span.file,
465 Position::new(name_token_span.start.line, name_token_span.start.col + 1),
466 Position::new(
467 name_token_span.end.line,
468 name_token_span
469 .end
470 .col
471 .saturating_sub(1)
472 .max(name_token_span.start.col + 1),
473 ),
474 );
475 if self
476 .expect(TokenKind::Colon, "':' after the rule name")
477 .is_err()
478 {
479 return false;
480 }
481 let line_indent = start.span.start.col;
482 let body_indent = match self.block_indent(line_indent) {
483 Some(indent) => indent,
484 None => return false,
485 };
486 let mut event = None;
487 let mut conditions = Vec::new();
488 let mut annotations = Vec::new();
489 let mut disabled = false;
490 let mut delimiter = false;
491 let mut new_page = None;
492 let mut actions = Vec::new();
493 loop {
494 self.skip_newlines();
495 if self.peek_kind() == TokenKind::Eof || self.peek().span.start.col < body_indent {
496 break;
497 }
498 if self.peek_kind() == TokenKind::At {
499 if !self.parse_directive(
500 &mut event,
501 &mut conditions,
502 &mut annotations,
503 &mut disabled,
504 &mut delimiter,
505 &mut new_page,
506 false,
507 ) {
508 self.recover_line();
509 }
510 continue;
511 }
512 match self.parse_statement() {
513 Ok(stmt) => actions.push(stmt),
514 Err(()) => self.recover_line(),
515 }
516 }
517 rules.push(RuleEntry::Rule(Rule {
518 name,
519 span: Span::new(start.span.file, start.span.start, name_token_span.end),
520 name_span,
521 disabled,
522 delimiter,
523 new_page,
524 annotations,
525 rule_prefix,
526 event: event.unwrap_or_else(|| Event {
527 name: "global".to_string(),
528 args: Vec::new(),
529 span: start.span,
530 }),
531 conditions,
532 actions,
533 }));
534 true
535 }
536
537 #[allow(clippy::too_many_arguments)]
538 fn parse_directive(
539 &mut self,
540 event: &mut Option<Event>,
541 conditions: &mut Vec<Expr>,
542 annotations: &mut Vec<Annotation>,
543 disabled: &mut bool,
544 delimiter: &mut bool,
545 new_page: &mut Option<String>,
546 subroutine: bool,
547 ) -> bool {
548 let at = self.advance();
549 let name = match self.expect_ident("a directive name after '@'") {
550 Ok(name) => name,
551 Err(()) => return false,
552 };
553 if matches!(
554 name.as_str(),
555 "Event" | "Team" | "Slot" | "Hero" | "Name" | "Disabled" | "Delimiter" | "NewPage"
556 ) && annotations.iter().any(|annotation| annotation.name == name)
557 {
558 self.error_at_current(format!("annotation '@{name}' was already declared"));
559 return false;
560 }
561 match name.as_str() {
562 "Event" => {
563 let event_name = match self.expect_ident("an event name after @Event") {
564 Ok(name) => name,
565 Err(()) => return false,
566 };
567 let event_annotation_arg = AnnotationArg {
568 text: event_name.clone(),
569 span: self.tokens[self.pos.saturating_sub(1)].span,
570 };
571 let mut args = Vec::new();
572 if self.peek_kind() == TokenKind::LParen
573 && self.parse_event_args(&mut args).is_err()
574 {
575 return false;
576 }
577 let end = self.peek().span.start;
578 *event = Some(Event {
579 name: event_name,
580 args,
581 span: Span::new(at.span.file, at.span.start, end),
582 });
583 annotations.push(Annotation {
584 name,
585 args: vec![event_annotation_arg],
586 span: Span::new(at.span.file, at.span.start, end),
587 });
588 true
589 }
590 "Condition" => {
591 let start = self.pos;
592 match self.parse_expr() {
593 Ok(expr) => {
594 let end = self.peek().span.start;
595 conditions.push(expr);
596 annotations.push(Annotation {
597 name,
598 args: vec![self.raw_annotation_arg(start, self.pos)],
599 span: Span::new(at.span.file, at.span.start, end),
600 });
601 true
602 }
603 Err(()) => false,
604 }
605 }
606 "Team" | "Slot" | "Hero" => {
607 let args = self.consume_annotation_args();
608 if args.len() != 1 {
609 self.error_at_current(format!("@{name} expects exactly one argument"));
610 return false;
611 }
612 if subroutine {
613 self.error_at_current(format!("@{name} is not valid on a subroutine"));
614 return false;
615 }
616 if (name == "Slot"
617 && annotations
618 .iter()
619 .any(|annotation| annotation.name == "Hero"))
620 || (name == "Hero"
621 && annotations
622 .iter()
623 .any(|annotation| annotation.name == "Slot"))
624 {
625 self.error_at_current("@Slot and @Hero cannot be used together".to_string());
626 return false;
627 }
628 let end = self.peek().span.start;
629 annotations.push(Annotation {
630 name,
631 args,
632 span: Span::new(at.span.file, at.span.start, end),
633 });
634 true
635 }
636 "Name" => {
637 let args = self.consume_annotation_args();
638 if args.len() != 1 || !self.annotation_arg_is_string(&args[0]) {
639 self.error_at_current(
640 "@Name expects exactly one plain string literal".to_string(),
641 );
642 return false;
643 }
644 if !subroutine {
645 self.error_at_current(
646 "@Name is only supported on subroutine definitions".to_string(),
647 );
648 return false;
649 }
650 let end = self.peek().span.start;
651 annotations.push(Annotation {
652 name,
653 args,
654 span: Span::new(at.span.file, at.span.start, end),
655 });
656 true
657 }
658 "SuppressWarnings" => {
659 let args = self.consume_annotation_args();
660 if args.is_empty() || args.iter().any(|arg| !is_identifier(&arg.text)) {
661 self.error_at_current(
662 "@SuppressWarnings expects one or more warning identifiers".to_string(),
663 );
664 return false;
665 }
666 let end = self.peek().span.start;
667 annotations.push(Annotation {
668 name,
669 args,
670 span: Span::new(at.span.file, at.span.start, end),
671 });
672 true
673 }
674 "Disabled" => {
675 if !self.expect_annotation_end("@Disabled") {
676 return false;
677 }
678 *disabled = true;
679 annotations.push(Annotation {
680 name,
681 args: Vec::new(),
682 span: at.span,
683 });
684 true
685 }
686 "Delimiter" => {
687 if !self.expect_annotation_end("@Delimiter") {
688 return false;
689 }
690 *delimiter = true;
691 annotations.push(Annotation {
692 name,
693 args: Vec::new(),
694 span: at.span,
695 });
696 true
697 }
698 "NewPage" => {
699 let args = self.consume_annotation_args();
700 if args.len() > 1
701 || args
702 .first()
703 .is_some_and(|arg| !self.annotation_arg_is_string(arg))
704 {
705 self.error_at_current(
706 "@NewPage expects at most one plain string literal".to_string(),
707 );
708 return false;
709 }
710 let end = self.peek().span.start;
711 *new_page = args.first().map(|arg| unquote_annotation_arg(&arg.text));
712 annotations.push(Annotation {
713 name,
714 args,
715 span: Span::new(at.span.file, at.span.start, end),
716 });
717 true
718 }
719 other => {
720 self.error_at_current(format!("unsupported directive '@{other}'"));
721 false
722 }
723 }
724 }
725
726 fn consume_annotation_args(&mut self) -> Vec<AnnotationArg> {
727 let start = self.pos;
728 while self.peek_kind() != TokenKind::Newline && self.peek_kind() != TokenKind::Eof {
729 self.advance();
730 }
731 if self.pos == start {
732 return Vec::new();
733 }
734 let tokens = &self.tokens[start..self.pos];
735 if tokens.len() == 3
736 && tokens[1].kind == TokenKind::Dot
737 && tokens[0].kind == TokenKind::Ident
738 {
739 return vec![AnnotationArg {
740 text: tokens.iter().map(|token| token.text.as_str()).collect(),
741 span: Span::new(
742 tokens[0].span.file,
743 tokens[0].span.start,
744 tokens[2].span.end,
745 ),
746 }];
747 }
748 tokens
749 .iter()
750 .map(|token| AnnotationArg {
751 text: if token.kind == TokenKind::String {
752 format!("\"{}\"", token.text)
753 } else {
754 token.text.clone()
755 },
756 span: token.span,
757 })
758 .collect()
759 }
760
761 fn raw_annotation_arg(&self, start: usize, end: usize) -> AnnotationArg {
762 let tokens = &self.tokens[start..end];
763 let first = tokens
764 .first()
765 .map(|token| token.span)
766 .unwrap_or(self.peek().span);
767 let last = tokens.last().map(|token| token.span).unwrap_or(first);
768 AnnotationArg {
769 text: tokens.iter().map(|token| token.text.as_str()).collect(),
770 span: Span::new(first.file, first.start, last.end),
771 }
772 }
773
774 fn annotation_arg_is_string(&self, arg: &AnnotationArg) -> bool {
775 arg.text.starts_with('"') && arg.text.ends_with('"')
776 }
777
778 fn expect_annotation_end(&mut self, name: &str) -> bool {
779 if self.peek_kind() == TokenKind::Newline || self.peek_kind() == TokenKind::Eof {
780 true
781 } else {
782 self.error_at_current(format!("{name} takes no arguments"));
783 false
784 }
785 }
786
787 fn parse_def(&mut self, rules: &mut Vec<RuleEntry>, rule_prefix: Option<String>) -> bool {
788 let start = self.advance();
789 let name_token = self.peek().clone();
793 let name = match self.expect_ident("a subroutine name after `def`") {
794 Ok(name) => name,
795 Err(()) => return false,
796 };
797 let name_span = if name_token.kind == TokenKind::Ident {
798 name_token.span
799 } else {
800 start.span
801 };
802 let params = match self.parse_param_list() {
803 Some(params) => params,
804 None => return false,
805 };
806 if !params.is_empty() {
807 self.error_at_current(
808 "subroutine parameters are outside the declared support matrix".to_string(),
809 );
810 return false;
811 }
812 if self
813 .expect(TokenKind::Colon, "':' after the subroutine signature")
814 .is_err()
815 {
816 return false;
817 }
818 let line_indent = start.span.start.col;
819 let body_indent = match self.block_indent(line_indent) {
820 Some(indent) => indent,
821 None => return false,
822 };
823 let mut annotations = Vec::new();
824 let mut event = None;
825 let mut conditions = Vec::new();
826 let mut disabled = false;
827 let mut delimiter = false;
828 let mut new_page = None;
829 loop {
830 self.skip_newlines();
831 if self.peek_kind() != TokenKind::At {
832 break;
833 }
834 if !self.parse_directive(
835 &mut event,
836 &mut conditions,
837 &mut annotations,
838 &mut disabled,
839 &mut delimiter,
840 &mut new_page,
841 true,
842 ) {
843 self.recover_line();
844 return false;
845 }
846 }
847 if event.is_some() || !conditions.is_empty() {
848 self.error_at_current("subroutines cannot have events or conditions".to_string());
849 return false;
850 }
851 let _ = (disabled, delimiter, new_page);
852 let presentation_name = annotations
853 .iter()
854 .find(|annotation| annotation.name == "Name")
855 .and_then(|annotation| annotation.args.first())
856 .map(|arg| unquote_annotation_arg(&arg.text));
857 let body = self.parse_block(body_indent);
858 let span = if name_token.kind == TokenKind::Ident {
859 Span::new(start.span.file, start.span.start, name_token.span.end)
860 } else {
861 start.span
862 };
863 rules.push(RuleEntry::SubroutineDef {
864 name,
865 presentation_name,
866 span,
867 name_span,
868 body,
869 annotations,
870 rule_prefix,
871 });
872 true
873 }
874
875 fn block_indent(&mut self, line_indent: u32) -> Option<u32> {
878 self.skip_newlines();
879 if self.peek_kind() == TokenKind::Eof {
880 self.error_at_current("expected an indented block".to_string());
881 return None;
882 }
883 let indent = self.peek().span.start.col;
884 if indent <= line_indent {
885 self.error_at_current("expected an indented block after ':'".to_string());
886 return None;
887 }
888 Some(indent)
889 }
890
891 fn parse_block(&mut self, block_indent: u32) -> Vec<Stmt> {
894 let mut stmts = Vec::new();
895 loop {
896 self.skip_newlines();
897 if self.peek_kind() == TokenKind::Eof {
898 break;
899 }
900 if self.peek().span.start.col < block_indent {
901 break;
902 }
903 if self.peek().span.start.col > block_indent {
904 self.error_at_current("unexpected indentation".to_string());
906 self.recover_line();
907 continue;
908 }
909 match self.parse_statement() {
910 Ok(stmt) => stmts.push(stmt),
911 Err(()) => self.recover_line(),
912 }
913 }
914 stmts
915 }
916
917 fn parse_statement(&mut self) -> Result<Stmt, ()> {
918 let token = self.peek();
919 if token.kind == TokenKind::Ident {
920 match token.text.as_str() {
921 "if" => return self.parse_if(),
922 "for" => return self.parse_for(),
923 "while" => return self.parse_while(),
924 "do" => return self.parse_do_while(),
925 "switch" => return self.parse_switch(),
926 "del" => return self.parse_delete(),
927 "continue" => {
928 let token = self.advance();
929 self.expect_statement_end("the continue statement")?;
930 return Ok(Stmt::Continue { span: token.span });
931 }
932 "goto" => return self.parse_goto(),
933 "break" => {
934 let token = self.advance();
935 return Ok(Stmt::Break { span: token.span });
936 }
937 "return" => {
938 let token = self.advance();
939 self.expect_statement_end("the return statement")?;
940 return Ok(Stmt::Return { span: token.span });
941 }
942 "pass" => {
943 let start = self.advance();
944 return Ok(Stmt::Pass { span: start.span });
945 }
946 _ => {}
947 }
948 if self.peek_at(1).kind == TokenKind::Colon {
949 return self.parse_label();
950 }
951 }
952 self.parse_expr_statement()
953 }
954
955 fn parse_delete(&mut self) -> Result<Stmt, ()> {
956 let start = self.advance();
957 let target = self.parse_postfix()?;
958 if !matches!(target, Expr::Index { .. }) {
959 self.errors.push(OpyError::at(
960 "parse-error",
961 "the del statement requires an array index target".to_string(),
962 target.span(),
963 ));
964 return Err(());
965 }
966 self.expect_statement_end("the del statement")?;
967 Ok(Stmt::Delete {
968 span: Span::new(start.span.file, start.span.start, target.span().end),
969 target,
970 })
971 }
972
973 fn parse_goto(&mut self) -> Result<Stmt, ()> {
974 let start = self.advance();
975 if self.is_ident("loc") {
976 self.advance();
977 self.expect(TokenKind::Plus, "'+' after 'goto loc'")?;
978 let offset = self.parse_expr()?;
979 self.expect_statement_end("the goto target")?;
980 return Ok(Stmt::Goto {
981 label: None,
982 span: Span::new(start.span.file, start.span.start, offset.span().end),
983 offset: Some(offset),
984 rule_start: false,
985 });
986 }
987
988 let label = self.expect_ident("a label or 'loc+...' after 'goto'")?;
989 self.expect_statement_end("the goto target")?;
990 let rule_start = label == "RULE_START";
991 Ok(Stmt::Goto {
992 label: (!rule_start).then_some(label),
993 offset: None,
994 rule_start,
995 span: Span::new(
996 start.span.file,
997 start.span.start,
998 self.tokens[self.pos - 1].span.end,
999 ),
1000 })
1001 }
1002
1003 fn parse_label(&mut self) -> Result<Stmt, ()> {
1004 let name = self.advance();
1005 let colon = self.expect(TokenKind::Colon, "':' after a label")?;
1006 self.expect_statement_end("the label")?;
1007 Ok(Stmt::Label {
1008 name: name.text,
1009 span: Span::new(name.span.file, name.span.start, colon.span.end),
1010 })
1011 }
1012
1013 fn parse_expr_statement(&mut self) -> Result<Stmt, ()> {
1014 let start = self.peek().span;
1015 let expr = self.parse_expr()?;
1016 match self.peek_kind() {
1017 TokenKind::Assign => {
1018 self.advance();
1019 let value = self.parse_expr()?;
1020 let end = self.peek().span.start;
1021 Ok(Stmt::Assign {
1022 target: expr,
1023 value,
1024 span: Span::new(start.file, start.start, end),
1025 })
1026 }
1027 TokenKind::PlusAssign
1028 | TokenKind::MinusAssign
1029 | TokenKind::StarAssign
1030 | TokenKind::SlashAssign
1031 | TokenKind::PercentAssign
1032 | TokenKind::DoubleStarAssign => {
1033 let op = match self.peek_kind() {
1034 TokenKind::PlusAssign => "+",
1035 TokenKind::MinusAssign => "-",
1036 TokenKind::StarAssign => "*",
1037 TokenKind::SlashAssign => "/",
1038 TokenKind::PercentAssign => "%",
1039 TokenKind::DoubleStarAssign => "**",
1040 _ => unreachable!(),
1041 }
1042 .to_string();
1043 self.advance();
1044 let rhs = self.parse_expr()?;
1045 let end = self.peek().span.start;
1046 let value = Expr::Binary {
1047 op,
1048 left: Box::new(expr.clone()),
1049 right: Box::new(rhs),
1050 span: Span::new(start.file, start.start, end),
1051 };
1052 Ok(Stmt::Assign {
1053 target: expr,
1054 value,
1055 span: Span::new(start.file, start.start, end),
1056 })
1057 }
1058 TokenKind::Ident
1059 if matches!(self.peek().text.as_str(), "min" | "max")
1060 && self.peek_at(1).kind == TokenKind::Assign =>
1061 {
1062 let op = self.advance().text;
1063 self.advance();
1064 let rhs = self.parse_expr()?;
1065 let end = self.peek().span.start;
1066 let value = Expr::Binary {
1067 op,
1068 left: Box::new(expr.clone()),
1069 right: Box::new(rhs),
1070 span: Span::new(start.file, start.start, end),
1071 };
1072 Ok(Stmt::Assign {
1073 target: expr,
1074 value,
1075 span: Span::new(start.file, start.start, end),
1076 })
1077 }
1078 TokenKind::Increment | TokenKind::Decrement => {
1079 let operator = self.advance();
1080 if !matches!(self.peek_kind(), TokenKind::Newline | TokenKind::Eof) {
1081 self.error_at_current(
1082 "postfix increment/decrement must be a standalone assignment".to_string(),
1083 );
1084 return Err(());
1085 }
1086 let operation = if operator.kind == TokenKind::Increment {
1087 "+"
1088 } else {
1089 "-"
1090 };
1091 let span = Span::new(start.file, start.start, operator.span.end);
1092 let value = Expr::Binary {
1093 op: operation.to_string(),
1094 left: Box::new(expr.clone()),
1095 right: Box::new(Expr::Number {
1096 value: 1.0,
1097 text: "1".to_string(),
1098 span: operator.span,
1099 }),
1100 span,
1101 };
1102 Ok(Stmt::Assign {
1103 target: expr,
1104 value,
1105 span,
1106 })
1107 }
1108 _ => {
1109 let end = self.peek().span.start;
1110 Ok(Stmt::Expr {
1111 expr,
1112 span: Span::new(start.file, start.start, end),
1113 })
1114 }
1115 }
1116 }
1117
1118 fn parse_if(&mut self) -> Result<Stmt, ()> {
1119 let start = self.advance();
1120 let line_indent = start.span.start.col;
1121 let condition = self.parse_expr()?;
1122 if self
1123 .expect(TokenKind::Colon, "':' after the if condition")
1124 .is_err()
1125 {
1126 return Err(());
1127 }
1128 let body_indent = self.block_indent(line_indent).ok_or(())?;
1129 let body = self.parse_block(body_indent);
1130 let mut branches = vec![IfBranch { condition, body }];
1131 let mut r#else = None;
1132 loop {
1133 let save = self.pos;
1134 self.skip_newlines();
1135 if self.peek_kind() == TokenKind::Eof || self.peek().span.start.col != line_indent {
1136 self.pos = save;
1137 break;
1138 }
1139 if self.is_ident("elif") {
1140 self.advance();
1141 let condition = match self.parse_expr() {
1142 Ok(expr) => expr,
1143 Err(()) => return Err(()),
1144 };
1145 if self
1146 .expect(TokenKind::Colon, "':' after the elif condition")
1147 .is_err()
1148 {
1149 return Err(());
1150 }
1151 let body_indent = self.block_indent(line_indent).ok_or(())?;
1152 let body = self.parse_block(body_indent);
1153 branches.push(IfBranch { condition, body });
1154 } else if self.is_ident("else") {
1155 self.advance();
1156 if self.expect(TokenKind::Colon, "':' after `else`").is_err() {
1157 return Err(());
1158 }
1159 let body_indent = self.block_indent(line_indent).ok_or(())?;
1160 let body = self.parse_block(body_indent);
1161 r#else = Some(body);
1162 break;
1163 } else {
1164 self.pos = save;
1165 break;
1166 }
1167 }
1168 Ok(Stmt::If {
1169 branches,
1170 r#else,
1171 span: start.span,
1172 })
1173 }
1174
1175 fn parse_for(&mut self) -> Result<Stmt, ()> {
1176 let start = self.advance();
1177 let variable = self.parse_postfix()?;
1178 if !self.is_ident("in") {
1179 self.error_at_current("expected `in` in the for statement".to_string());
1180 return Err(());
1181 }
1182 self.advance();
1183 let iterable = self.parse_expr()?;
1184 if self
1185 .expect(TokenKind::Colon, "':' after the for header")
1186 .is_err()
1187 {
1188 return Err(());
1189 }
1190 let line_indent = start.span.start.col;
1191 let body_indent = self.block_indent(line_indent).ok_or(())?;
1192 let body = self.parse_block(body_indent);
1193 Ok(Stmt::For {
1194 variable,
1195 iterable,
1196 body,
1197 span: start.span,
1198 })
1199 }
1200
1201 fn parse_while(&mut self) -> Result<Stmt, ()> {
1202 let start = self.advance();
1203 let condition = self.parse_expr()?;
1204 if self
1205 .expect(TokenKind::Colon, "':' after the while condition")
1206 .is_err()
1207 {
1208 return Err(());
1209 }
1210 let line_indent = start.span.start.col;
1211 let body_indent = self.block_indent(line_indent).ok_or(())?;
1212 let body = self.parse_block(body_indent);
1213 Ok(Stmt::While {
1214 condition,
1215 body,
1216 span: start.span,
1217 })
1218 }
1219
1220 fn parse_do_while(&mut self) -> Result<Stmt, ()> {
1221 let start = self.advance();
1222 if self.expect(TokenKind::Colon, "':' after `do`").is_err() {
1223 return Err(());
1224 }
1225 let body_indent = self.block_indent(start.span.start.col).ok_or(())?;
1226 let body = self.parse_block(body_indent);
1227 if !self.is_ident("while") {
1228 self.error_at_current("expected `while` after the do block".to_string());
1229 return Err(());
1230 }
1231 self.advance();
1232 let condition = self.parse_expr()?;
1233 if self.peek_kind() != TokenKind::Newline && self.peek_kind() != TokenKind::Eof {
1234 self.error_at_current("expected the end of the do-while condition".to_string());
1235 return Err(());
1236 }
1237 Ok(Stmt::DoWhile {
1238 condition,
1239 body,
1240 span: start.span,
1241 })
1242 }
1243
1244 fn parse_switch(&mut self) -> Result<Stmt, ()> {
1245 let start = self.advance();
1246 let value = self.parse_expr()?;
1247 if self
1248 .expect(TokenKind::Colon, "':' after the switch value")
1249 .is_err()
1250 {
1251 return Err(());
1252 }
1253 let body_indent = self.block_indent(start.span.start.col).ok_or(())?;
1254 let mut arms = Vec::new();
1255 loop {
1256 self.skip_newlines();
1257 if self.peek_kind() == TokenKind::Eof || self.peek().span.start.col < body_indent {
1258 break;
1259 }
1260 if self.peek().span.start.col != body_indent {
1261 self.error_at_current("unexpected indentation in switch".to_string());
1262 self.recover_line();
1263 continue;
1264 }
1265 if self.is_ident("case") {
1266 let case_start = self.advance();
1267 let case_value = self.parse_expr()?;
1268 if self
1269 .expect(TokenKind::Colon, "':' after the case value")
1270 .is_err()
1271 {
1272 return Err(());
1273 }
1274 let case_body_indent = self.block_indent(body_indent).ok_or(())?;
1275 let body = self.parse_block(case_body_indent);
1276 arms.push(SwitchArm::Case {
1277 value: case_value,
1278 body,
1279 span: case_start.span,
1280 });
1281 } else if self.is_ident("default") {
1282 let default_start = self.advance();
1283 if self
1284 .expect(TokenKind::Colon, "':' after `default`")
1285 .is_err()
1286 {
1287 return Err(());
1288 }
1289 let default_body_indent = self.block_indent(body_indent).ok_or(())?;
1290 arms.push(SwitchArm::Default {
1291 body: self.parse_block(default_body_indent),
1292 span: default_start.span,
1293 });
1294 if default_start.span.start.col != body_indent {
1295 self.error_at_current("invalid default indentation".to_string());
1296 return Err(());
1297 }
1298 } else {
1299 self.error_at_current("expected `case` or `default` in switch".to_string());
1300 self.recover_line();
1301 }
1302 }
1303 if arms.is_empty() {
1304 self.errors.push(OpyError::at(
1305 "parse-error",
1306 "switch must contain at least one case or default arm".to_string(),
1307 start.span,
1308 ));
1309 return Err(());
1310 }
1311 Ok(Stmt::Switch {
1312 value,
1313 arms,
1314 span: start.span,
1315 })
1316 }
1317
1318 fn parse_expr(&mut self) -> Result<Expr, ()> {
1321 self.skip_expression_newlines();
1322 let then_value = self.parse_or()?;
1323 self.skip_expression_newlines();
1324 if !self.is_ident("if") {
1325 return Ok(then_value);
1326 }
1327
1328 self.advance();
1329 self.skip_expression_newlines();
1330 let condition = self.parse_or()?;
1331 self.skip_expression_newlines();
1332 if !self.is_ident("else") {
1333 self.error_at_current("expected `else` in conditional expression".to_string());
1334 return Err(());
1335 }
1336 self.advance();
1337 let else_value = self.parse_expr()?;
1340 let span = Span::new(
1341 then_value.span().file,
1342 then_value.span().start,
1343 else_value.span().end,
1344 );
1345 Ok(Expr::Conditional {
1346 then_value: Box::new(then_value),
1347 condition: Box::new(condition),
1348 else_value: Box::new(else_value),
1349 span,
1350 })
1351 }
1352
1353 fn parse_or(&mut self) -> Result<Expr, ()> {
1354 self.skip_expression_newlines();
1355 let mut left = self.parse_and()?;
1356 loop {
1357 self.skip_expression_newlines();
1358 if !self.is_ident("or") {
1359 break;
1360 }
1361 self.advance();
1362 self.skip_expression_newlines();
1363 let right = self.parse_and()?;
1364 let span = Span::new(left.span().file, left.span().start, right.span().end);
1365 left = Expr::Binary {
1366 op: "or".to_string(),
1367 left: Box::new(left),
1368 right: Box::new(right),
1369 span,
1370 };
1371 }
1372 Ok(left)
1373 }
1374
1375 fn parse_and(&mut self) -> Result<Expr, ()> {
1376 self.skip_expression_newlines();
1377 let mut left = self.parse_not()?;
1378 loop {
1379 self.skip_expression_newlines();
1380 if !self.is_ident("and") {
1381 break;
1382 }
1383 self.advance();
1384 self.skip_expression_newlines();
1385 let right = self.parse_not()?;
1386 let span = Span::new(left.span().file, left.span().start, right.span().end);
1387 left = Expr::Binary {
1388 op: "and".to_string(),
1389 left: Box::new(left),
1390 right: Box::new(right),
1391 span,
1392 };
1393 }
1394 Ok(left)
1395 }
1396
1397 fn parse_not(&mut self) -> Result<Expr, ()> {
1398 self.skip_expression_newlines();
1399 if self.is_ident("not") {
1400 let start = self.advance();
1401 self.skip_expression_newlines();
1402 let operand = self.parse_not()?;
1403 let end = operand.span().end;
1404 return Ok(Expr::Unary {
1405 op: "not".to_string(),
1406 operand: Box::new(operand),
1407 span: Span::new(start.span.file, start.span.start, end),
1408 });
1409 }
1410 self.parse_comparison()
1411 }
1412
1413 fn parse_comparison(&mut self) -> Result<Expr, ()> {
1414 self.skip_expression_newlines();
1415 let mut left = self.parse_additive()?;
1416 loop {
1417 self.skip_expression_newlines();
1418 let op = match self.peek_kind() {
1419 TokenKind::Eq => "==",
1420 TokenKind::Ne => "!=",
1421 TokenKind::Lt => "<",
1422 TokenKind::Le => "<=",
1423 TokenKind::Gt => ">",
1424 TokenKind::Ge => ">=",
1425 _ if self.is_ident("in") => "in",
1426 _ if self.is_ident("not") && self.peek_at(1).text == "in" => "not in",
1427 _ => break,
1428 };
1429 self.advance();
1430 if op == "not in" {
1431 self.advance();
1432 }
1433 self.skip_expression_newlines();
1434 let right = self.parse_additive()?;
1435 let span = Span::new(left.span().file, left.span().start, right.span().end);
1436 left = Expr::Binary {
1437 op: op.to_string(),
1438 left: Box::new(left),
1439 right: Box::new(right),
1440 span,
1441 };
1442 }
1443 Ok(left)
1444 }
1445
1446 fn parse_additive(&mut self) -> Result<Expr, ()> {
1447 self.skip_expression_newlines();
1448 let mut left = self.parse_multiplicative()?;
1449 loop {
1450 self.skip_expression_newlines();
1451 if self.peek_kind() == TokenKind::Decrement
1452 && !matches!(self.peek_at(1).kind, TokenKind::Newline | TokenKind::Eof)
1453 {
1454 let operator = self.advance();
1455 let operand = self.parse_unary()?;
1456 let unary = Expr::Unary {
1457 op: "-".to_string(),
1458 span: Span::new(operator.span.file, operator.span.start, operand.span().end),
1459 operand: Box::new(operand),
1460 };
1461 let right = self.parse_multiplicative_tail(unary)?;
1462 let span = Span::new(left.span().file, left.span().start, right.span().end);
1463 left = Expr::Binary {
1464 op: "-".to_string(),
1465 left: Box::new(left),
1466 right: Box::new(right),
1467 span,
1468 };
1469 continue;
1470 }
1471 let op = match self.peek_kind() {
1472 TokenKind::Plus => "+",
1473 TokenKind::Minus => "-",
1474 _ => break,
1475 };
1476 self.advance();
1477 self.skip_expression_newlines();
1478 let right = self.parse_multiplicative()?;
1479 let span = Span::new(left.span().file, left.span().start, right.span().end);
1480 left = Expr::Binary {
1481 op: op.to_string(),
1482 left: Box::new(left),
1483 right: Box::new(right),
1484 span,
1485 };
1486 }
1487 Ok(left)
1488 }
1489
1490 fn parse_multiplicative(&mut self) -> Result<Expr, ()> {
1491 self.skip_expression_newlines();
1492 let left = self.parse_unary()?;
1493 self.parse_multiplicative_tail(left)
1494 }
1495
1496 fn parse_multiplicative_tail(&mut self, mut left: Expr) -> Result<Expr, ()> {
1497 loop {
1498 self.skip_expression_newlines();
1499 let op = match self.peek_kind() {
1500 TokenKind::Star => "*",
1501 TokenKind::Slash => "/",
1502 TokenKind::Percent => "%",
1503 _ => break,
1504 };
1505 self.advance();
1506 self.skip_expression_newlines();
1507 let right = self.parse_unary()?;
1508 let span = Span::new(left.span().file, left.span().start, right.span().end);
1509 left = Expr::Binary {
1510 op: op.to_string(),
1511 left: Box::new(left),
1512 right: Box::new(right),
1513 span,
1514 };
1515 }
1516 Ok(left)
1517 }
1518
1519 fn parse_unary(&mut self) -> Result<Expr, ()> {
1520 self.skip_expression_newlines();
1521 if matches!(self.peek_kind(), TokenKind::Minus | TokenKind::Decrement) {
1522 let start = self.advance();
1523 self.skip_expression_newlines();
1524 let operand = self.parse_unary()?;
1525 let end = operand.span().end;
1526 let unary = Expr::Unary {
1527 op: "-".to_string(),
1528 operand: Box::new(operand),
1529 span: Span::new(start.span.file, start.span.start, end),
1530 };
1531 if start.kind == TokenKind::Decrement {
1532 return Ok(Expr::Unary {
1533 op: "-".to_string(),
1534 operand: Box::new(unary),
1535 span: Span::new(start.span.file, start.span.start, end),
1536 });
1537 }
1538 return Ok(unary);
1539 }
1540 self.parse_power()
1541 }
1542
1543 fn parse_power(&mut self) -> Result<Expr, ()> {
1544 let base = self.parse_postfix()?;
1545 self.skip_expression_newlines();
1546 if self.peek_kind() == TokenKind::DoubleStar {
1547 self.advance();
1548 self.skip_expression_newlines();
1550 let exponent = self.parse_unary()?;
1551 let span = Span::new(base.span().file, base.span().start, exponent.span().end);
1552 return Ok(Expr::Binary {
1553 op: "**".to_string(),
1554 left: Box::new(base),
1555 right: Box::new(exponent),
1556 span,
1557 });
1558 }
1559 Ok(base)
1560 }
1561
1562 fn parse_postfix(&mut self) -> Result<Expr, ()> {
1563 let mut base = self.parse_primary()?;
1564 loop {
1565 self.skip_expression_newlines();
1566 match self.peek_kind() {
1567 TokenKind::LParen => {
1568 let mut args = Vec::new();
1569 self.parse_call_args(&mut args)?;
1570 let end = self.tokens[self.pos.saturating_sub(1)].span.end;
1571 base = match base {
1572 Expr::Name { name, span } => Expr::Call {
1573 name,
1574 args,
1575 span: Span::new(span.file, span.start, end),
1576 },
1577 Expr::Member {
1578 receiver,
1579 member,
1580 span,
1581 ..
1582 } => Expr::ReceiverCall {
1583 receiver,
1584 name: member,
1585 args,
1586 span: Span::new(span.file, span.start, end),
1587 },
1588 _other => {
1589 self.errors.push(OpyError::at(
1590 "parse-error",
1591 "cannot call this expression".to_string(),
1592 self.peek().span,
1593 ));
1594 return Err(());
1595 }
1596 };
1597 }
1598 TokenKind::LBracket => {
1599 self.advance();
1600 let index = self.parse_expr()?;
1601 if self.peek_kind() == TokenKind::Colon {
1602 self.advance();
1603 let maximum = self.parse_expr()?;
1604 let end = self.expect(TokenKind::RBracket, "']'")?.span.end;
1605 let Expr::Name { name, span } = &base else {
1606 self.error_at_current(
1607 "a range type must start with a type name".to_string(),
1608 );
1609 return Err(());
1610 };
1611 base = Expr::Type {
1612 name: name.clone(),
1613 args: vec![index, maximum],
1614 span: Span::new(span.file, span.start, end),
1615 };
1616 continue;
1617 }
1618 let end = match self.expect(TokenKind::RBracket, "']'") {
1619 Ok(token) => token.span.end,
1620 Err(()) => return Err(()),
1621 };
1622 let span = Span::new(base.span().file, base.span().start, end);
1623 base = Expr::Index {
1624 array: Box::new(base),
1625 index: Box::new(index),
1626 span,
1627 };
1628 }
1629 TokenKind::Dot => {
1630 self.advance();
1631 let member_token = self.peek().clone();
1632 let member = match self.peek_kind() {
1633 TokenKind::Ident | TokenKind::Number => self.advance().text,
1634 _ => {
1635 self.error_at_current("expected a member name after '.'".to_string());
1636 return Err(());
1637 }
1638 };
1639 let member_span = member_token.span;
1640 let end = member_span.end;
1641 let span = Span::new(base.span().file, base.span().start, end);
1642 base = Expr::Member {
1643 receiver: Box::new(base),
1644 member,
1645 member_span,
1646 span,
1647 };
1648 }
1649 _ => break,
1650 }
1651 }
1652 Ok(base)
1653 }
1654
1655 fn parse_event_args(&mut self, args: &mut Vec<Expr>) -> Result<(), ()> {
1658 self.expect(TokenKind::LParen, "'('")?;
1659 self.skip_newlines();
1660 if self.peek_kind() == TokenKind::RParen {
1661 self.advance();
1662 return Ok(());
1663 }
1664 loop {
1665 let expr = self.parse_expr()?;
1666 if self.peek_kind() == TokenKind::Assign {
1667 self.error_at_current("keyword arguments are not valid in @Event".to_string());
1668 return Err(());
1669 }
1670 args.push(expr);
1671 self.skip_newlines();
1672 if self.peek_kind() == TokenKind::Comma {
1673 self.advance();
1674 self.skip_newlines();
1675 if self.peek_kind() == TokenKind::RParen {
1676 break;
1677 }
1678 } else {
1679 break;
1680 }
1681 }
1682 self.expect(TokenKind::RParen, "')'")?;
1683 Ok(())
1684 }
1685
1686 fn parse_call_args(&mut self, args: &mut Vec<CallArg>) -> Result<(), ()> {
1687 self.expect(TokenKind::LParen, "'('")?;
1688 self.skip_newlines();
1689 if self.peek_kind() == TokenKind::RParen {
1690 self.advance();
1691 return Ok(());
1692 }
1693 loop {
1694 match self.parse_expr() {
1695 Ok(expr) => {
1696 if self.peek_kind() == TokenKind::Assign {
1701 let Expr::Name { name, span } = expr else {
1702 self.error_at_current(
1703 "expected a keyword name before '=' in this call".to_string(),
1704 );
1705 return Err(());
1706 };
1707 self.advance();
1708 let value = match self.parse_expr() {
1709 Ok(value) => value,
1710 Err(()) => return Err(()),
1711 };
1712 args.push(CallArg {
1713 keyword: Some((name, span)),
1714 value,
1715 });
1716 } else {
1717 args.push(CallArg {
1718 keyword: None,
1719 value: expr,
1720 });
1721 }
1722 }
1723 Err(()) => return Err(()),
1724 }
1725 self.skip_newlines();
1726 if self.peek_kind() == TokenKind::Comma {
1727 self.advance();
1728 self.skip_newlines();
1729 if self.peek_kind() == TokenKind::RParen {
1730 break;
1731 }
1732 } else {
1733 break;
1734 }
1735 }
1736 self.expect(TokenKind::RParen, "')'")?;
1737 Ok(())
1738 }
1739
1740 fn parse_primary(&mut self) -> Result<Expr, ()> {
1741 let token = self.peek();
1742 match token.kind {
1743 TokenKind::Number => {
1744 let token = self.advance();
1745 let value = if let Some(hex) = token
1746 .text
1747 .strip_prefix("0x")
1748 .or_else(|| token.text.strip_prefix("0X"))
1749 {
1750 u64::from_str_radix(hex, 16).map_or(f64::NAN, |value| value as f64)
1751 } else {
1752 token.text.parse().unwrap_or(f64::NAN)
1753 };
1754 Ok(Expr::Number {
1755 value,
1756 text: token.text.clone(),
1757 span: token.span,
1758 })
1759 }
1760 TokenKind::String => self.parse_string_literal(),
1761 TokenKind::Ident => {
1762 let token = self.advance();
1763 if token.text == "lambda" {
1764 return self.parse_lambda(token.span);
1765 }
1766 if is_string_modifier(&token.text) && self.peek_kind() == TokenKind::String {
1767 let string = self.advance();
1768 let (format_text, interpolations) = if token.text == "f" {
1769 let raw = string.raw.as_deref().unwrap_or(&string.text);
1770 let (format_text, interpolations) =
1771 self.parse_f_string(raw, string.span)?;
1772 (Some(format_text), interpolations)
1773 } else {
1774 (None, Vec::new())
1775 };
1776 return Ok(Expr::StringModifier {
1777 modifier: token.text.chars().next().unwrap_or_default(),
1778 value: string.text,
1779 format_text,
1780 interpolations,
1781 span: Span::new(token.span.file, token.span.start, string.span.end),
1782 });
1783 }
1784 match token.text.as_str() {
1785 "true" => Ok(Expr::Bool {
1786 value: true,
1787 span: token.span,
1788 }),
1789 "false" => Ok(Expr::Bool {
1790 value: false,
1791 span: token.span,
1792 }),
1793 "None" | "null" => Ok(Expr::Null { span: token.span }),
1794 _ => Ok(Expr::Name {
1795 name: token.text.clone(),
1796 span: token.span,
1797 }),
1798 }
1799 }
1800 TokenKind::LParen => {
1801 self.advance();
1802 self.skip_expression_newlines();
1803 let expr = self.parse_expr()?;
1804 self.skip_expression_newlines();
1805 self.expect(TokenKind::RParen, "')'")?;
1806 Ok(expr)
1807 }
1808 TokenKind::LBracket => {
1809 let open = self.advance();
1810 let mut elements = Vec::new();
1811 self.skip_newlines();
1812 if self.peek_kind() == TokenKind::RBracket {
1813 let end = self.advance().span.end;
1814 return Ok(Expr::Array {
1815 elements,
1816 span: Span::new(open.span.file, open.span.start, end),
1817 });
1818 }
1819 let first = self.parse_expr()?;
1820 if self.is_ident("for") {
1821 self.advance();
1822 let variable_token =
1823 self.expect(TokenKind::Ident, "a comprehension variable")?;
1824 let index = if self.peek_kind() == TokenKind::Comma {
1825 self.advance();
1826 let index = self.expect(TokenKind::Ident, "a comprehension index")?;
1827 Some((index.text, index.span))
1828 } else {
1829 None
1830 };
1831 if !self.is_ident("in") {
1832 self.error_at_current("expected `in` in list comprehension".to_string());
1833 return Err(());
1834 }
1835 self.advance();
1836 let iterable = self.parse_or()?;
1837 let condition = if self.is_ident("if") {
1838 self.advance();
1839 Some(Box::new(self.parse_or()?))
1840 } else {
1841 None
1842 };
1843 let end = self.expect(TokenKind::RBracket, "']'")?.span.end;
1844 return Ok(Expr::Comprehension {
1845 element: Box::new(first),
1846 variable: variable_token.text,
1847 variable_span: variable_token.span,
1848 index,
1849 iterable: Box::new(iterable),
1850 condition,
1851 span: Span::new(open.span.file, open.span.start, end),
1852 });
1853 }
1854 elements.push(first);
1855 loop {
1856 self.skip_newlines();
1857 if self.peek_kind() == TokenKind::Comma {
1858 self.advance();
1859 self.skip_newlines();
1860 if self.peek_kind() == TokenKind::RBracket {
1861 break;
1862 }
1863 elements.push(self.parse_expr()?);
1864 } else {
1865 break;
1866 }
1867 }
1868 let end = match self.expect(TokenKind::RBracket, "']'") {
1869 Ok(token) => token.span.end,
1870 Err(()) => return Err(()),
1871 };
1872 Ok(Expr::Array {
1873 elements,
1874 span: Span::new(open.span.file, open.span.start, end),
1875 })
1876 }
1877 TokenKind::LBrace => self.parse_dict(),
1878 _ => {
1879 self.error_at_current(format!("expected an expression but found '{}'", token.text));
1880 Err(())
1881 }
1882 }
1883 }
1884
1885 fn parse_string_literal(&mut self) -> Result<Expr, ()> {
1892 let first = self.advance();
1893 let mut value = first.text.clone();
1894 let mut end = first.span.end;
1895 loop {
1896 let saved = self.pos;
1897 if self.inside_delimiter_group() {
1898 self.skip_newlines();
1899 }
1900 if self.peek_kind() != TokenKind::String || self.peek().span.file != first.span.file {
1901 self.pos = saved;
1902 break;
1903 }
1904 let next = self.advance();
1905 value.push_str(&next.text);
1906 end = next.span.end;
1907 }
1908 Ok(Expr::String {
1909 value,
1910 span: Span::new(first.span.file, first.span.start, end),
1911 })
1912 }
1913
1914 fn inside_delimiter_group(&self) -> bool {
1919 let mut depth = 0usize;
1920 for token in &self.tokens[..self.pos] {
1921 match token.kind {
1922 TokenKind::LParen | TokenKind::LBracket | TokenKind::LBrace => depth += 1,
1923 TokenKind::RParen | TokenKind::RBracket | TokenKind::RBrace => {
1924 depth = depth.saturating_sub(1)
1925 }
1926 _ => {}
1927 }
1928 }
1929 depth != 0
1930 }
1931
1932 fn parse_dict(&mut self) -> Result<Expr, ()> {
1933 let open = self.advance();
1934 let mut entries = Vec::new();
1935 self.skip_newlines();
1936 if self.peek_kind() == TokenKind::RBrace {
1937 let end = self.advance().span.end;
1938 return Ok(Expr::Dict {
1939 entries,
1940 span: Span::new(open.span.file, open.span.start, end),
1941 });
1942 }
1943 loop {
1944 let key = self.parse_expr()?;
1945 self.expect(TokenKind::Colon, "':' in a dictionary entry")?;
1946 let value = self.parse_expr()?;
1947 let span = Span::new(key.span().file, key.span().start, value.span().end);
1948 entries.push(DictEntry { key, value, span });
1949 self.skip_newlines();
1950 if self.peek_kind() == TokenKind::Comma {
1951 self.advance();
1952 self.skip_newlines();
1953 if self.peek_kind() == TokenKind::RBrace {
1954 break;
1955 }
1956 } else {
1957 break;
1958 }
1959 }
1960 let end = self.expect(TokenKind::RBrace, "'}'")?.span.end;
1961 Ok(Expr::Dict {
1962 entries,
1963 span: Span::new(open.span.file, open.span.start, end),
1964 })
1965 }
1966
1967 fn parse_lambda(&mut self, start: Span) -> Result<Expr, ()> {
1968 let mut params = Vec::new();
1969 loop {
1970 let param = self.expect(TokenKind::Ident, "a lambda parameter")?;
1971 params.push((param.text, param.span));
1972 if self.peek_kind() == TokenKind::Comma {
1973 self.advance();
1974 } else {
1975 break;
1976 }
1977 }
1978 self.expect(TokenKind::Colon, "':' after lambda parameters")?;
1979 let body = self.parse_expr()?;
1980 Ok(Expr::Lambda {
1981 params,
1982 body: Box::new(body.clone()),
1983 span: Span::new(start.file, start.start, body.span().end),
1984 })
1985 }
1986
1987 fn parse_f_string(&mut self, raw: &str, string_span: Span) -> Result<(String, Vec<Expr>), ()> {
1992 let chars: Vec<char> = raw.chars().collect();
1993 let mut text = String::new();
1994 let mut interpolations = Vec::new();
1995 let mut index = 0;
1996 while index < chars.len() {
1997 match chars[index] {
1998 '{' if chars.get(index + 1) == Some(&'{') => {
1999 text.push_str("{{");
2000 index += 2;
2001 }
2002 '}' if chars.get(index + 1) == Some(&'}') => {
2003 text.push_str("}}");
2004 index += 2;
2005 }
2006 '{' => {
2007 let end = self.find_f_string_end(&chars, index + 1);
2008 let Some(end) = end else {
2009 self.errors.push(OpyError::at(
2010 "parse-error",
2011 "unterminated f-string interpolation".to_string(),
2012 string_span,
2013 ));
2014 return Err(());
2015 };
2016 let expression: String = chars[index + 1..end].iter().collect();
2017 if expression.trim().is_empty() {
2018 self.errors.push(OpyError::at(
2019 "parse-error",
2020 "f-string interpolation cannot be empty".to_string(),
2021 Span::new(
2022 string_span.file,
2023 Position::new(
2024 string_span.start.line,
2025 string_span.start.col + index as u32 + 1,
2026 ),
2027 Position::new(
2028 string_span.start.line,
2029 string_span.start.col + end as u32 + 1,
2030 ),
2031 ),
2032 ));
2033 return Err(());
2034 }
2035 let origin = Position::new(
2036 string_span.start.line,
2037 string_span.start.col + index as u32 + 1,
2038 );
2039 let parsed = parse_expression_fragment(&expression, string_span.file, origin)
2040 .map_err(|error| {
2041 self.errors.push(error);
2042 });
2043 let Ok(parsed) = parsed else {
2044 return Err(());
2045 };
2046 text.push_str(&format!("{{{}}}", interpolations.len()));
2047 interpolations.push(parsed);
2048 index = end + 1;
2049 }
2050 '}' => {
2051 self.errors.push(OpyError::at(
2052 "parse-error",
2053 "single '}' is not valid in an f-string".to_string(),
2054 string_span,
2055 ));
2056 return Err(());
2057 }
2058 '\\' if index + 1 < chars.len() => {
2059 text.push(decode_string_escape(chars[index + 1]));
2060 index += 2;
2061 }
2062 character => {
2063 text.push(character);
2064 index += 1;
2065 }
2066 }
2067 }
2068 Ok((text, interpolations))
2069 }
2070
2071 fn find_f_string_end(&self, chars: &[char], start: usize) -> Option<usize> {
2072 let mut nested_braces = 0;
2073 let mut quote = None;
2074 let mut escaped = false;
2075 for (index, character) in chars.iter().enumerate().skip(start) {
2076 if escaped {
2077 escaped = false;
2078 continue;
2079 }
2080 if *character == '\\' && quote.is_some() {
2081 escaped = true;
2082 continue;
2083 }
2084 if let Some(active_quote) = quote {
2085 if *character == active_quote {
2086 quote = None;
2087 }
2088 continue;
2089 }
2090 match character {
2091 '"' | '\'' => quote = Some(*character),
2092 '{' => nested_braces += 1,
2093 '}' if nested_braces == 0 => return Some(index),
2094 '}' => nested_braces -= 1,
2095 _ => {}
2096 }
2097 }
2098 None
2099 }
2100}
2101
2102fn parse_expression_fragment(text: &str, file: u32, origin: Position) -> Result<Expr, OpyError> {
2105 let mut tokens = crate::lexer::lex(crate::lexer::LexInput {
2106 file_id: file,
2107 text,
2108 })?;
2109 for token in &mut tokens {
2110 token.span = shift_span(token.span, origin);
2111 }
2112 let mut parser = Parser {
2113 tokens: &tokens,
2114 pos: 0,
2115 allow_macro_redeclaration: false,
2116 errors: Vec::new(),
2117 };
2118 let expression = parser.parse_expr().map_err(|()| {
2119 parser.errors.first().cloned().unwrap_or_else(|| {
2120 OpyError::at(
2121 "parse-error",
2122 "invalid f-string expression",
2123 Span::new(file, origin, origin),
2124 )
2125 })
2126 })?;
2127 if parser.peek_kind() != TokenKind::Eof {
2128 parser.error_at_current("unexpected tokens in f-string interpolation".to_string());
2129 }
2130 parser.errors.into_iter().next().map_or(Ok(expression), Err)
2131}
2132
2133fn shift_span(span: Span, origin: Position) -> Span {
2134 fn shift(position: Position, origin: Position) -> Position {
2135 Position::new(
2136 origin.line + position.line.saturating_sub(1),
2137 if position.line == 1 {
2138 origin.col + position.col.saturating_sub(1)
2139 } else {
2140 position.col
2141 },
2142 )
2143 }
2144 Span::new(
2145 span.file,
2146 shift(span.start, origin),
2147 shift(span.end, origin),
2148 )
2149}
2150
2151fn decode_string_escape(character: char) -> char {
2152 match character {
2153 'n' => '\n',
2154 't' => '\t',
2155 'r' => '\r',
2156 '\\' => '\\',
2157 '"' => '"',
2158 '\'' => '\'',
2159 other => other,
2160 }
2161}
2162
2163fn is_string_modifier(text: &str) -> bool {
2164 matches!(text, "f" | "w" | "l" | "b" | "c" | "t")
2165}
2166
2167#[cfg(test)]
2168mod tests {
2169 use super::*;
2170 use crate::lexer::{LexInput, lex};
2171
2172 fn parse_ok(text: &str) -> Program {
2173 let tokens = lex(LexInput { file_id: 0, text }).unwrap();
2174 let output = parse(&tokens);
2175 assert!(
2176 output.errors.is_empty(),
2177 "unexpected errors: {:?}",
2178 output.errors
2179 );
2180 output.program.unwrap()
2181 }
2182
2183 fn parse_err(text: &str) -> Vec<OpyError> {
2184 let tokens = lex(LexInput { file_id: 0, text }).unwrap();
2185 parse(&tokens).errors
2186 }
2187
2188 #[test]
2189 fn parses_basic_rule() {
2190 let program = parse_ok("rule \"setup\":\n @Event global\n disableInspector()\n");
2191 assert_eq!(program.rules.len(), 1);
2192 let RuleEntry::Rule(rule) = &program.rules[0] else {
2193 panic!("expected rule");
2194 };
2195 assert_eq!(rule.name, "setup");
2196 assert_eq!(rule.event.name, "global");
2197 assert_eq!(rule.actions.len(), 1);
2198 }
2199
2200 #[test]
2201 fn parses_power_augmented_assignment() {
2202 let program =
2205 parse_ok("globalvar a\nrule \"r\":\n @Event global\n a = 2\n a **= 3\n");
2206 let RuleEntry::Rule(rule) = &program.rules[0] else {
2207 panic!("expected rule");
2208 };
2209 let Stmt::Assign {
2210 value,
2211 target: assigned_target,
2212 ..
2213 } = &rule.actions[1]
2214 else {
2215 panic!("expected an assignment");
2216 };
2217 let Expr::Binary {
2218 op, left, right, ..
2219 } = value
2220 else {
2221 panic!("expected a binary modification, got {value:?}");
2222 };
2223 assert_eq!(op, "**");
2224 assert!(matches!(&**left, Expr::Name { .. }));
2225 assert!(matches!(
2226 assigned_target,
2227 Expr::Name { name, .. } if name == "a"
2228 ));
2229 assert!(matches!(right.as_ref(), Expr::Number { .. }));
2230 }
2231
2232 #[test]
2233 fn parses_postfix_increment_and_decrement_as_modifications() {
2234 let program =
2235 parse_ok("globalvar value\nrule \"r\":\n @Event global\n value++\n value--\n");
2236 let RuleEntry::Rule(rule) = &program.rules[0] else {
2237 panic!("expected a rule");
2238 };
2239 for (statement, expected_op) in [(&rule.actions[0], "+"), (&rule.actions[1], "-")] {
2240 let Stmt::Assign { target, value, .. } = statement else {
2241 panic!("expected a postfix assignment");
2242 };
2243 let Expr::Binary {
2244 op, left, right, ..
2245 } = value
2246 else {
2247 panic!("expected a synthetic modification value");
2248 };
2249 assert_eq!(op, expected_op);
2250 let Expr::Name {
2251 name: left_name, ..
2252 } = left.as_ref()
2253 else {
2254 panic!("expected the target to be the modification's left operand");
2255 };
2256 let Expr::Name {
2257 name: target_name, ..
2258 } = target
2259 else {
2260 panic!("expected a name target");
2261 };
2262 assert_eq!(left_name, target_name);
2263 assert!(
2264 matches!(right.as_ref(), Expr::Number { value, text, .. } if *value == 1.0 && text == "1")
2265 );
2266 }
2267 }
2268
2269 #[test]
2270 fn rejects_prefix_increment_and_embedded_postfix_forms() {
2271 for source in [
2272 "globalvar value\nrule \"r\":\n @Event global\n ++value\n",
2273 "globalvar value\nrule \"r\":\n @Event global\n value++++\n",
2274 ] {
2275 let errors = parse_err(source);
2276 assert!(!errors.is_empty());
2277 assert!(errors.iter().all(|error| error.code == "parse-error"));
2278 assert!(errors.iter().all(|error| error.span.is_some()));
2279 }
2280 }
2281
2282 #[test]
2283 fn preserves_consecutive_unary_minus_expressions() {
2284 let source = concat!(
2285 "globalvar value = 0\n",
2286 "globalvar B = 1\n",
2287 "rule \"r\":\n",
2288 " @Event global\n",
2289 " value = --1\n",
2290 " value = --B\n",
2291 " value = B--1\n",
2292 );
2293 parse_ok(source);
2294 }
2295
2296 #[test]
2297 fn parses_control_flow() {
2298 let program = parse_ok(
2299 "globalvar index = 0\n\nrule \"r\":\n @Event global\n for index in range(3):\n if index == 0:\n debug(index)\n elif index == 1:\n debug(index)\n else:\n debug(index)\n while index < 3:\n index += 1\n wait()\n",
2300 );
2301 let RuleEntry::Rule(rule) = &program.rules[0] else {
2302 panic!();
2303 };
2304 assert!(matches!(rule.actions[0], Stmt::For { .. }));
2305 let Stmt::For { body, .. } = &rule.actions[0] else {
2306 panic!();
2307 };
2308 let Stmt::If {
2309 branches, r#else, ..
2310 } = &body[0]
2311 else {
2312 panic!();
2313 };
2314 assert_eq!(branches.len(), 2);
2315 assert!(r#else.is_some());
2316 let Stmt::While { body, .. } = &rule.actions[1] else {
2317 panic!();
2318 };
2319 assert_eq!(body.len(), 2);
2320 }
2321
2322 #[test]
2323 fn parses_issue_141_statement_surface() {
2324 let program = parse_ok(concat!(
2325 "globalvar value\n",
2326 "rule \"r\":\n",
2327 " @Event global\n",
2328 " del value[1]\n",
2329 " value min= 2\n",
2330 " value max= 3\n",
2331 " while value < 4:\n",
2332 " continue\n",
2333 " goto RULE_START\n",
2334 " goto target\n",
2335 " goto loc + value\n",
2336 " target:\n",
2337 ));
2338 let RuleEntry::Rule(rule) = &program.rules[0] else {
2339 panic!("expected rule");
2340 };
2341 assert!(matches!(rule.actions[0], Stmt::Delete { .. }));
2342 for (statement, expected) in [(&rule.actions[1], "min"), (&rule.actions[2], "max")] {
2343 let Stmt::Assign { value, .. } = statement else {
2344 panic!("expected augmented assignment");
2345 };
2346 assert!(matches!(value, Expr::Binary { op, .. } if op == expected));
2347 }
2348 let Stmt::While { body, .. } = &rule.actions[3] else {
2349 panic!("expected while");
2350 };
2351 assert!(matches!(body.as_slice(), [Stmt::Continue { .. }]));
2352 assert!(matches!(
2353 &rule.actions[4],
2354 Stmt::Goto {
2355 label: None,
2356 offset: None,
2357 rule_start: true,
2358 ..
2359 }
2360 ));
2361 assert!(matches!(
2362 &rule.actions[5],
2363 Stmt::Goto {
2364 label: Some(label),
2365 offset: None,
2366 rule_start: false,
2367 ..
2368 } if label == "target"
2369 ));
2370 assert!(matches!(
2371 &rule.actions[6],
2372 Stmt::Goto {
2373 label: None,
2374 offset: Some(_),
2375 rule_start: false,
2376 ..
2377 }
2378 ));
2379 assert!(matches!(&rule.actions[7], Stmt::Label { name, .. } if name == "target"));
2380 }
2381
2382 #[test]
2383 fn rejects_invalid_issue_141_statement_forms() {
2384 for source in [
2385 "rule \"r\":\n @Event global\n del value\n",
2386 "rule \"r\":\n @Event global\n goto\n",
2387 "rule \"r\":\n @Event global\n goto loc\n",
2388 "rule \"r\":\n @Event global\n goto target extra\n",
2389 "rule \"r\":\n @Event global\n continue now\n",
2390 "rule \"r\":\n @Event global\n A = 1; A = 2\n",
2391 ] {
2392 let errors = parse_err(source);
2393 assert!(!errors.is_empty(), "invalid form parsed: {source}");
2394 assert!(errors.iter().all(|error| error.code == "parse-error"));
2395 assert!(errors.iter().all(|error| error.span.is_some()));
2396 }
2397 }
2398
2399 #[test]
2400 fn parses_issue_28_constructs() {
2401 let program = parse_ok(
2402 "globalvar x\nrule \"r\":\n @Event global\n switch x:\n case 0x10:\n x = 1 in [1, 2]\n default:\n do:\n x = {\"x\": 1}[\"x\"]\n while x not in [2, 3]\n x = [value * 2 for value, index in [1, 2] if value > index]\n x = sorted([1, 2], key=lambda value: value)\n x = w\"wide\"\n",
2403 );
2404 let RuleEntry::Rule(rule) = &program.rules[0] else {
2405 panic!("expected rule");
2406 };
2407 assert!(matches!(rule.actions[0], Stmt::Switch { .. }));
2408 assert!(matches!(rule.actions[1], Stmt::Assign { .. }));
2409 }
2410
2411 #[test]
2412 fn rejects_incomplete_do_while_and_dictionary_entries() {
2413 let errors = parse_err(
2414 "rule \"r\":\n @Event global\n do:\n pass\n while\n x = {\"x\"}\n",
2415 );
2416 assert!(!errors.is_empty());
2417 assert!(errors.iter().all(|error| error.code == "parse-error"));
2418 }
2419
2420 #[test]
2421 fn parses_multi_line_array() {
2422 let program = parse_ok(
2423 "globalvar p\nrule \"r\":\n @Event global\n p = [\n vect(1, 0, 0),\n vect(2, 0, 0),\n ]\n",
2424 );
2425 let RuleEntry::Rule(rule) = &program.rules[0] else {
2426 panic!();
2427 };
2428 let Stmt::Assign { value, .. } = &rule.actions[0] else {
2429 panic!();
2430 };
2431 let Expr::Array { elements, .. } = value else {
2432 panic!("expected array, got {value:?}");
2433 };
2434 assert_eq!(elements.len(), 2);
2435 }
2436
2437 #[test]
2438 fn missing_colon_is_a_structured_error() {
2439 let errors = parse_err("rule \"x\"\n @Event global\n");
2440 assert!(!errors.is_empty());
2441 assert_eq!(errors[0].code, "parse-error");
2442 assert!(errors[0].span.is_some());
2443 }
2444
2445 #[test]
2446 fn def_and_macro_parse() {
2447 let program = parse_ok(
2448 "subroutine showStatus\n\ndef showStatus():\n print(\"hi\")\n\nmacro double(value):\n value + value\n",
2449 );
2450 assert_eq!(program.declarations.len(), 2);
2451 assert!(matches!(program.declarations[1], Decl::Macro { .. }));
2452 let Decl::Macro { args, body, .. } = &program.declarations[1] else {
2453 panic!();
2454 };
2455 assert_eq!(args, &vec!["value".to_string()]);
2456 assert_eq!(body.len(), 1);
2457 }
2458
2459 #[test]
2460 fn macro_and_enum_redeclarations_are_checked_at_ast_surfaces() {
2461 let text = "enum Kind:\n First\n First\nmacro helper():\n pass\nmacro helper():\n pass\n";
2462 let errors = parse_err(text);
2463 assert_eq!(
2464 errors
2465 .iter()
2466 .filter(|error| error.code == "macro-redeclaration")
2467 .count(),
2468 2
2469 );
2470
2471 let tokens = lex(LexInput { file_id: 0, text }).unwrap();
2472 let output = parse_with_options(&tokens, true);
2473 assert!(
2474 output.errors.is_empty(),
2475 "unexpected errors: {:?}",
2476 output.errors
2477 );
2478 assert!(output.program.is_some());
2479 }
2480
2481 #[test]
2482 fn multiple_errors_are_reported() {
2483 let errors =
2484 parse_err("rule \"a\"\n bad statement here\nrule \"b\"\n @Event global\n");
2485 assert!(!errors.is_empty());
2486 }
2487
2488 #[test]
2489 fn precedence_parses_python_like() {
2490 let program = parse_ok("globalvar x\nrule \"r\":\n @Event global\n x = 1 + 2 * 3\n");
2491 let RuleEntry::Rule(rule) = &program.rules[0] else {
2492 panic!();
2493 };
2494 let Stmt::Assign { value, .. } = &rule.actions[0] else {
2495 panic!();
2496 };
2497 let Expr::Binary {
2498 op, left, right, ..
2499 } = value
2500 else {
2501 panic!();
2502 };
2503 assert_eq!(op, "+");
2504 let Expr::Binary { op: inner, .. } = right.as_ref() else {
2505 panic!();
2506 };
2507 assert_eq!(inner, "*");
2508 assert!(matches!(left.as_ref(), Expr::Number { .. }));
2509 }
2510
2511 #[test]
2512 fn parses_right_associative_conditional_expressions() {
2513 let program = parse_ok(
2514 "rule \"r\":\n @Event global\n debug(1 if true else 2 if false else 3)\n",
2515 );
2516 let RuleEntry::Rule(rule) = &program.rules[0] else {
2517 panic!("expected a rule");
2518 };
2519 let Stmt::Expr { expr, .. } = &rule.actions[0] else {
2520 panic!("expected an expression statement");
2521 };
2522 let Expr::Call { args, .. } = expr else {
2523 panic!("expected a call");
2524 };
2525 let Expr::Conditional {
2526 then_value,
2527 condition,
2528 else_value,
2529 span,
2530 } = &args[0].value
2531 else {
2532 panic!("expected a conditional expression");
2533 };
2534 assert!(matches!(then_value.as_ref(), Expr::Number { value, .. } if *value == 1.0));
2535 assert!(matches!(condition.as_ref(), Expr::Bool { value: true, .. }));
2536 assert!(matches!(
2537 else_value.as_ref(),
2538 Expr::Conditional { then_value, condition, else_value, .. }
2539 if matches!(then_value.as_ref(), Expr::Number { value, .. } if *value == 2.0)
2540 && matches!(condition.as_ref(), Expr::Bool { value: false, .. })
2541 && matches!(else_value.as_ref(), Expr::Number { value, .. } if *value == 3.0)
2542 ));
2543 assert_eq!(span.start.line, 3);
2544 assert_eq!(span.start.col, 11);
2545 }
2546
2547 #[test]
2548 fn parses_parenthesized_nested_conditional_and_rejects_missing_else() {
2549 let program = parse_ok(
2550 "rule \"r\":\n @Event global\n debug((1 if true else 2) if false else 3)\n",
2551 );
2552 let RuleEntry::Rule(rule) = &program.rules[0] else {
2553 panic!("expected a rule");
2554 };
2555 let Stmt::Expr { expr, .. } = &rule.actions[0] else {
2556 panic!("expected an expression statement");
2557 };
2558 let Expr::Call { args, .. } = expr else {
2559 panic!("expected a call");
2560 };
2561 assert!(matches!(
2562 &args[0].value,
2563 Expr::Conditional {
2564 then_value,
2565 condition,
2566 else_value,
2567 ..
2568 } if matches!(then_value.as_ref(), Expr::Conditional { .. })
2569 && matches!(condition.as_ref(), Expr::Bool { value: false, .. })
2570 && matches!(else_value.as_ref(), Expr::Number { value, .. } if *value == 3.0)
2571 ));
2572
2573 let errors = parse_err("rule \"r\":\n @Event global\n debug(1 if true)\n");
2574 assert_eq!(errors[0].code, "parse-error");
2575 assert!(errors[0].message.contains("expected `else`"));
2576 }
2577
2578 #[test]
2579 fn parses_receiver_calls() {
2580 let program =
2583 parse_ok("rule \"r\":\n @Event eachPlayer\n eventPlayer.setMoveSpeed(100)\n");
2584 let RuleEntry::Rule(rule) = &program.rules[0] else {
2585 panic!("expected rule");
2586 };
2587 let Stmt::Expr { expr, .. } = &rule.actions[0] else {
2588 panic!("expected expression statement, got {:?}", rule.actions[0]);
2589 };
2590 let Expr::ReceiverCall {
2591 receiver,
2592 name,
2593 args,
2594 ..
2595 } = &expr
2596 else {
2597 panic!("expected receiver call, got {expr:?}");
2598 };
2599 assert_eq!(name, "setMoveSpeed");
2600 assert!(
2601 matches!(receiver.as_ref(), Expr::Name { name, .. } if name == "eventPlayer"),
2602 "receiver must be the eventPlayer name"
2603 );
2604 assert_eq!(args.len(), 1);
2605 assert!(args[0].keyword.is_none(), "positional argument");
2606 assert!(matches!(&args[0].value, Expr::Number { .. }));
2607 }
2608
2609 #[test]
2610 fn parses_keyword_arguments_with_name_spans() {
2611 let program =
2614 parse_ok("rule \"r\":\n @Event global\n wait(time=1)\n debug(g == 1)\n");
2615 let RuleEntry::Rule(rule) = &program.rules[0] else {
2616 panic!("expected rule");
2617 };
2618 let Stmt::Expr { expr, .. } = &rule.actions[0] else {
2619 panic!("expected expression statement");
2620 };
2621 let Expr::Call { args, .. } = expr else {
2622 panic!("expected a call, got {expr:?}");
2623 };
2624 let (keyword, span) = args[0].keyword.as_ref().expect("keyword argument");
2625 assert_eq!(keyword, "time");
2626 assert_eq!(span.start.line, 3);
2627 assert!(matches!(&args[0].value, Expr::Number { .. }));
2628
2629 let Stmt::Expr { expr, .. } = &rule.actions[1] else {
2630 panic!("expected expression statement");
2631 };
2632 let Expr::Call { args, .. } = expr else {
2633 panic!("expected a call, got {expr:?}");
2634 };
2635 assert!(args[0].keyword.is_none(), "comparisons are not keywords");
2636 assert!(matches!(&args[0].value, Expr::Binary { .. }));
2637 }
2638
2639 #[test]
2640 fn adjacent_string_literals_concatenate_and_preserve_span() {
2641 let program = parse_ok("rule \"r\":\n @Event global\n debug(\"one\" \"two\")\n");
2642 let RuleEntry::Rule(rule) = &program.rules[0] else {
2643 panic!("expected rule");
2644 };
2645 let Stmt::Expr { expr, .. } = &rule.actions[0] else {
2646 panic!("expected expression statement");
2647 };
2648 let Expr::Call { args, .. } = expr else {
2649 panic!("expected call");
2650 };
2651 let Expr::String { value, span } = &args[0].value else {
2652 panic!("expected concatenated string");
2653 };
2654 assert_eq!(value, "onetwo");
2655 assert_eq!(span.start.line, 3);
2656 assert_eq!(span.start.col, 11);
2657 assert_eq!(span.end.col, 22);
2658 }
2659
2660 #[test]
2661 fn multiline_adjacent_string_literals_concatenate_inside_group() {
2662 let program =
2663 parse_ok("rule \"r\":\n @Event global\n debug(\"one\"\n \"two\")\n");
2664 let RuleEntry::Rule(rule) = &program.rules[0] else {
2665 panic!("expected rule");
2666 };
2667 let Stmt::Expr { expr, .. } = &rule.actions[0] else {
2668 panic!("expected expression statement");
2669 };
2670 let Expr::Call { args, .. } = expr else {
2671 panic!("expected call");
2672 };
2673 assert!(matches!(
2674 &args[0].value,
2675 Expr::String { value, .. } if value == "onetwo"
2676 ));
2677 }
2678
2679 #[test]
2680 fn newline_outside_group_keeps_adjacent_literals_as_statements() {
2681 let program = parse_ok("rule \"r\":\n @Event global\n \"one\"\n \"two\"\n");
2682 let RuleEntry::Rule(rule) = &program.rules[0] else {
2683 panic!("expected rule");
2684 };
2685 assert_eq!(rule.actions.len(), 2);
2686 }
2687
2688 #[test]
2689 fn non_name_keyword_lhs_is_a_parse_error() {
2690 let errors = parse_err("rule \"r\":\n @Event global\n debug(1 = 2)\n");
2692 assert!(!errors.is_empty());
2693 assert_eq!(errors[0].code, "parse-error");
2694 }
2695
2696 #[test]
2697 fn parses_member_call_on_call_result() {
2698 let program = parse_ok(
2701 "rule \"r\":\n @Event eachPlayer\n getPlayersInRadius(eventPlayer, 10).setStatusEffect(eventPlayer, 30)\n",
2702 );
2703 let RuleEntry::Rule(rule) = &program.rules[0] else {
2704 panic!("expected rule");
2705 };
2706 let Stmt::Expr { expr, .. } = &rule.actions[0] else {
2707 panic!("expected expression statement");
2708 };
2709 let Expr::ReceiverCall {
2710 receiver,
2711 name,
2712 args,
2713 ..
2714 } = &expr
2715 else {
2716 panic!("expected receiver call, got {expr:?}");
2717 };
2718 assert_eq!(name, "setStatusEffect");
2719 assert!(
2720 matches!(receiver.as_ref(), Expr::Call { name, .. } if name == "getPlayersInRadius"),
2721 "receiver must be the preceding call"
2722 );
2723 assert_eq!(args.len(), 2);
2724 }
2725
2726 #[test]
2727 fn member_without_call_is_not_a_call() {
2728 let program =
2731 parse_ok("rule \"r\":\n @Event eachPlayer\n x = eventPlayer.moveSpeed\n");
2732 let RuleEntry::Rule(rule) = &program.rules[0] else {
2733 panic!("expected rule");
2734 };
2735 let Stmt::Assign { value, .. } = &rule.actions[0] else {
2736 panic!("expected assignment");
2737 };
2738 assert!(matches!(
2739 &value,
2740 Expr::Member { member, .. } if member == "moveSpeed"
2741 ));
2742 }
2743
2744 #[test]
2745 fn parses_advanced_rule_annotations_with_source_arguments() {
2746 let program = parse_ok(
2747 "subroutine helper\ndef helper():\n @Name \"renamed\"\n @SuppressWarnings unusedVariable\n pass\nrule \"r\":\n @Event eachPlayer\n @Team 1\n @Hero dmon\n @Disabled\n @Delimiter\n @NewPage \"Page\"\n @SuppressWarnings unusedVariable\n pass\n",
2748 );
2749 let RuleEntry::SubroutineDef { annotations, .. } = &program.rules[0] else {
2750 panic!("expected subroutine");
2751 };
2752 assert_eq!(annotations.len(), 2);
2753 let RuleEntry::Rule(rule) = &program.rules[1] else {
2754 panic!("expected rule");
2755 };
2756 assert!(rule.disabled);
2757 assert!(rule.delimiter);
2758 assert_eq!(rule.new_page.as_deref(), Some("Page"));
2759 assert_eq!(rule.annotations.len(), 7);
2760 assert_eq!(rule.annotations[1].args[0].text, "1");
2761 assert_eq!(rule.annotations[2].args[0].text, "dmon");
2762 }
2763}