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