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