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