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 language support surface"
927 .to_string(),
928 );
929 return false;
930 }
931 if self
932 .expect(TokenKind::Colon, "':' after the subroutine signature")
933 .is_err()
934 {
935 return false;
936 }
937 let line_indent = start.span.start.col;
938 let body_indent = match self.block_indent(line_indent) {
939 Some(indent) => indent,
940 None => return false,
941 };
942 let mut annotations = Vec::new();
943 let mut event = None;
944 let mut conditions = Vec::new();
945 let mut disabled = false;
946 let mut delimiter = false;
947 let mut new_page = None;
948 loop {
949 self.skip_newlines();
950 if self.peek_kind() != TokenKind::At {
951 break;
952 }
953 if !self.parse_directive(
954 &mut event,
955 &mut conditions,
956 &mut annotations,
957 &mut disabled,
958 &mut delimiter,
959 &mut new_page,
960 true,
961 ) {
962 self.recover_line();
963 return false;
964 }
965 }
966 if event.is_some() || !conditions.is_empty() {
967 self.error_at_current("subroutines cannot have events or conditions".to_string());
968 return false;
969 }
970 let _ = (disabled, delimiter, new_page);
971 let presentation_name = annotations
972 .iter()
973 .find(|annotation| annotation.name == "Name")
974 .and_then(|annotation| annotation.args.first())
975 .map(|arg| unquote_annotation_arg(&arg.text));
976 let body = self.parse_block(body_indent);
977 let span = if name_token.kind == TokenKind::Ident {
978 Span::new(start.span.file, start.span.start, name_token.span.end)
979 } else {
980 start.span
981 };
982 rules.push(RuleEntry::SubroutineDef {
983 name,
984 presentation_name,
985 span,
986 name_span,
987 body,
988 annotations,
989 rule_prefix,
990 });
991 true
992 }
993
994 fn block_indent(&mut self, line_indent: u32) -> Option<u32> {
997 self.skip_newlines();
998 if self.peek_kind() == TokenKind::Eof {
999 self.error_at_current("expected an indented block".to_string());
1000 return None;
1001 }
1002 let indent = self.peek().span.start.col;
1003 if indent <= line_indent {
1004 self.error_at_current("expected an indented block after ':'".to_string());
1005 return None;
1006 }
1007 Some(indent)
1008 }
1009
1010 fn parse_block(&mut self, block_indent: u32) -> Vec<Stmt> {
1013 let mut stmts = Vec::new();
1014 loop {
1015 self.skip_newlines();
1016 if self.peek_kind() == TokenKind::Eof {
1017 break;
1018 }
1019 if self.peek().span.start.col < block_indent {
1020 break;
1021 }
1022 if self.peek().span.start.col > block_indent {
1023 self.error_at_current("unexpected indentation".to_string());
1025 self.recover_line();
1026 continue;
1027 }
1028 match self.parse_statement() {
1029 Ok(stmt) => stmts.push(stmt),
1030 Err(()) => self.recover_line(),
1031 }
1032 }
1033 stmts
1034 }
1035
1036 fn parse_statement(&mut self) -> Result<Stmt, ()> {
1037 let token = self.peek();
1038 if token.kind == TokenKind::Ident {
1039 match token.text.as_str() {
1040 "if" => return self.parse_if(),
1041 "for" => return self.parse_for(),
1042 "while" => return self.parse_while(),
1043 "do" => return self.parse_do_while(),
1044 "switch" => return self.parse_switch(),
1045 "del" => return self.parse_delete(),
1046 "continue" => {
1047 let token = self.advance();
1048 self.expect_statement_end("the continue statement")?;
1049 return Ok(Stmt::Continue { span: token.span });
1050 }
1051 "goto" => return self.parse_goto(),
1052 "break" => {
1053 let token = self.advance();
1054 return Ok(Stmt::Break { span: token.span });
1055 }
1056 "return" => {
1057 let token = self.advance();
1058 self.expect_statement_end("the return statement")?;
1059 return Ok(Stmt::Return { span: token.span });
1060 }
1061 "pass" => {
1062 let start = self.advance();
1063 return Ok(Stmt::Pass { span: start.span });
1064 }
1065 _ => {}
1066 }
1067 if self.peek_at(1).kind == TokenKind::Colon {
1068 return self.parse_label();
1069 }
1070 }
1071 self.parse_expr_statement()
1072 }
1073
1074 fn parse_delete(&mut self) -> Result<Stmt, ()> {
1075 let start = self.advance();
1076 let target = self.parse_postfix()?;
1077 if !matches!(target, Expr::Index { .. }) {
1078 self.errors.push(OpyError::at(
1079 "parse-error",
1080 "the del statement requires an array index target".to_string(),
1081 target.span(),
1082 ));
1083 return Err(());
1084 }
1085 self.expect_statement_end("the del statement")?;
1086 Ok(Stmt::Delete {
1087 span: Span::new(start.span.file, start.span.start, target.span().end),
1088 target,
1089 })
1090 }
1091
1092 fn parse_goto(&mut self) -> Result<Stmt, ()> {
1093 let start = self.advance();
1094 if self.is_ident("loc") {
1095 self.advance();
1096 self.expect(TokenKind::Plus, "'+' after 'goto loc'")?;
1097 let offset = self.parse_expr()?;
1098 self.expect_statement_end("the goto target")?;
1099 return Ok(Stmt::Goto {
1100 label: None,
1101 span: Span::new(start.span.file, start.span.start, offset.span().end),
1102 offset: Some(offset),
1103 rule_start: false,
1104 });
1105 }
1106
1107 let label = self.expect_ident("a label or 'loc+...' after 'goto'")?;
1108 self.expect_statement_end("the goto target")?;
1109 let rule_start = label == "RULE_START";
1110 Ok(Stmt::Goto {
1111 label: (!rule_start).then_some(label),
1112 offset: None,
1113 rule_start,
1114 span: Span::new(
1115 start.span.file,
1116 start.span.start,
1117 self.tokens[self.pos - 1].span.end,
1118 ),
1119 })
1120 }
1121
1122 fn parse_label(&mut self) -> Result<Stmt, ()> {
1123 let name = self.advance();
1124 let colon = self.expect(TokenKind::Colon, "':' after a label")?;
1125 self.expect_statement_end("the label")?;
1126 Ok(Stmt::Label {
1127 name: name.text,
1128 span: Span::new(name.span.file, name.span.start, colon.span.end),
1129 })
1130 }
1131
1132 fn parse_expr_statement(&mut self) -> Result<Stmt, ()> {
1133 if self.peek_kind() == TokenKind::LParen {
1134 let save_pos = self.pos;
1135 let save_errors = self.errors.len();
1136 let start = self.advance().span;
1137 if let Ok(target) = self.parse_postfix()
1138 && self.peek_kind() == TokenKind::Assign
1139 {
1140 self.advance();
1141 let value = self.parse_expr()?;
1142 let end = self.expect(TokenKind::RParen, "')'")?.span.end;
1143 return Ok(Stmt::Assign {
1144 target,
1145 value,
1146 span: Span::new(start.file, start.start, end),
1147 });
1148 }
1149 self.pos = save_pos;
1150 self.errors.truncate(save_errors);
1151 }
1152 let start = self.peek().span;
1153 let expr = self.parse_expr()?;
1154 match self.peek_kind() {
1155 TokenKind::Assign => {
1156 self.advance();
1157 let value = self.parse_expr()?;
1158 let end = value.span().end;
1159 Ok(Stmt::Assign {
1160 target: expr,
1161 value,
1162 span: Span::new(start.file, start.start, end),
1163 })
1164 }
1165 TokenKind::PlusAssign
1166 | TokenKind::MinusAssign
1167 | TokenKind::StarAssign
1168 | TokenKind::SlashAssign
1169 | TokenKind::PercentAssign
1170 | TokenKind::DoubleStarAssign => {
1171 let op = match self.peek_kind() {
1172 TokenKind::PlusAssign => "+",
1173 TokenKind::MinusAssign => "-",
1174 TokenKind::StarAssign => "*",
1175 TokenKind::SlashAssign => "/",
1176 TokenKind::PercentAssign => "%",
1177 TokenKind::DoubleStarAssign => "**",
1178 _ => unreachable!(),
1179 }
1180 .to_string();
1181 self.advance();
1182 let rhs = self.parse_expr()?;
1183 let end = rhs.span().end;
1184 let value = Expr::Binary {
1185 op,
1186 left: Box::new(expr.clone()),
1187 right: Box::new(rhs),
1188 span: Span::new(start.file, start.start, end),
1189 };
1190 Ok(Stmt::Assign {
1191 target: expr,
1192 value,
1193 span: Span::new(start.file, start.start, end),
1194 })
1195 }
1196 TokenKind::Ident
1197 if matches!(self.peek().text.as_str(), "min" | "max")
1198 && self.peek_at(1).kind == TokenKind::Assign =>
1199 {
1200 let op = self.advance().text;
1201 self.advance();
1202 let rhs = self.parse_expr()?;
1203 let end = rhs.span().end;
1204 let value = Expr::Binary {
1205 op,
1206 left: Box::new(expr.clone()),
1207 right: Box::new(rhs),
1208 span: Span::new(start.file, start.start, end),
1209 };
1210 Ok(Stmt::Assign {
1211 target: expr,
1212 value,
1213 span: Span::new(start.file, start.start, end),
1214 })
1215 }
1216 TokenKind::Increment | TokenKind::Decrement => {
1217 let operator = self.advance();
1218 if !matches!(self.peek_kind(), TokenKind::Newline | TokenKind::Eof) {
1219 self.error_at_current(
1220 "postfix increment/decrement must be a standalone assignment".to_string(),
1221 );
1222 return Err(());
1223 }
1224 let operation = if operator.kind == TokenKind::Increment {
1225 "+"
1226 } else {
1227 "-"
1228 };
1229 let span = Span::new(start.file, start.start, operator.span.end);
1230 let value = Expr::Binary {
1231 op: operation.to_string(),
1232 left: Box::new(expr.clone()),
1233 right: Box::new(Expr::Number {
1234 value: 1.0,
1235 text: "1".to_string(),
1236 span: operator.span,
1237 }),
1238 span,
1239 };
1240 Ok(Stmt::Assign {
1241 target: expr,
1242 value,
1243 span,
1244 })
1245 }
1246 _ => {
1247 let end = expr.span().end;
1248 Ok(Stmt::Expr {
1249 expr,
1250 span: Span::new(start.file, start.start, end),
1251 })
1252 }
1253 }
1254 }
1255
1256 fn parse_if(&mut self) -> Result<Stmt, ()> {
1257 let start = self.advance();
1258 let line_indent = start.span.start.col;
1259 let condition = self.parse_expr()?;
1260 if self
1261 .expect(TokenKind::Colon, "':' after the if condition")
1262 .is_err()
1263 {
1264 return Err(());
1265 }
1266 let body = self.parse_colon_body(line_indent)?;
1267 let mut branches = vec![IfBranch { condition, body }];
1268 let mut r#else = None;
1269 loop {
1270 let save = self.pos;
1271 self.skip_newlines();
1272 if self.peek_kind() == TokenKind::Eof || self.peek().span.start.col > line_indent {
1273 self.pos = save;
1274 break;
1275 }
1276 if self.is_ident("elif") {
1277 let branch_start = self.advance();
1278 let condition = match self.parse_expr() {
1279 Ok(expr) => expr,
1280 Err(()) => return Err(()),
1281 };
1282 if self
1283 .expect(TokenKind::Colon, "':' after the elif condition")
1284 .is_err()
1285 {
1286 return Err(());
1287 }
1288 let body = self.parse_colon_body(branch_start.span.start.col)?;
1289 branches.push(IfBranch { condition, body });
1290 } else if self.is_ident("else") {
1291 let branch_start = self.advance();
1292 if self.is_ident("if") {
1293 self.advance();
1294 let condition = self.parse_expr()?;
1295 if self
1296 .expect(TokenKind::Colon, "':' after the else-if condition")
1297 .is_err()
1298 {
1299 return Err(());
1300 }
1301 let body = self.parse_colon_body(branch_start.span.start.col)?;
1302 branches.push(IfBranch { condition, body });
1303 continue;
1304 }
1305 if self.expect(TokenKind::Colon, "':' after `else`").is_err() {
1306 return Err(());
1307 }
1308 let body = self.parse_colon_body(branch_start.span.start.col)?;
1309 r#else = Some(body);
1310 break;
1311 } else {
1312 self.pos = save;
1313 break;
1314 }
1315 }
1316 Ok(Stmt::If {
1317 branches,
1318 r#else,
1319 span: start.span,
1320 })
1321 }
1322
1323 fn parse_colon_body(&mut self, line_indent: u32) -> Result<Vec<Stmt>, ()> {
1324 if matches!(self.peek_kind(), TokenKind::Newline | TokenKind::Eof) {
1325 let save = self.pos;
1326 self.skip_newlines();
1327 if self.peek_kind() != TokenKind::Eof && self.peek().span.start.col == line_indent {
1328 let statement = self.parse_statement()?;
1329 self.expect_statement_end("the inline statement")?;
1330 return Ok(vec![statement]);
1331 }
1332 self.pos = save;
1333 let body_indent = self.block_indent(line_indent).ok_or(())?;
1334 Ok(self.parse_block(body_indent))
1335 } else {
1336 let statement = self.parse_statement()?;
1337 self.expect_statement_end("the inline statement")?;
1338 Ok(vec![statement])
1339 }
1340 }
1341
1342 fn parse_for(&mut self) -> Result<Stmt, ()> {
1343 let start = self.advance();
1344 let variable = self.parse_postfix()?;
1345 if !self.is_ident("in") {
1346 self.error_at_current("expected `in` in the for statement".to_string());
1347 return Err(());
1348 }
1349 self.advance();
1350 let iterable = self.parse_expr()?;
1351 if self
1352 .expect(TokenKind::Colon, "':' after the for header")
1353 .is_err()
1354 {
1355 return Err(());
1356 }
1357 let line_indent = start.span.start.col;
1358 let body_indent = self.block_indent(line_indent).ok_or(())?;
1359 let body = self.parse_block(body_indent);
1360 Ok(Stmt::For {
1361 variable,
1362 iterable,
1363 body,
1364 span: start.span,
1365 })
1366 }
1367
1368 fn parse_while(&mut self) -> Result<Stmt, ()> {
1369 let start = self.advance();
1370 let condition = self.parse_expr()?;
1371 if self
1372 .expect(TokenKind::Colon, "':' after the while condition")
1373 .is_err()
1374 {
1375 return Err(());
1376 }
1377 let line_indent = start.span.start.col;
1378 let body_indent = self.block_indent(line_indent).ok_or(())?;
1379 let body = self.parse_block(body_indent);
1380 Ok(Stmt::While {
1381 condition,
1382 body,
1383 span: start.span,
1384 })
1385 }
1386
1387 fn parse_do_while(&mut self) -> Result<Stmt, ()> {
1388 let start = self.advance();
1389 if self.expect(TokenKind::Colon, "':' after `do`").is_err() {
1390 return Err(());
1391 }
1392 let body_indent = self.block_indent(start.span.start.col).ok_or(())?;
1393 let body = self.parse_block(body_indent);
1394 if !self.is_ident("while") {
1395 self.error_at_current("expected `while` after the do block".to_string());
1396 return Err(());
1397 }
1398 self.advance();
1399 let condition = self.parse_expr()?;
1400 if self.peek_kind() != TokenKind::Newline && self.peek_kind() != TokenKind::Eof {
1401 self.error_at_current("expected the end of the do-while condition".to_string());
1402 return Err(());
1403 }
1404 Ok(Stmt::DoWhile {
1405 condition,
1406 body,
1407 span: start.span,
1408 })
1409 }
1410
1411 fn parse_switch(&mut self) -> Result<Stmt, ()> {
1412 let start = self.advance();
1413 let value = self.parse_expr()?;
1414 if self
1415 .expect(TokenKind::Colon, "':' after the switch value")
1416 .is_err()
1417 {
1418 return Err(());
1419 }
1420 let body_indent = self.block_indent(start.span.start.col).ok_or(())?;
1421 let mut arms = Vec::new();
1422 loop {
1423 self.skip_newlines();
1424 if self.peek_kind() == TokenKind::Eof || self.peek().span.start.col < body_indent {
1425 break;
1426 }
1427 if self.peek().span.start.col != body_indent {
1428 self.error_at_current("unexpected indentation in switch".to_string());
1429 self.recover_line();
1430 continue;
1431 }
1432 if self.is_ident("case") {
1433 let case_start = self.advance();
1434 let case_value = self.parse_expr()?;
1435 if self
1436 .expect(TokenKind::Colon, "':' after the case value")
1437 .is_err()
1438 {
1439 return Err(());
1440 }
1441 let case_body_indent = self.block_indent(body_indent).ok_or(())?;
1442 let body = self.parse_block(case_body_indent);
1443 arms.push(SwitchArm::Case {
1444 value: case_value,
1445 body,
1446 span: case_start.span,
1447 });
1448 } else if self.is_ident("default") {
1449 let default_start = self.advance();
1450 if self
1451 .expect(TokenKind::Colon, "':' after `default`")
1452 .is_err()
1453 {
1454 return Err(());
1455 }
1456 let default_body_indent = self.block_indent(body_indent).ok_or(())?;
1457 arms.push(SwitchArm::Default {
1458 body: self.parse_block(default_body_indent),
1459 span: default_start.span,
1460 });
1461 if default_start.span.start.col != body_indent {
1462 self.error_at_current("invalid default indentation".to_string());
1463 return Err(());
1464 }
1465 } else {
1466 self.error_at_current("expected `case` or `default` in switch".to_string());
1467 self.recover_line();
1468 }
1469 }
1470 if arms.is_empty() {
1471 self.errors.push(OpyError::at(
1472 "parse-error",
1473 "switch must contain at least one case or default arm".to_string(),
1474 start.span,
1475 ));
1476 return Err(());
1477 }
1478 Ok(Stmt::Switch {
1479 value,
1480 arms,
1481 span: start.span,
1482 })
1483 }
1484
1485 fn parse_expr(&mut self) -> Result<Expr, ()> {
1488 self.parse_expr_inner(false)
1489 }
1490
1491 fn parse_expr_inner(&mut self, allow_multiline_conditional: bool) -> Result<Expr, ()> {
1492 self.skip_expression_newlines();
1493 let then_value = self.parse_or()?;
1494 let has_newline_before_conditional = self.tokens[..self.pos].iter().any(|token| {
1495 token.kind == TokenKind::Newline
1496 && token.span.file == then_value.span().file
1497 && token.span.start.line > then_value.span().end.line
1498 });
1499 if self.is_ident("if")
1500 && !self.inside_delimiter_group()
1501 && has_newline_before_conditional
1502 && self.peek().span.start.line != then_value.span().end.line
1503 {
1504 return Ok(then_value);
1505 }
1506 if allow_multiline_conditional && self.inside_delimiter_group() {
1507 self.skip_newlines();
1508 }
1509 if !self.is_ident("if") {
1510 return Ok(then_value);
1511 }
1512
1513 let conditional_start = self.pos;
1514 self.advance();
1515 self.skip_expression_newlines();
1516 let condition = self.parse_or()?;
1517 self.skip_expression_newlines();
1518 if !self.is_ident("else") {
1519 if self.peek_kind() == TokenKind::Colon {
1520 self.pos = conditional_start;
1521 return Ok(then_value);
1522 }
1523 self.error_at_current("expected `else` in conditional expression".to_string());
1524 return Err(());
1525 }
1526 self.advance();
1527 let else_value = self.parse_expr_inner(true)?;
1530 let span = Span::new(
1531 then_value.span().file,
1532 then_value.span().start,
1533 else_value.span().end,
1534 );
1535 Ok(Expr::Conditional {
1536 then_value: Box::new(then_value),
1537 condition: Box::new(condition),
1538 else_value: Box::new(else_value),
1539 span,
1540 })
1541 }
1542
1543 fn parse_or(&mut self) -> Result<Expr, ()> {
1544 self.skip_expression_newlines();
1545 let mut left = self.parse_and()?;
1546 loop {
1547 self.skip_expression_newlines();
1548 if !self.is_ident("or") {
1549 break;
1550 }
1551 self.advance();
1552 self.skip_expression_newlines();
1553 let right = self.parse_and()?;
1554 let span = Span::new(left.span().file, left.span().start, right.span().end);
1555 left = Expr::Binary {
1556 op: "or".to_string(),
1557 left: Box::new(left),
1558 right: Box::new(right),
1559 span,
1560 };
1561 }
1562 Ok(left)
1563 }
1564
1565 fn parse_and(&mut self) -> Result<Expr, ()> {
1566 self.skip_expression_newlines();
1567 let mut left = self.parse_not()?;
1568 loop {
1569 self.skip_expression_newlines();
1570 if !self.is_ident("and") {
1571 break;
1572 }
1573 self.advance();
1574 self.skip_expression_newlines();
1575 let right = self.parse_not()?;
1576 let span = Span::new(left.span().file, left.span().start, right.span().end);
1577 left = Expr::Binary {
1578 op: "and".to_string(),
1579 left: Box::new(left),
1580 right: Box::new(right),
1581 span,
1582 };
1583 }
1584 Ok(left)
1585 }
1586
1587 fn parse_not(&mut self) -> Result<Expr, ()> {
1588 self.skip_expression_newlines();
1589 if self.is_ident("not") {
1590 let start = self.advance();
1591 self.skip_expression_newlines();
1592 let operand = self.parse_not()?;
1593 let end = operand.span().end;
1594 return Ok(Expr::Unary {
1595 op: "not".to_string(),
1596 operand: Box::new(operand),
1597 span: Span::new(start.span.file, start.span.start, end),
1598 });
1599 }
1600 self.parse_comparison()
1601 }
1602
1603 fn parse_comparison(&mut self) -> Result<Expr, ()> {
1604 self.skip_expression_newlines();
1605 let mut left = self.parse_additive()?;
1606 loop {
1607 self.skip_expression_newlines();
1608 let op = match self.peek_kind() {
1609 TokenKind::Eq => "==",
1610 TokenKind::Ne => "!=",
1611 TokenKind::Lt => "<",
1612 TokenKind::Le => "<=",
1613 TokenKind::Gt => ">",
1614 TokenKind::Ge => ">=",
1615 _ if self.is_ident("in") => "in",
1616 _ if self.is_ident("not") && self.peek_at(1).text == "in" => "not in",
1617 _ => break,
1618 };
1619 self.advance();
1620 if op == "not in" {
1621 self.advance();
1622 }
1623 self.skip_expression_newlines();
1624 let right = self.parse_additive()?;
1625 let span = Span::new(left.span().file, left.span().start, right.span().end);
1626 left = Expr::Binary {
1627 op: op.to_string(),
1628 left: Box::new(left),
1629 right: Box::new(right),
1630 span,
1631 };
1632 }
1633 Ok(left)
1634 }
1635
1636 fn parse_additive(&mut self) -> Result<Expr, ()> {
1637 self.skip_expression_newlines();
1638 let mut left = self.parse_multiplicative()?;
1639 loop {
1640 self.skip_expression_newlines();
1641 if self.peek_kind() == TokenKind::Decrement
1642 && !matches!(self.peek_at(1).kind, TokenKind::Newline | TokenKind::Eof)
1643 {
1644 let operator = self.advance();
1645 let operand = self.parse_unary()?;
1646 let unary = Expr::Unary {
1647 op: "-".to_string(),
1648 span: Span::new(operator.span.file, operator.span.start, operand.span().end),
1649 operand: Box::new(operand),
1650 };
1651 let right = self.parse_multiplicative_tail(unary)?;
1652 let span = Span::new(left.span().file, left.span().start, right.span().end);
1653 left = Expr::Binary {
1654 op: "-".to_string(),
1655 left: Box::new(left),
1656 right: Box::new(right),
1657 span,
1658 };
1659 continue;
1660 }
1661 let op = match self.peek_kind() {
1662 TokenKind::Plus => "+",
1663 TokenKind::Minus => "-",
1664 _ => break,
1665 };
1666 self.advance();
1667 self.skip_expression_newlines();
1668 let right = self.parse_multiplicative()?;
1669 let span = Span::new(left.span().file, left.span().start, right.span().end);
1670 left = Expr::Binary {
1671 op: op.to_string(),
1672 left: Box::new(left),
1673 right: Box::new(right),
1674 span,
1675 };
1676 }
1677 Ok(left)
1678 }
1679
1680 fn parse_multiplicative(&mut self) -> Result<Expr, ()> {
1681 self.skip_expression_newlines();
1682 let left = self.parse_unary()?;
1683 self.parse_multiplicative_tail(left)
1684 }
1685
1686 fn parse_multiplicative_tail(&mut self, mut left: Expr) -> Result<Expr, ()> {
1687 loop {
1688 self.skip_expression_newlines();
1689 let op = match self.peek_kind() {
1690 TokenKind::Star => "*",
1691 TokenKind::Slash => "/",
1692 TokenKind::Percent => "%",
1693 _ => break,
1694 };
1695 self.advance();
1696 self.skip_expression_newlines();
1697 let right = self.parse_unary()?;
1698 let span = Span::new(left.span().file, left.span().start, right.span().end);
1699 left = Expr::Binary {
1700 op: op.to_string(),
1701 left: Box::new(left),
1702 right: Box::new(right),
1703 span,
1704 };
1705 }
1706 Ok(left)
1707 }
1708
1709 fn parse_unary(&mut self) -> Result<Expr, ()> {
1710 self.skip_expression_newlines();
1711 if matches!(self.peek_kind(), TokenKind::Minus | TokenKind::Decrement) {
1712 let start = self.advance();
1713 self.skip_expression_newlines();
1714 let operand = self.parse_unary()?;
1715 let end = operand.span().end;
1716 let unary = Expr::Unary {
1717 op: "-".to_string(),
1718 operand: Box::new(operand),
1719 span: Span::new(start.span.file, start.span.start, end),
1720 };
1721 if start.kind == TokenKind::Decrement {
1722 return Ok(Expr::Unary {
1723 op: "-".to_string(),
1724 operand: Box::new(unary),
1725 span: Span::new(start.span.file, start.span.start, end),
1726 });
1727 }
1728 return Ok(unary);
1729 }
1730 self.parse_power()
1731 }
1732
1733 fn parse_power(&mut self) -> Result<Expr, ()> {
1734 let base = self.parse_postfix()?;
1735 self.skip_expression_newlines();
1736 if self.peek_kind() == TokenKind::DoubleStar {
1737 self.advance();
1738 self.skip_expression_newlines();
1740 let exponent = self.parse_unary()?;
1741 let span = Span::new(base.span().file, base.span().start, exponent.span().end);
1742 return Ok(Expr::Binary {
1743 op: "**".to_string(),
1744 left: Box::new(base),
1745 right: Box::new(exponent),
1746 span,
1747 });
1748 }
1749 Ok(base)
1750 }
1751
1752 fn parse_postfix(&mut self) -> Result<Expr, ()> {
1753 let mut base = self.parse_primary()?;
1754 loop {
1755 self.skip_expression_newlines();
1756 match self.peek_kind() {
1757 TokenKind::LParen => {
1758 let mut args = Vec::new();
1759 self.parse_call_args(&mut args)?;
1760 let end = self.tokens[self.pos.saturating_sub(1)].span.end;
1761 base = match base {
1762 Expr::Name { name, span } => Expr::Call {
1763 name,
1764 args,
1765 span: Span::new(span.file, span.start, end),
1766 },
1767 Expr::Member {
1768 receiver,
1769 member,
1770 span,
1771 ..
1772 } => Expr::ReceiverCall {
1773 receiver,
1774 name: member,
1775 args,
1776 span: Span::new(span.file, span.start, end),
1777 },
1778 _other => {
1779 self.errors.push(OpyError::at(
1780 "parse-error",
1781 "cannot call this expression".to_string(),
1782 self.peek().span,
1783 ));
1784 return Err(());
1785 }
1786 };
1787 }
1788 TokenKind::LBracket => {
1789 self.advance();
1790 let index = self.parse_expr()?;
1791 if self.peek_kind() == TokenKind::Colon {
1792 self.advance();
1793 let maximum = self.parse_expr()?;
1794 let end = self.expect(TokenKind::RBracket, "']'")?.span.end;
1795 let Expr::Name { name, span } = &base else {
1796 self.error_at_current(
1797 "a range type must start with a type name".to_string(),
1798 );
1799 return Err(());
1800 };
1801 base = Expr::Type {
1802 name: name.clone(),
1803 args: vec![index, maximum],
1804 span: Span::new(span.file, span.start, end),
1805 };
1806 continue;
1807 }
1808 let end = match self.expect(TokenKind::RBracket, "']'") {
1809 Ok(token) => token.span.end,
1810 Err(()) => return Err(()),
1811 };
1812 let span = Span::new(base.span().file, base.span().start, end);
1813 base = Expr::Index {
1814 array: Box::new(base),
1815 index: Box::new(index),
1816 span,
1817 };
1818 }
1819 TokenKind::Dot => {
1820 self.advance();
1821 let member_token = self.peek().clone();
1822 let member = match self.peek_kind() {
1823 TokenKind::Ident | TokenKind::Number => self.advance().text,
1824 _ => {
1825 self.error_at_current("expected a member name after '.'".to_string());
1826 return Err(());
1827 }
1828 };
1829 let member_span = member_token.span;
1830 let end = member_span.end;
1831 let span = Span::new(base.span().file, base.span().start, end);
1832 base = Expr::Member {
1833 receiver: Box::new(base),
1834 member,
1835 member_span,
1836 span,
1837 };
1838 }
1839 _ => break,
1840 }
1841 }
1842 Ok(base)
1843 }
1844
1845 fn parse_event_args(&mut self, args: &mut Vec<Expr>) -> Result<(), ()> {
1848 self.expect(TokenKind::LParen, "'('")?;
1849 self.skip_newlines();
1850 if self.peek_kind() == TokenKind::RParen {
1851 self.advance();
1852 return Ok(());
1853 }
1854 loop {
1855 let expr = self.parse_expr()?;
1856 if self.peek_kind() == TokenKind::Assign {
1857 self.error_at_current("keyword arguments are not valid in @Event".to_string());
1858 return Err(());
1859 }
1860 args.push(expr);
1861 self.skip_newlines();
1862 if self.peek_kind() == TokenKind::Comma {
1863 self.advance();
1864 self.skip_newlines();
1865 if self.peek_kind() == TokenKind::RParen {
1866 break;
1867 }
1868 } else {
1869 break;
1870 }
1871 }
1872 self.expect(TokenKind::RParen, "')'")?;
1873 Ok(())
1874 }
1875
1876 fn parse_call_args(&mut self, args: &mut Vec<CallArg>) -> Result<(), ()> {
1877 self.expect(TokenKind::LParen, "'('")?;
1878 self.skip_newlines();
1879 if self.peek_kind() == TokenKind::RParen {
1880 self.advance();
1881 return Ok(());
1882 }
1883 loop {
1884 match self.parse_expr() {
1885 Ok(expr) => {
1886 if self.peek_kind() == TokenKind::Assign {
1891 let Expr::Name { name, span } = expr else {
1892 self.error_at_current(
1893 "expected a keyword name before '=' in this call".to_string(),
1894 );
1895 return Err(());
1896 };
1897 self.advance();
1898 let value = match self.parse_expr() {
1899 Ok(value) => value,
1900 Err(()) => return Err(()),
1901 };
1902 args.push(CallArg {
1903 keyword: Some((name, span)),
1904 value,
1905 });
1906 } else {
1907 args.push(CallArg {
1908 keyword: None,
1909 value: expr,
1910 });
1911 }
1912 }
1913 Err(()) => return Err(()),
1914 }
1915 self.skip_newlines();
1916 if self.peek_kind() == TokenKind::Comma {
1917 self.advance();
1918 self.skip_newlines();
1919 if self.peek_kind() == TokenKind::RParen {
1920 break;
1921 }
1922 } else {
1923 break;
1924 }
1925 }
1926 self.expect(TokenKind::RParen, "')'")?;
1927 Ok(())
1928 }
1929
1930 fn parse_primary(&mut self) -> Result<Expr, ()> {
1931 let token = self.peek();
1932 match token.kind {
1933 TokenKind::Number => {
1934 let token = self.advance();
1935 let value = if let Some(hex) = token
1936 .text
1937 .strip_prefix("0x")
1938 .or_else(|| token.text.strip_prefix("0X"))
1939 {
1940 u64::from_str_radix(hex, 16).map_or(f64::NAN, |value| value as f64)
1941 } else {
1942 token.text.parse().unwrap_or(f64::NAN)
1943 };
1944 Ok(Expr::Number {
1945 value,
1946 text: token.text.clone(),
1947 span: token.span,
1948 })
1949 }
1950 TokenKind::String => self.parse_string_literal(),
1951 TokenKind::Ident => {
1952 let token = self.advance();
1953 if token.text == "lambda" {
1954 return self.parse_lambda(token.span);
1955 }
1956 if is_string_modifier(&token.text) && self.peek_kind() == TokenKind::String {
1957 let string = self.advance();
1958 let (format_text, interpolations) = if token.text == "f" {
1959 let raw = string.raw.as_deref().unwrap_or(&string.text);
1960 let (format_text, interpolations) =
1961 self.parse_f_string(raw, string.span)?;
1962 (Some(format_text), interpolations)
1963 } else {
1964 (None, Vec::new())
1965 };
1966 return Ok(Expr::StringModifier {
1967 modifier: token.text.chars().next().unwrap_or_default(),
1968 value: string.text,
1969 format_text,
1970 interpolations,
1971 span: Span::new(token.span.file, token.span.start, string.span.end),
1972 });
1973 }
1974 match token.text.as_str() {
1975 "true" => Ok(Expr::Bool {
1976 value: true,
1977 span: token.span,
1978 }),
1979 "false" => Ok(Expr::Bool {
1980 value: false,
1981 span: token.span,
1982 }),
1983 "None" | "null" => Ok(Expr::Null { span: token.span }),
1984 _ => Ok(Expr::Name {
1985 name: token.text.clone(),
1986 span: token.span,
1987 }),
1988 }
1989 }
1990 TokenKind::LParen => {
1991 self.advance();
1992 self.skip_expression_newlines();
1993 let expr = self.parse_expr()?;
1994 self.skip_expression_newlines();
1995 self.expect(TokenKind::RParen, "')'")?;
1996 Ok(expr)
1997 }
1998 TokenKind::LBracket => {
1999 let open = self.advance();
2000 let mut elements = Vec::new();
2001 self.skip_newlines();
2002 if self.peek_kind() == TokenKind::RBracket {
2003 let end = self.advance().span.end;
2004 return Ok(Expr::Array {
2005 elements,
2006 span: Span::new(open.span.file, open.span.start, end),
2007 });
2008 }
2009 let first = self.parse_expr()?;
2010 if self.is_ident("for") {
2011 self.advance();
2012 let variable_token =
2013 self.expect(TokenKind::Ident, "a comprehension variable")?;
2014 self.skip_newlines();
2015 let index = if self.peek_kind() == TokenKind::Comma {
2016 self.advance();
2017 self.skip_newlines();
2018 let index = self.expect(TokenKind::Ident, "a comprehension index")?;
2019 Some((index.text, index.span))
2020 } else {
2021 None
2022 };
2023 self.skip_newlines();
2024 if !self.is_ident("in") {
2025 self.error_at_current("expected `in` in list comprehension".to_string());
2026 return Err(());
2027 }
2028 self.advance();
2029 self.skip_newlines();
2030 let iterable = self.parse_or()?;
2031 self.skip_newlines();
2032 let condition = if self.is_ident("if") {
2033 self.advance();
2034 self.skip_newlines();
2035 Some(Box::new(self.parse_or()?))
2036 } else {
2037 None
2038 };
2039 self.skip_newlines();
2040 let end = self.expect(TokenKind::RBracket, "']'")?.span.end;
2041 return Ok(Expr::Comprehension {
2042 element: Box::new(first),
2043 variable: variable_token.text,
2044 variable_span: variable_token.span,
2045 index,
2046 iterable: Box::new(iterable),
2047 condition,
2048 span: Span::new(open.span.file, open.span.start, end),
2049 });
2050 }
2051 elements.push(first);
2052 loop {
2053 self.skip_newlines();
2054 if self.peek_kind() == TokenKind::Comma {
2055 self.advance();
2056 self.skip_newlines();
2057 if self.peek_kind() == TokenKind::RBracket {
2058 break;
2059 }
2060 elements.push(self.parse_expr()?);
2061 } else {
2062 break;
2063 }
2064 }
2065 let end = match self.expect(TokenKind::RBracket, "']'") {
2066 Ok(token) => token.span.end,
2067 Err(()) => return Err(()),
2068 };
2069 Ok(Expr::Array {
2070 elements,
2071 span: Span::new(open.span.file, open.span.start, end),
2072 })
2073 }
2074 TokenKind::LBrace => self.parse_dict(),
2075 _ => {
2076 self.error_at_current(format!("expected an expression but found '{}'", token.text));
2077 Err(())
2078 }
2079 }
2080 }
2081
2082 fn parse_string_literal(&mut self) -> Result<Expr, ()> {
2089 let first = self.advance();
2090 let mut value = first.text.clone();
2091 let mut end = first.span.end;
2092 loop {
2093 let saved = self.pos;
2094 if self.inside_delimiter_group() {
2095 self.skip_newlines();
2096 }
2097 if self.peek_kind() != TokenKind::String || self.peek().span.file != first.span.file {
2098 self.pos = saved;
2099 break;
2100 }
2101 let next = self.advance();
2102 value.push_str(&next.text);
2103 end = next.span.end;
2104 }
2105 Ok(Expr::String {
2106 value,
2107 span: Span::new(first.span.file, first.span.start, end),
2108 })
2109 }
2110
2111 fn inside_delimiter_group(&self) -> bool {
2116 let mut depth = 0usize;
2117 for token in &self.tokens[..self.pos] {
2118 match token.kind {
2119 TokenKind::LParen | TokenKind::LBracket | TokenKind::LBrace => depth += 1,
2120 TokenKind::RParen | TokenKind::RBracket | TokenKind::RBrace => {
2121 depth = depth.saturating_sub(1)
2122 }
2123 _ => {}
2124 }
2125 }
2126 depth != 0
2127 }
2128
2129 fn parse_dict(&mut self) -> Result<Expr, ()> {
2130 let open = self.advance();
2131 let mut entries = Vec::new();
2132 self.skip_newlines();
2133 if self.peek_kind() == TokenKind::RBrace {
2134 let end = self.advance().span.end;
2135 return Ok(Expr::Dict {
2136 entries,
2137 span: Span::new(open.span.file, open.span.start, end),
2138 });
2139 }
2140 loop {
2141 let key = self.parse_expr()?;
2142 self.expect(TokenKind::Colon, "':' in a dictionary entry")?;
2143 let value = self.parse_expr()?;
2144 let span = Span::new(key.span().file, key.span().start, value.span().end);
2145 entries.push(DictEntry { key, value, span });
2146 self.skip_newlines();
2147 if self.peek_kind() == TokenKind::Comma {
2148 self.advance();
2149 self.skip_newlines();
2150 if self.peek_kind() == TokenKind::RBrace {
2151 break;
2152 }
2153 } else {
2154 break;
2155 }
2156 }
2157 let end = self.expect(TokenKind::RBrace, "'}'")?.span.end;
2158 Ok(Expr::Dict {
2159 entries,
2160 span: Span::new(open.span.file, open.span.start, end),
2161 })
2162 }
2163
2164 fn parse_lambda(&mut self, start: Span) -> Result<Expr, ()> {
2165 let mut params = Vec::new();
2166 loop {
2167 let param = self.expect(TokenKind::Ident, "a lambda parameter")?;
2168 params.push((param.text, param.span));
2169 if self.peek_kind() == TokenKind::Comma {
2170 self.advance();
2171 } else {
2172 break;
2173 }
2174 }
2175 self.expect(TokenKind::Colon, "':' after lambda parameters")?;
2176 let body = self.parse_expr()?;
2177 Ok(Expr::Lambda {
2178 params,
2179 body: Box::new(body.clone()),
2180 span: Span::new(start.file, start.start, body.span().end),
2181 })
2182 }
2183
2184 fn parse_f_string(&mut self, raw: &str, string_span: Span) -> Result<(String, Vec<Expr>), ()> {
2189 let chars: Vec<char> = raw.chars().collect();
2190 let mut text = String::new();
2191 let mut interpolations = Vec::new();
2192 let mut index = 0;
2193 while index < chars.len() {
2194 match chars[index] {
2195 '{' if chars.get(index + 1) == Some(&'{') => {
2196 text.push_str("{{");
2197 index += 2;
2198 }
2199 '}' if chars.get(index + 1) == Some(&'}') => {
2200 text.push_str("}}");
2201 index += 2;
2202 }
2203 '{' => {
2204 let end = self.find_f_string_end(&chars, index + 1);
2205 let Some(end) = end else {
2206 self.errors.push(OpyError::at(
2207 "parse-error",
2208 "unterminated f-string interpolation".to_string(),
2209 string_span,
2210 ));
2211 return Err(());
2212 };
2213 let expression: String = chars[index + 1..end].iter().collect();
2214 if expression.trim().is_empty() {
2215 self.errors.push(OpyError::at(
2216 "parse-error",
2217 "f-string interpolation cannot be empty".to_string(),
2218 Span::new(
2219 string_span.file,
2220 Position::new(
2221 string_span.start.line,
2222 string_span.start.col + index as u32 + 1,
2223 ),
2224 Position::new(
2225 string_span.start.line,
2226 string_span.start.col + end as u32 + 1,
2227 ),
2228 ),
2229 ));
2230 return Err(());
2231 }
2232 let origin = Position::new(
2233 string_span.start.line,
2234 string_span.start.col + index as u32 + 1,
2235 );
2236 let parsed = parse_expression_fragment(&expression, string_span.file, origin)
2237 .map_err(|error| {
2238 self.errors.push(error);
2239 });
2240 let Ok(parsed) = parsed else {
2241 return Err(());
2242 };
2243 text.push_str(&format!("{{{}}}", interpolations.len()));
2244 interpolations.push(parsed);
2245 index = end + 1;
2246 }
2247 '}' => {
2248 self.errors.push(OpyError::at(
2249 "parse-error",
2250 "single '}' is not valid in an f-string".to_string(),
2251 string_span,
2252 ));
2253 return Err(());
2254 }
2255 '\\' if index + 1 < chars.len() => {
2256 text.push(decode_string_escape(chars[index + 1]));
2257 index += 2;
2258 }
2259 character => {
2260 text.push(character);
2261 index += 1;
2262 }
2263 }
2264 }
2265 Ok((text, interpolations))
2266 }
2267
2268 fn find_f_string_end(&self, chars: &[char], start: usize) -> Option<usize> {
2269 let mut nested_braces = 0;
2270 let mut quote = None;
2271 let mut escaped = false;
2272 for (index, character) in chars.iter().enumerate().skip(start) {
2273 if escaped {
2274 escaped = false;
2275 continue;
2276 }
2277 if *character == '\\' && quote.is_some() {
2278 escaped = true;
2279 continue;
2280 }
2281 if let Some(active_quote) = quote {
2282 if *character == active_quote {
2283 quote = None;
2284 }
2285 continue;
2286 }
2287 match character {
2288 '"' | '\'' => quote = Some(*character),
2289 '{' => nested_braces += 1,
2290 '}' if nested_braces == 0 => return Some(index),
2291 '}' => nested_braces -= 1,
2292 _ => {}
2293 }
2294 }
2295 None
2296 }
2297}
2298
2299pub(crate) fn parse_expression_fragment(
2302 text: &str,
2303 file: u32,
2304 origin: Position,
2305) -> Result<Expr, OpyError> {
2306 let mut tokens = crate::lexer::lex(crate::lexer::LexInput {
2307 file_id: file,
2308 text,
2309 })?;
2310 for token in &mut tokens {
2311 token.span = shift_span(token.span, origin);
2312 }
2313 let mut parser = Parser {
2314 tokens: &tokens,
2315 pos: 0,
2316 allow_macro_redeclaration: false,
2317 errors: Vec::new(),
2318 };
2319 let expression = parser.parse_expr().map_err(|()| {
2320 parser.errors.first().cloned().unwrap_or_else(|| {
2321 OpyError::at(
2322 "parse-error",
2323 "invalid f-string expression",
2324 Span::new(file, origin, origin),
2325 )
2326 })
2327 })?;
2328 if parser.peek_kind() != TokenKind::Eof {
2329 parser.error_at_current("unexpected tokens in f-string interpolation".to_string());
2330 }
2331 parser.errors.into_iter().next().map_or(Ok(expression), Err)
2332}
2333
2334fn shift_span(span: Span, origin: Position) -> Span {
2335 fn shift(position: Position, origin: Position) -> Position {
2336 Position::new(
2337 origin.line + position.line.saturating_sub(1),
2338 if position.line == 1 {
2339 origin.col + position.col.saturating_sub(1)
2340 } else {
2341 position.col
2342 },
2343 )
2344 }
2345 Span::new(
2346 span.file,
2347 shift(span.start, origin),
2348 shift(span.end, origin),
2349 )
2350}
2351
2352fn decode_string_escape(character: char) -> char {
2353 match character {
2354 'n' => '\n',
2355 't' => '\t',
2356 'r' => '\r',
2357 '\\' => '\\',
2358 '"' => '"',
2359 '\'' => '\'',
2360 other => other,
2361 }
2362}
2363
2364fn is_string_modifier(text: &str) -> bool {
2365 matches!(text, "f" | "w" | "l" | "b" | "c" | "t")
2366}
2367
2368#[cfg(test)]
2369mod tests {
2370 use super::*;
2371 use crate::lexer::{LexInput, lex};
2372
2373 fn parse_ok(text: &str) -> Program {
2374 let tokens = lex(LexInput { file_id: 0, text }).unwrap();
2375 let output = parse(&tokens);
2376 assert!(
2377 output.errors.is_empty(),
2378 "unexpected errors: {:?}",
2379 output.errors
2380 );
2381 output.program.unwrap()
2382 }
2383
2384 fn parse_err(text: &str) -> Vec<OpyError> {
2385 let tokens = lex(LexInput { file_id: 0, text }).unwrap();
2386 parse(&tokens).errors
2387 }
2388
2389 #[test]
2390 fn parses_basic_rule() {
2391 let program = parse_ok("rule \"setup\":\n @Event global\n disableInspector()\n");
2392 assert_eq!(program.rules.len(), 1);
2393 let RuleEntry::Rule(rule) = &program.rules[0] else {
2394 panic!("expected rule");
2395 };
2396 assert_eq!(rule.name, "setup");
2397 assert_eq!(rule.event.name, "global");
2398 assert_eq!(rule.actions.len(), 1);
2399 }
2400
2401 #[test]
2402 fn parses_power_augmented_assignment() {
2403 let program =
2406 parse_ok("globalvar a\nrule \"r\":\n @Event global\n a = 2\n a **= 3\n");
2407 let RuleEntry::Rule(rule) = &program.rules[0] else {
2408 panic!("expected rule");
2409 };
2410 let Stmt::Assign {
2411 value,
2412 target: assigned_target,
2413 ..
2414 } = &rule.actions[1]
2415 else {
2416 panic!("expected an assignment");
2417 };
2418 let Expr::Binary {
2419 op, left, right, ..
2420 } = value
2421 else {
2422 panic!("expected a binary modification, got {value:?}");
2423 };
2424 assert_eq!(op, "**");
2425 assert!(matches!(&**left, Expr::Name { .. }));
2426 assert!(matches!(
2427 assigned_target,
2428 Expr::Name { name, .. } if name == "a"
2429 ));
2430 assert!(matches!(right.as_ref(), Expr::Number { .. }));
2431 }
2432
2433 #[test]
2434 fn parses_postfix_increment_and_decrement_as_modifications() {
2435 let program =
2436 parse_ok("globalvar value\nrule \"r\":\n @Event global\n value++\n value--\n");
2437 let RuleEntry::Rule(rule) = &program.rules[0] else {
2438 panic!("expected a rule");
2439 };
2440 for (statement, expected_op) in [(&rule.actions[0], "+"), (&rule.actions[1], "-")] {
2441 let Stmt::Assign { target, value, .. } = statement else {
2442 panic!("expected a postfix assignment");
2443 };
2444 let Expr::Binary {
2445 op, left, right, ..
2446 } = value
2447 else {
2448 panic!("expected a synthetic modification value");
2449 };
2450 assert_eq!(op, expected_op);
2451 let Expr::Name {
2452 name: left_name, ..
2453 } = left.as_ref()
2454 else {
2455 panic!("expected the target to be the modification's left operand");
2456 };
2457 let Expr::Name {
2458 name: target_name, ..
2459 } = target
2460 else {
2461 panic!("expected a name target");
2462 };
2463 assert_eq!(left_name, target_name);
2464 assert!(
2465 matches!(right.as_ref(), Expr::Number { value, text, .. } if *value == 1.0 && text == "1")
2466 );
2467 }
2468 }
2469
2470 #[test]
2471 fn rejects_prefix_increment_and_embedded_postfix_forms() {
2472 for source in [
2473 "globalvar value\nrule \"r\":\n @Event global\n ++value\n",
2474 "globalvar value\nrule \"r\":\n @Event global\n value++++\n",
2475 ] {
2476 let errors = parse_err(source);
2477 assert!(!errors.is_empty());
2478 assert!(errors.iter().all(|error| error.code == "parse-error"));
2479 assert!(errors.iter().all(|error| error.span.is_some()));
2480 }
2481 }
2482
2483 #[test]
2484 fn preserves_consecutive_unary_minus_expressions() {
2485 let source = concat!(
2486 "globalvar value = 0\n",
2487 "globalvar B = 1\n",
2488 "rule \"r\":\n",
2489 " @Event global\n",
2490 " value = --1\n",
2491 " value = --B\n",
2492 " value = B--1\n",
2493 );
2494 parse_ok(source);
2495 }
2496
2497 #[test]
2498 fn parses_control_flow() {
2499 let program = parse_ok(
2500 "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",
2501 );
2502 let RuleEntry::Rule(rule) = &program.rules[0] else {
2503 panic!();
2504 };
2505 assert!(matches!(rule.actions[0], Stmt::For { .. }));
2506 let Stmt::For { body, .. } = &rule.actions[0] else {
2507 panic!();
2508 };
2509 let Stmt::If {
2510 branches, r#else, ..
2511 } = &body[0]
2512 else {
2513 panic!();
2514 };
2515 assert_eq!(branches.len(), 2);
2516 assert!(r#else.is_some());
2517 let Stmt::While { body, .. } = &rule.actions[1] else {
2518 panic!();
2519 };
2520 assert_eq!(body.len(), 2);
2521 }
2522
2523 #[test]
2524 fn parses_source_statement_surface() {
2525 let program = parse_ok(concat!(
2526 "globalvar value\n",
2527 "rule \"r\":\n",
2528 " @Event global\n",
2529 " del value[1]\n",
2530 " value min= 2\n",
2531 " value max= 3\n",
2532 " while value < 4:\n",
2533 " continue\n",
2534 " goto RULE_START\n",
2535 " goto target\n",
2536 " goto loc + value\n",
2537 " target:\n",
2538 ));
2539 let RuleEntry::Rule(rule) = &program.rules[0] else {
2540 panic!("expected rule");
2541 };
2542 assert!(matches!(rule.actions[0], Stmt::Delete { .. }));
2543 for (statement, expected) in [(&rule.actions[1], "min"), (&rule.actions[2], "max")] {
2544 let Stmt::Assign { value, .. } = statement else {
2545 panic!("expected augmented assignment");
2546 };
2547 assert!(matches!(value, Expr::Binary { op, .. } if op == expected));
2548 }
2549 let Stmt::While { body, .. } = &rule.actions[3] else {
2550 panic!("expected while");
2551 };
2552 assert!(matches!(body.as_slice(), [Stmt::Continue { .. }]));
2553 assert!(matches!(
2554 &rule.actions[4],
2555 Stmt::Goto {
2556 label: None,
2557 offset: None,
2558 rule_start: true,
2559 ..
2560 }
2561 ));
2562 assert!(matches!(
2563 &rule.actions[5],
2564 Stmt::Goto {
2565 label: Some(label),
2566 offset: None,
2567 rule_start: false,
2568 ..
2569 } if label == "target"
2570 ));
2571 assert!(matches!(
2572 &rule.actions[6],
2573 Stmt::Goto {
2574 label: None,
2575 offset: Some(_),
2576 rule_start: false,
2577 ..
2578 }
2579 ));
2580 assert!(matches!(&rule.actions[7], Stmt::Label { name, .. } if name == "target"));
2581 }
2582
2583 #[test]
2584 fn rejects_invalid_source_statement_forms() {
2585 for source in [
2586 "rule \"r\":\n @Event global\n del value\n",
2587 "rule \"r\":\n @Event global\n goto\n",
2588 "rule \"r\":\n @Event global\n goto loc\n",
2589 "rule \"r\":\n @Event global\n goto target extra\n",
2590 "rule \"r\":\n @Event global\n continue now\n",
2591 "rule \"r\":\n @Event global\n A = 1; A = 2\n",
2592 ] {
2593 let errors = parse_err(source);
2594 assert!(!errors.is_empty(), "invalid form parsed: {source}");
2595 assert!(errors.iter().all(|error| error.code == "parse-error"));
2596 assert!(errors.iter().all(|error| error.span.is_some()));
2597 }
2598 }
2599
2600 #[test]
2601 fn parses_syntax_constructs() {
2602 let program = parse_ok(
2603 "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",
2604 );
2605 let RuleEntry::Rule(rule) = &program.rules[0] else {
2606 panic!("expected rule");
2607 };
2608 assert!(matches!(rule.actions[0], Stmt::Switch { .. }));
2609 assert!(matches!(rule.actions[1], Stmt::Assign { .. }));
2610 }
2611
2612 #[test]
2613 fn rejects_incomplete_do_while_and_dictionary_entries() {
2614 let errors = parse_err(
2615 "rule \"r\":\n @Event global\n do:\n pass\n while\n x = {\"x\"}\n",
2616 );
2617 assert!(!errors.is_empty());
2618 assert!(errors.iter().all(|error| error.code == "parse-error"));
2619 }
2620
2621 #[test]
2622 fn parses_explicit_enum_member_values() {
2623 let program = parse_ok("enum EventType:\n BUFF = 0\n DEBUFF\n MECH = 2\n");
2624 let Decl::Enum { members, .. } = &program.declarations[0] else {
2625 panic!("expected enum");
2626 };
2627 assert_eq!(
2628 members
2629 .iter()
2630 .map(|(name, _)| name.as_str())
2631 .collect::<Vec<_>>(),
2632 ["BUFF", "DEBUFF", "MECH"]
2633 );
2634 }
2635
2636 #[test]
2637 fn parses_multi_line_array() {
2638 let program = parse_ok(
2639 "globalvar p\nrule \"r\":\n @Event global\n p = [\n vect(1, 0, 0),\n vect(2, 0, 0),\n ]\n",
2640 );
2641 let RuleEntry::Rule(rule) = &program.rules[0] else {
2642 panic!();
2643 };
2644 let Stmt::Assign { value, .. } = &rule.actions[0] else {
2645 panic!();
2646 };
2647 let Expr::Array { elements, .. } = value else {
2648 panic!("expected array, got {value:?}");
2649 };
2650 assert_eq!(elements.len(), 2);
2651 }
2652
2653 #[test]
2654 fn missing_colon_is_a_structured_error() {
2655 let errors = parse_err("rule \"x\"\n @Event global\n");
2656 assert!(!errors.is_empty());
2657 assert_eq!(errors[0].code, "parse-error");
2658 assert!(errors[0].span.is_some());
2659 }
2660
2661 #[test]
2662 fn def_and_macro_parse() {
2663 let program = parse_ok(
2664 "subroutine showStatus\n\nmacro VERSION = \"1.4.3\"\n\ndef showStatus():\n print(\"hi\")\n\nmacro double(value):\n value + value\n",
2665 );
2666 assert_eq!(program.declarations.len(), 3);
2667 assert!(matches!(program.declarations[1], Decl::Constant { .. }));
2668 assert!(matches!(program.declarations[2], Decl::Macro { .. }));
2669 let Decl::Macro { args, body, .. } = &program.declarations[2] else {
2670 panic!();
2671 };
2672 assert_eq!(args, &vec!["value".to_string()]);
2673 assert_eq!(body.len(), 1);
2674 }
2675
2676 #[test]
2677 fn macro_and_enum_redeclarations_are_checked_at_ast_surfaces() {
2678 let text = "enum Kind:\n First\n First\nmacro helper():\n pass\nmacro helper():\n pass\n";
2679 let errors = parse_err(text);
2680 assert_eq!(
2681 errors
2682 .iter()
2683 .filter(|error| error.code == "macro-redeclaration")
2684 .count(),
2685 2
2686 );
2687
2688 let tokens = lex(LexInput { file_id: 0, text }).unwrap();
2689 let output = parse_with_options(&tokens, true);
2690 assert!(
2691 output.errors.is_empty(),
2692 "unexpected errors: {:?}",
2693 output.errors
2694 );
2695 assert!(output.program.is_some());
2696 }
2697
2698 #[test]
2699 fn multiple_errors_are_reported() {
2700 let errors =
2701 parse_err("rule \"a\"\n bad statement here\nrule \"b\"\n @Event global\n");
2702 assert!(!errors.is_empty());
2703 }
2704
2705 #[test]
2706 fn precedence_parses_python_like() {
2707 let program = parse_ok("globalvar x\nrule \"r\":\n @Event global\n x = 1 + 2 * 3\n");
2708 let RuleEntry::Rule(rule) = &program.rules[0] else {
2709 panic!();
2710 };
2711 let Stmt::Assign { value, .. } = &rule.actions[0] else {
2712 panic!();
2713 };
2714 let Expr::Binary {
2715 op, left, right, ..
2716 } = value
2717 else {
2718 panic!();
2719 };
2720 assert_eq!(op, "+");
2721 let Expr::Binary { op: inner, .. } = right.as_ref() else {
2722 panic!();
2723 };
2724 assert_eq!(inner, "*");
2725 assert!(matches!(left.as_ref(), Expr::Number { .. }));
2726 }
2727
2728 #[test]
2729 fn parses_right_associative_conditional_expressions() {
2730 let program = parse_ok(
2731 "rule \"r\":\n @Event global\n debug(1 if true else 2 if false else 3)\n",
2732 );
2733 let RuleEntry::Rule(rule) = &program.rules[0] else {
2734 panic!("expected a rule");
2735 };
2736 let Stmt::Expr { expr, .. } = &rule.actions[0] else {
2737 panic!("expected an expression statement");
2738 };
2739 let Expr::Call { args, .. } = expr else {
2740 panic!("expected a call");
2741 };
2742 let Expr::Conditional {
2743 then_value,
2744 condition,
2745 else_value,
2746 span,
2747 } = &args[0].value
2748 else {
2749 panic!("expected a conditional expression");
2750 };
2751 assert!(matches!(then_value.as_ref(), Expr::Number { value, .. } if *value == 1.0));
2752 assert!(matches!(condition.as_ref(), Expr::Bool { value: true, .. }));
2753 assert!(matches!(
2754 else_value.as_ref(),
2755 Expr::Conditional { then_value, condition, else_value, .. }
2756 if matches!(then_value.as_ref(), Expr::Number { value, .. } if *value == 2.0)
2757 && matches!(condition.as_ref(), Expr::Bool { value: false, .. })
2758 && matches!(else_value.as_ref(), Expr::Number { value, .. } if *value == 3.0)
2759 ));
2760 assert_eq!(span.start.line, 3);
2761 assert_eq!(span.start.col, 11);
2762 }
2763
2764 #[test]
2765 fn parses_parenthesized_nested_conditional_and_rejects_missing_else() {
2766 let program = parse_ok(
2767 "rule \"r\":\n @Event global\n debug((1 if true else 2) if false else 3)\n",
2768 );
2769 let RuleEntry::Rule(rule) = &program.rules[0] else {
2770 panic!("expected a rule");
2771 };
2772 let Stmt::Expr { expr, .. } = &rule.actions[0] else {
2773 panic!("expected an expression statement");
2774 };
2775 let Expr::Call { args, .. } = expr else {
2776 panic!("expected a call");
2777 };
2778 assert!(matches!(
2779 &args[0].value,
2780 Expr::Conditional {
2781 then_value,
2782 condition,
2783 else_value,
2784 ..
2785 } if matches!(then_value.as_ref(), Expr::Conditional { .. })
2786 && matches!(condition.as_ref(), Expr::Bool { value: false, .. })
2787 && matches!(else_value.as_ref(), Expr::Number { value, .. } if *value == 3.0)
2788 ));
2789
2790 let errors = parse_err("rule \"r\":\n @Event global\n debug(1 if true)\n");
2791 assert_eq!(errors[0].code, "parse-error");
2792 assert!(errors[0].message.contains("expected `else`"));
2793 }
2794
2795 #[test]
2796 fn parses_receiver_calls() {
2797 let program =
2800 parse_ok("rule \"r\":\n @Event eachPlayer\n eventPlayer.setMoveSpeed(100)\n");
2801 let RuleEntry::Rule(rule) = &program.rules[0] else {
2802 panic!("expected rule");
2803 };
2804 let Stmt::Expr { expr, .. } = &rule.actions[0] else {
2805 panic!("expected expression statement, got {:?}", rule.actions[0]);
2806 };
2807 let Expr::ReceiverCall {
2808 receiver,
2809 name,
2810 args,
2811 ..
2812 } = &expr
2813 else {
2814 panic!("expected receiver call, got {expr:?}");
2815 };
2816 assert_eq!(name, "setMoveSpeed");
2817 assert!(
2818 matches!(receiver.as_ref(), Expr::Name { name, .. } if name == "eventPlayer"),
2819 "receiver must be the eventPlayer name"
2820 );
2821 assert_eq!(args.len(), 1);
2822 assert!(args[0].keyword.is_none(), "positional argument");
2823 assert!(matches!(&args[0].value, Expr::Number { .. }));
2824 }
2825
2826 #[test]
2827 fn parses_keyword_arguments_with_name_spans() {
2828 let program =
2831 parse_ok("rule \"r\":\n @Event global\n wait(time=1)\n debug(g == 1)\n");
2832 let RuleEntry::Rule(rule) = &program.rules[0] else {
2833 panic!("expected rule");
2834 };
2835 let Stmt::Expr { expr, .. } = &rule.actions[0] else {
2836 panic!("expected expression statement");
2837 };
2838 let Expr::Call { args, .. } = expr else {
2839 panic!("expected a call, got {expr:?}");
2840 };
2841 let (keyword, span) = args[0].keyword.as_ref().expect("keyword argument");
2842 assert_eq!(keyword, "time");
2843 assert_eq!(span.start.line, 3);
2844 assert!(matches!(&args[0].value, Expr::Number { .. }));
2845
2846 let Stmt::Expr { expr, .. } = &rule.actions[1] else {
2847 panic!("expected expression statement");
2848 };
2849 let Expr::Call { args, .. } = expr else {
2850 panic!("expected a call, got {expr:?}");
2851 };
2852 assert!(args[0].keyword.is_none(), "comparisons are not keywords");
2853 assert!(matches!(&args[0].value, Expr::Binary { .. }));
2854 }
2855
2856 #[test]
2857 fn adjacent_string_literals_concatenate_and_preserve_span() {
2858 let program = parse_ok("rule \"r\":\n @Event global\n debug(\"one\" \"two\")\n");
2859 let RuleEntry::Rule(rule) = &program.rules[0] else {
2860 panic!("expected rule");
2861 };
2862 let Stmt::Expr { expr, .. } = &rule.actions[0] else {
2863 panic!("expected expression statement");
2864 };
2865 let Expr::Call { args, .. } = expr else {
2866 panic!("expected call");
2867 };
2868 let Expr::String { value, span } = &args[0].value else {
2869 panic!("expected concatenated string");
2870 };
2871 assert_eq!(value, "onetwo");
2872 assert_eq!(span.start.line, 3);
2873 assert_eq!(span.start.col, 11);
2874 assert_eq!(span.end.col, 22);
2875 }
2876
2877 #[test]
2878 fn multiline_adjacent_string_literals_concatenate_inside_group() {
2879 let program =
2880 parse_ok("rule \"r\":\n @Event global\n debug(\"one\"\n \"two\")\n");
2881 let RuleEntry::Rule(rule) = &program.rules[0] else {
2882 panic!("expected rule");
2883 };
2884 let Stmt::Expr { expr, .. } = &rule.actions[0] else {
2885 panic!("expected expression statement");
2886 };
2887 let Expr::Call { args, .. } = expr else {
2888 panic!("expected call");
2889 };
2890 assert!(matches!(
2891 &args[0].value,
2892 Expr::String { value, .. } if value == "onetwo"
2893 ));
2894 }
2895
2896 #[test]
2897 fn newline_outside_group_keeps_adjacent_literals_as_statements() {
2898 let program = parse_ok("rule \"r\":\n @Event global\n \"one\"\n \"two\"\n");
2899 let RuleEntry::Rule(rule) = &program.rules[0] else {
2900 panic!("expected rule");
2901 };
2902 assert_eq!(rule.actions.len(), 2);
2903 }
2904
2905 #[test]
2906 fn non_name_keyword_lhs_is_a_parse_error() {
2907 let errors = parse_err("rule \"r\":\n @Event global\n debug(1 = 2)\n");
2909 assert!(!errors.is_empty());
2910 assert_eq!(errors[0].code, "parse-error");
2911 }
2912
2913 #[test]
2914 fn parses_member_call_on_call_result() {
2915 let program = parse_ok(
2918 "rule \"r\":\n @Event eachPlayer\n getPlayersInRadius(eventPlayer, 10).setStatusEffect(eventPlayer, 30)\n",
2919 );
2920 let RuleEntry::Rule(rule) = &program.rules[0] else {
2921 panic!("expected rule");
2922 };
2923 let Stmt::Expr { expr, .. } = &rule.actions[0] else {
2924 panic!("expected expression statement");
2925 };
2926 let Expr::ReceiverCall {
2927 receiver,
2928 name,
2929 args,
2930 ..
2931 } = &expr
2932 else {
2933 panic!("expected receiver call, got {expr:?}");
2934 };
2935 assert_eq!(name, "setStatusEffect");
2936 assert!(
2937 matches!(receiver.as_ref(), Expr::Call { name, .. } if name == "getPlayersInRadius"),
2938 "receiver must be the preceding call"
2939 );
2940 assert_eq!(args.len(), 2);
2941 }
2942
2943 #[test]
2944 fn member_without_call_is_not_a_call() {
2945 let program =
2948 parse_ok("rule \"r\":\n @Event eachPlayer\n x = eventPlayer.moveSpeed\n");
2949 let RuleEntry::Rule(rule) = &program.rules[0] else {
2950 panic!("expected rule");
2951 };
2952 let Stmt::Assign { value, .. } = &rule.actions[0] else {
2953 panic!("expected assignment");
2954 };
2955 assert!(matches!(
2956 &value,
2957 Expr::Member { member, .. } if member == "moveSpeed"
2958 ));
2959 }
2960
2961 #[test]
2962 fn parses_advanced_rule_annotations_with_source_arguments() {
2963 let program = parse_ok(
2964 "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",
2965 );
2966 let RuleEntry::SubroutineDef { annotations, .. } = &program.rules[0] else {
2967 panic!("expected subroutine");
2968 };
2969 assert_eq!(annotations.len(), 2);
2970 let RuleEntry::Rule(rule) = &program.rules[1] else {
2971 panic!("expected rule");
2972 };
2973 assert!(rule.disabled);
2974 assert!(rule.delimiter);
2975 assert_eq!(rule.new_page.as_deref(), Some("Page"));
2976 assert_eq!(rule.annotations.len(), 7);
2977 assert_eq!(rule.annotations[1].args[0].text, "1");
2978 assert_eq!(rule.annotations[2].args[0].text, "dmon");
2979 }
2980}