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 _ => {
953 let end = self.peek().span.start;
954 Ok(Stmt::Expr {
955 expr,
956 span: Span::new(start.file, start.start, end),
957 })
958 }
959 }
960 }
961
962 fn parse_if(&mut self) -> Result<Stmt, ()> {
963 let start = self.advance();
964 let line_indent = start.span.start.col;
965 let condition = self.parse_expr()?;
966 if self
967 .expect(TokenKind::Colon, "':' after the if condition")
968 .is_err()
969 {
970 return Err(());
971 }
972 let body_indent = self.block_indent(line_indent).ok_or(())?;
973 let body = self.parse_block(body_indent);
974 let mut branches = vec![IfBranch { condition, body }];
975 let mut r#else = None;
976 loop {
977 let save = self.pos;
978 self.skip_newlines();
979 if self.peek_kind() == TokenKind::Eof || self.peek().span.start.col != line_indent {
980 self.pos = save;
981 break;
982 }
983 if self.is_ident("elif") {
984 self.advance();
985 let condition = match self.parse_expr() {
986 Ok(expr) => expr,
987 Err(()) => return Err(()),
988 };
989 if self
990 .expect(TokenKind::Colon, "':' after the elif condition")
991 .is_err()
992 {
993 return Err(());
994 }
995 let body_indent = self.block_indent(line_indent).ok_or(())?;
996 let body = self.parse_block(body_indent);
997 branches.push(IfBranch { condition, body });
998 } else if self.is_ident("else") {
999 self.advance();
1000 if self.expect(TokenKind::Colon, "':' after `else`").is_err() {
1001 return Err(());
1002 }
1003 let body_indent = self.block_indent(line_indent).ok_or(())?;
1004 let body = self.parse_block(body_indent);
1005 r#else = Some(body);
1006 break;
1007 } else {
1008 self.pos = save;
1009 break;
1010 }
1011 }
1012 Ok(Stmt::If {
1013 branches,
1014 r#else,
1015 span: start.span,
1016 })
1017 }
1018
1019 fn parse_for(&mut self) -> Result<Stmt, ()> {
1020 let start = self.advance();
1021 let variable = self.parse_primary()?;
1022 if !self.is_ident("in") {
1023 self.error_at_current("expected `in` in the for statement".to_string());
1024 return Err(());
1025 }
1026 self.advance();
1027 let iterable = self.parse_expr()?;
1028 if self
1029 .expect(TokenKind::Colon, "':' after the for header")
1030 .is_err()
1031 {
1032 return Err(());
1033 }
1034 let line_indent = start.span.start.col;
1035 let body_indent = self.block_indent(line_indent).ok_or(())?;
1036 let body = self.parse_block(body_indent);
1037 Ok(Stmt::For {
1038 variable,
1039 iterable,
1040 body,
1041 span: start.span,
1042 })
1043 }
1044
1045 fn parse_while(&mut self) -> Result<Stmt, ()> {
1046 let start = self.advance();
1047 let condition = self.parse_expr()?;
1048 if self
1049 .expect(TokenKind::Colon, "':' after the while condition")
1050 .is_err()
1051 {
1052 return Err(());
1053 }
1054 let line_indent = start.span.start.col;
1055 let body_indent = self.block_indent(line_indent).ok_or(())?;
1056 let body = self.parse_block(body_indent);
1057 Ok(Stmt::While {
1058 condition,
1059 body,
1060 span: start.span,
1061 })
1062 }
1063
1064 fn parse_do_while(&mut self) -> Result<Stmt, ()> {
1065 let start = self.advance();
1066 if self.expect(TokenKind::Colon, "':' after `do`").is_err() {
1067 return Err(());
1068 }
1069 let body_indent = self.block_indent(start.span.start.col).ok_or(())?;
1070 let body = self.parse_block(body_indent);
1071 if !self.is_ident("while") {
1072 self.error_at_current("expected `while` after the do block".to_string());
1073 return Err(());
1074 }
1075 self.advance();
1076 let condition = self.parse_expr()?;
1077 if self.peek_kind() != TokenKind::Newline && self.peek_kind() != TokenKind::Eof {
1078 self.error_at_current("expected the end of the do-while condition".to_string());
1079 return Err(());
1080 }
1081 Ok(Stmt::DoWhile {
1082 condition,
1083 body,
1084 span: start.span,
1085 })
1086 }
1087
1088 fn parse_switch(&mut self) -> Result<Stmt, ()> {
1089 let start = self.advance();
1090 let value = self.parse_expr()?;
1091 if self
1092 .expect(TokenKind::Colon, "':' after the switch value")
1093 .is_err()
1094 {
1095 return Err(());
1096 }
1097 let body_indent = self.block_indent(start.span.start.col).ok_or(())?;
1098 let mut arms = Vec::new();
1099 loop {
1100 self.skip_newlines();
1101 if self.peek_kind() == TokenKind::Eof || self.peek().span.start.col < body_indent {
1102 break;
1103 }
1104 if self.peek().span.start.col != body_indent {
1105 self.error_at_current("unexpected indentation in switch".to_string());
1106 self.recover_line();
1107 continue;
1108 }
1109 if self.is_ident("case") {
1110 let case_start = self.advance();
1111 let case_value = self.parse_expr()?;
1112 if self
1113 .expect(TokenKind::Colon, "':' after the case value")
1114 .is_err()
1115 {
1116 return Err(());
1117 }
1118 let case_body_indent = self.block_indent(body_indent).ok_or(())?;
1119 let body = self.parse_block(case_body_indent);
1120 arms.push(SwitchArm::Case {
1121 value: case_value,
1122 body,
1123 span: case_start.span,
1124 });
1125 } else if self.is_ident("default") {
1126 let default_start = self.advance();
1127 if self
1128 .expect(TokenKind::Colon, "':' after `default`")
1129 .is_err()
1130 {
1131 return Err(());
1132 }
1133 let default_body_indent = self.block_indent(body_indent).ok_or(())?;
1134 arms.push(SwitchArm::Default {
1135 body: self.parse_block(default_body_indent),
1136 span: default_start.span,
1137 });
1138 if default_start.span.start.col != body_indent {
1139 self.error_at_current("invalid default indentation".to_string());
1140 return Err(());
1141 }
1142 } else {
1143 self.error_at_current("expected `case` or `default` in switch".to_string());
1144 self.recover_line();
1145 }
1146 }
1147 if arms.is_empty() {
1148 self.errors.push(OpyError::at(
1149 "parse-error",
1150 "switch must contain at least one case or default arm".to_string(),
1151 start.span,
1152 ));
1153 return Err(());
1154 }
1155 Ok(Stmt::Switch {
1156 value,
1157 arms,
1158 span: start.span,
1159 })
1160 }
1161
1162 fn parse_expr(&mut self) -> Result<Expr, ()> {
1165 self.parse_or()
1166 }
1167
1168 fn parse_or(&mut self) -> Result<Expr, ()> {
1169 let mut left = self.parse_and()?;
1170 while self.is_ident("or") {
1171 self.advance();
1172 let right = self.parse_and()?;
1173 let span = Span::new(left.span().file, left.span().start, right.span().end);
1174 left = Expr::Binary {
1175 op: "or".to_string(),
1176 left: Box::new(left),
1177 right: Box::new(right),
1178 span,
1179 };
1180 }
1181 Ok(left)
1182 }
1183
1184 fn parse_and(&mut self) -> Result<Expr, ()> {
1185 let mut left = self.parse_not()?;
1186 while self.is_ident("and") {
1187 self.advance();
1188 let right = self.parse_not()?;
1189 let span = Span::new(left.span().file, left.span().start, right.span().end);
1190 left = Expr::Binary {
1191 op: "and".to_string(),
1192 left: Box::new(left),
1193 right: Box::new(right),
1194 span,
1195 };
1196 }
1197 Ok(left)
1198 }
1199
1200 fn parse_not(&mut self) -> Result<Expr, ()> {
1201 if self.is_ident("not") {
1202 let start = self.advance();
1203 let operand = self.parse_not()?;
1204 let end = operand.span().end;
1205 return Ok(Expr::Unary {
1206 op: "not".to_string(),
1207 operand: Box::new(operand),
1208 span: Span::new(start.span.file, start.span.start, end),
1209 });
1210 }
1211 self.parse_comparison()
1212 }
1213
1214 fn parse_comparison(&mut self) -> Result<Expr, ()> {
1215 let mut left = self.parse_additive()?;
1216 loop {
1217 let op = match self.peek_kind() {
1218 TokenKind::Eq => "==",
1219 TokenKind::Ne => "!=",
1220 TokenKind::Lt => "<",
1221 TokenKind::Le => "<=",
1222 TokenKind::Gt => ">",
1223 TokenKind::Ge => ">=",
1224 _ if self.is_ident("in") => "in",
1225 _ if self.is_ident("not") && self.peek_at(1).text == "in" => "not in",
1226 _ => break,
1227 };
1228 self.advance();
1229 if op == "not in" {
1230 self.advance();
1231 }
1232 let right = self.parse_additive()?;
1233 let span = Span::new(left.span().file, left.span().start, right.span().end);
1234 left = Expr::Binary {
1235 op: op.to_string(),
1236 left: Box::new(left),
1237 right: Box::new(right),
1238 span,
1239 };
1240 }
1241 Ok(left)
1242 }
1243
1244 fn parse_additive(&mut self) -> Result<Expr, ()> {
1245 let mut left = self.parse_multiplicative()?;
1246 loop {
1247 let op = match self.peek_kind() {
1248 TokenKind::Plus => "+",
1249 TokenKind::Minus => "-",
1250 _ => break,
1251 };
1252 self.advance();
1253 let right = self.parse_multiplicative()?;
1254 let span = Span::new(left.span().file, left.span().start, right.span().end);
1255 left = Expr::Binary {
1256 op: op.to_string(),
1257 left: Box::new(left),
1258 right: Box::new(right),
1259 span,
1260 };
1261 }
1262 Ok(left)
1263 }
1264
1265 fn parse_multiplicative(&mut self) -> Result<Expr, ()> {
1266 let mut left = self.parse_unary()?;
1267 loop {
1268 let op = match self.peek_kind() {
1269 TokenKind::Star => "*",
1270 TokenKind::Slash => "/",
1271 TokenKind::Percent => "%",
1272 _ => break,
1273 };
1274 self.advance();
1275 let right = self.parse_unary()?;
1276 let span = Span::new(left.span().file, left.span().start, right.span().end);
1277 left = Expr::Binary {
1278 op: op.to_string(),
1279 left: Box::new(left),
1280 right: Box::new(right),
1281 span,
1282 };
1283 }
1284 Ok(left)
1285 }
1286
1287 fn parse_unary(&mut self) -> Result<Expr, ()> {
1288 if self.peek_kind() == TokenKind::Minus {
1289 let start = self.advance();
1290 let operand = self.parse_unary()?;
1291 let end = operand.span().end;
1292 return Ok(Expr::Unary {
1293 op: "-".to_string(),
1294 operand: Box::new(operand),
1295 span: Span::new(start.span.file, start.span.start, end),
1296 });
1297 }
1298 self.parse_power()
1299 }
1300
1301 fn parse_power(&mut self) -> Result<Expr, ()> {
1302 let base = self.parse_postfix()?;
1303 if self.peek_kind() == TokenKind::DoubleStar {
1304 self.advance();
1305 let exponent = self.parse_unary()?;
1307 let span = Span::new(base.span().file, base.span().start, exponent.span().end);
1308 return Ok(Expr::Binary {
1309 op: "**".to_string(),
1310 left: Box::new(base),
1311 right: Box::new(exponent),
1312 span,
1313 });
1314 }
1315 Ok(base)
1316 }
1317
1318 fn parse_postfix(&mut self) -> Result<Expr, ()> {
1319 let mut base = self.parse_primary()?;
1320 loop {
1321 match self.peek_kind() {
1322 TokenKind::LParen => {
1323 let mut args = Vec::new();
1324 self.parse_call_args(&mut args)?;
1325 let end = self.tokens[self.pos.saturating_sub(1)].span.end;
1326 base = match base {
1327 Expr::Name { name, span } => Expr::Call {
1328 name,
1329 args,
1330 span: Span::new(span.file, span.start, end),
1331 },
1332 Expr::Member {
1333 receiver,
1334 member,
1335 span,
1336 ..
1337 } => Expr::ReceiverCall {
1338 receiver,
1339 name: member,
1340 args,
1341 span: Span::new(span.file, span.start, end),
1342 },
1343 _other => {
1344 self.errors.push(OpyError::at(
1345 "parse-error",
1346 "cannot call this expression".to_string(),
1347 self.peek().span,
1348 ));
1349 return Err(());
1350 }
1351 };
1352 }
1353 TokenKind::LBracket => {
1354 self.advance();
1355 let index = self.parse_expr()?;
1356 let end = match self.expect(TokenKind::RBracket, "']'") {
1357 Ok(token) => token.span.end,
1358 Err(()) => return Err(()),
1359 };
1360 let span = Span::new(base.span().file, base.span().start, end);
1361 base = Expr::Index {
1362 array: Box::new(base),
1363 index: Box::new(index),
1364 span,
1365 };
1366 }
1367 TokenKind::Dot => {
1368 self.advance();
1369 let member_token = self.peek().clone();
1370 let member = match self.expect_ident("a member name after '.'") {
1371 Ok(member) => member,
1372 Err(()) => return Err(()),
1373 };
1374 let member_span = member_token.span;
1375 let end = member_span.end;
1376 let span = Span::new(base.span().file, base.span().start, end);
1377 base = Expr::Member {
1378 receiver: Box::new(base),
1379 member,
1380 member_span,
1381 span,
1382 };
1383 }
1384 _ => break,
1385 }
1386 }
1387 Ok(base)
1388 }
1389
1390 fn parse_event_args(&mut self, args: &mut Vec<Expr>) -> Result<(), ()> {
1393 self.expect(TokenKind::LParen, "'('")?;
1394 self.skip_newlines();
1395 if self.peek_kind() == TokenKind::RParen {
1396 self.advance();
1397 return Ok(());
1398 }
1399 loop {
1400 let expr = self.parse_expr()?;
1401 if self.peek_kind() == TokenKind::Assign {
1402 self.error_at_current("keyword arguments are not valid in @Event".to_string());
1403 return Err(());
1404 }
1405 args.push(expr);
1406 self.skip_newlines();
1407 if self.peek_kind() == TokenKind::Comma {
1408 self.advance();
1409 self.skip_newlines();
1410 if self.peek_kind() == TokenKind::RParen {
1411 break;
1412 }
1413 } else {
1414 break;
1415 }
1416 }
1417 self.expect(TokenKind::RParen, "')'")?;
1418 Ok(())
1419 }
1420
1421 fn parse_call_args(&mut self, args: &mut Vec<CallArg>) -> Result<(), ()> {
1422 self.expect(TokenKind::LParen, "'('")?;
1423 self.skip_newlines();
1424 if self.peek_kind() == TokenKind::RParen {
1425 self.advance();
1426 return Ok(());
1427 }
1428 loop {
1429 match self.parse_expr() {
1430 Ok(expr) => {
1431 if self.peek_kind() == TokenKind::Assign {
1436 let Expr::Name { name, span } = expr else {
1437 self.error_at_current(
1438 "expected a keyword name before '=' in this call".to_string(),
1439 );
1440 return Err(());
1441 };
1442 self.advance();
1443 let value = match self.parse_expr() {
1444 Ok(value) => value,
1445 Err(()) => return Err(()),
1446 };
1447 args.push(CallArg {
1448 keyword: Some((name, span)),
1449 value,
1450 });
1451 } else {
1452 args.push(CallArg {
1453 keyword: None,
1454 value: expr,
1455 });
1456 }
1457 }
1458 Err(()) => return Err(()),
1459 }
1460 self.skip_newlines();
1461 if self.peek_kind() == TokenKind::Comma {
1462 self.advance();
1463 self.skip_newlines();
1464 if self.peek_kind() == TokenKind::RParen {
1465 break;
1466 }
1467 } else {
1468 break;
1469 }
1470 }
1471 self.expect(TokenKind::RParen, "')'")?;
1472 Ok(())
1473 }
1474
1475 fn parse_primary(&mut self) -> Result<Expr, ()> {
1476 let token = self.peek();
1477 match token.kind {
1478 TokenKind::Number => {
1479 let token = self.advance();
1480 let value = if let Some(hex) = token
1481 .text
1482 .strip_prefix("0x")
1483 .or_else(|| token.text.strip_prefix("0X"))
1484 {
1485 u64::from_str_radix(hex, 16).map_or(f64::NAN, |value| value as f64)
1486 } else {
1487 token.text.parse().unwrap_or(f64::NAN)
1488 };
1489 Ok(Expr::Number {
1490 value,
1491 text: token.text.clone(),
1492 span: token.span,
1493 })
1494 }
1495 TokenKind::String => {
1496 let token = self.advance();
1497 Ok(Expr::String {
1498 value: token.text.clone(),
1499 span: token.span,
1500 })
1501 }
1502 TokenKind::Ident => {
1503 let token = self.advance();
1504 if token.text == "lambda" {
1505 return self.parse_lambda(token.span);
1506 }
1507 if is_string_modifier(&token.text) && self.peek_kind() == TokenKind::String {
1508 let string = self.advance();
1509 let (format_text, interpolations) = if token.text == "f" {
1510 let raw = string.raw.as_deref().unwrap_or(&string.text);
1511 let (format_text, interpolations) =
1512 self.parse_f_string(raw, string.span)?;
1513 (Some(format_text), interpolations)
1514 } else {
1515 (None, Vec::new())
1516 };
1517 return Ok(Expr::StringModifier {
1518 modifier: token.text.chars().next().unwrap_or_default(),
1519 value: string.text,
1520 format_text,
1521 interpolations,
1522 span: Span::new(token.span.file, token.span.start, string.span.end),
1523 });
1524 }
1525 match token.text.as_str() {
1526 "true" => Ok(Expr::Bool {
1527 value: true,
1528 span: token.span,
1529 }),
1530 "false" => Ok(Expr::Bool {
1531 value: false,
1532 span: token.span,
1533 }),
1534 "None" | "null" => Ok(Expr::Null { span: token.span }),
1535 _ => Ok(Expr::Name {
1536 name: token.text.clone(),
1537 span: token.span,
1538 }),
1539 }
1540 }
1541 TokenKind::LParen => {
1542 self.advance();
1543 let expr = self.parse_expr()?;
1544 self.expect(TokenKind::RParen, "')'")?;
1545 Ok(expr)
1546 }
1547 TokenKind::LBracket => {
1548 let open = self.advance();
1549 let mut elements = Vec::new();
1550 self.skip_newlines();
1551 if self.peek_kind() == TokenKind::RBracket {
1552 let end = self.advance().span.end;
1553 return Ok(Expr::Array {
1554 elements,
1555 span: Span::new(open.span.file, open.span.start, end),
1556 });
1557 }
1558 let first = self.parse_expr()?;
1559 if self.is_ident("for") {
1560 self.advance();
1561 let variable_token =
1562 self.expect(TokenKind::Ident, "a comprehension variable")?;
1563 let index = if self.peek_kind() == TokenKind::Comma {
1564 self.advance();
1565 let index = self.expect(TokenKind::Ident, "a comprehension index")?;
1566 Some((index.text, index.span))
1567 } else {
1568 None
1569 };
1570 if !self.is_ident("in") {
1571 self.error_at_current("expected `in` in list comprehension".to_string());
1572 return Err(());
1573 }
1574 self.advance();
1575 let iterable = self.parse_expr()?;
1576 let condition = if self.is_ident("if") {
1577 self.advance();
1578 Some(Box::new(self.parse_expr()?))
1579 } else {
1580 None
1581 };
1582 let end = self.expect(TokenKind::RBracket, "']'")?.span.end;
1583 return Ok(Expr::Comprehension {
1584 element: Box::new(first),
1585 variable: variable_token.text,
1586 variable_span: variable_token.span,
1587 index,
1588 iterable: Box::new(iterable),
1589 condition,
1590 span: Span::new(open.span.file, open.span.start, end),
1591 });
1592 }
1593 elements.push(first);
1594 loop {
1595 self.skip_newlines();
1596 if self.peek_kind() == TokenKind::Comma {
1597 self.advance();
1598 self.skip_newlines();
1599 if self.peek_kind() == TokenKind::RBracket {
1600 break;
1601 }
1602 elements.push(self.parse_expr()?);
1603 } else {
1604 break;
1605 }
1606 }
1607 let end = match self.expect(TokenKind::RBracket, "']'") {
1608 Ok(token) => token.span.end,
1609 Err(()) => return Err(()),
1610 };
1611 Ok(Expr::Array {
1612 elements,
1613 span: Span::new(open.span.file, open.span.start, end),
1614 })
1615 }
1616 TokenKind::LBrace => self.parse_dict(),
1617 _ => {
1618 self.error_at_current(format!("expected an expression but found '{}'", token.text));
1619 Err(())
1620 }
1621 }
1622 }
1623
1624 fn parse_dict(&mut self) -> Result<Expr, ()> {
1625 let open = self.advance();
1626 let mut entries = Vec::new();
1627 self.skip_newlines();
1628 if self.peek_kind() == TokenKind::RBrace {
1629 let end = self.advance().span.end;
1630 return Ok(Expr::Dict {
1631 entries,
1632 span: Span::new(open.span.file, open.span.start, end),
1633 });
1634 }
1635 loop {
1636 let key = self.parse_expr()?;
1637 self.expect(TokenKind::Colon, "':' in a dictionary entry")?;
1638 let value = self.parse_expr()?;
1639 let span = Span::new(key.span().file, key.span().start, value.span().end);
1640 entries.push(DictEntry { key, value, span });
1641 self.skip_newlines();
1642 if self.peek_kind() == TokenKind::Comma {
1643 self.advance();
1644 self.skip_newlines();
1645 if self.peek_kind() == TokenKind::RBrace {
1646 break;
1647 }
1648 } else {
1649 break;
1650 }
1651 }
1652 let end = self.expect(TokenKind::RBrace, "'}'")?.span.end;
1653 Ok(Expr::Dict {
1654 entries,
1655 span: Span::new(open.span.file, open.span.start, end),
1656 })
1657 }
1658
1659 fn parse_lambda(&mut self, start: Span) -> Result<Expr, ()> {
1660 let mut params = Vec::new();
1661 loop {
1662 let param = self.expect(TokenKind::Ident, "a lambda parameter")?;
1663 params.push((param.text, param.span));
1664 if self.peek_kind() == TokenKind::Comma {
1665 self.advance();
1666 } else {
1667 break;
1668 }
1669 }
1670 self.expect(TokenKind::Colon, "':' after lambda parameters")?;
1671 let body = self.parse_expr()?;
1672 Ok(Expr::Lambda {
1673 params,
1674 body: Box::new(body.clone()),
1675 span: Span::new(start.file, start.start, body.span().end),
1676 })
1677 }
1678
1679 fn parse_f_string(&mut self, raw: &str, string_span: Span) -> Result<(String, Vec<Expr>), ()> {
1684 let chars: Vec<char> = raw.chars().collect();
1685 let mut text = String::new();
1686 let mut interpolations = Vec::new();
1687 let mut index = 0;
1688 while index < chars.len() {
1689 match chars[index] {
1690 '{' if chars.get(index + 1) == Some(&'{') => {
1691 text.push_str("{{");
1692 index += 2;
1693 }
1694 '}' if chars.get(index + 1) == Some(&'}') => {
1695 text.push_str("}}");
1696 index += 2;
1697 }
1698 '{' => {
1699 let end = self.find_f_string_end(&chars, index + 1);
1700 let Some(end) = end else {
1701 self.errors.push(OpyError::at(
1702 "parse-error",
1703 "unterminated f-string interpolation".to_string(),
1704 string_span,
1705 ));
1706 return Err(());
1707 };
1708 let expression: String = chars[index + 1..end].iter().collect();
1709 if expression.trim().is_empty() {
1710 self.errors.push(OpyError::at(
1711 "parse-error",
1712 "f-string interpolation cannot be empty".to_string(),
1713 Span::new(
1714 string_span.file,
1715 Position::new(
1716 string_span.start.line,
1717 string_span.start.col + index as u32 + 1,
1718 ),
1719 Position::new(
1720 string_span.start.line,
1721 string_span.start.col + end as u32 + 1,
1722 ),
1723 ),
1724 ));
1725 return Err(());
1726 }
1727 let origin = Position::new(
1728 string_span.start.line,
1729 string_span.start.col + index as u32 + 1,
1730 );
1731 let parsed = parse_expression_fragment(&expression, string_span.file, origin)
1732 .map_err(|error| {
1733 self.errors.push(error);
1734 });
1735 let Ok(parsed) = parsed else {
1736 return Err(());
1737 };
1738 text.push_str(&format!("{{{}}}", interpolations.len()));
1739 interpolations.push(parsed);
1740 index = end + 1;
1741 }
1742 '}' => {
1743 self.errors.push(OpyError::at(
1744 "parse-error",
1745 "single '}' is not valid in an f-string".to_string(),
1746 string_span,
1747 ));
1748 return Err(());
1749 }
1750 '\\' if index + 1 < chars.len() => {
1751 text.push(decode_string_escape(chars[index + 1]));
1752 index += 2;
1753 }
1754 character => {
1755 text.push(character);
1756 index += 1;
1757 }
1758 }
1759 }
1760 Ok((text, interpolations))
1761 }
1762
1763 fn find_f_string_end(&self, chars: &[char], start: usize) -> Option<usize> {
1764 let mut nested_braces = 0;
1765 let mut quote = None;
1766 let mut escaped = false;
1767 for (index, character) in chars.iter().enumerate().skip(start) {
1768 if escaped {
1769 escaped = false;
1770 continue;
1771 }
1772 if *character == '\\' && quote.is_some() {
1773 escaped = true;
1774 continue;
1775 }
1776 if let Some(active_quote) = quote {
1777 if *character == active_quote {
1778 quote = None;
1779 }
1780 continue;
1781 }
1782 match character {
1783 '"' | '\'' => quote = Some(*character),
1784 '{' => nested_braces += 1,
1785 '}' if nested_braces == 0 => return Some(index),
1786 '}' => nested_braces -= 1,
1787 _ => {}
1788 }
1789 }
1790 None
1791 }
1792}
1793
1794fn parse_expression_fragment(text: &str, file: u32, origin: Position) -> Result<Expr, OpyError> {
1797 let mut tokens = crate::lexer::lex(crate::lexer::LexInput {
1798 file_id: file,
1799 text,
1800 })?;
1801 for token in &mut tokens {
1802 token.span = shift_span(token.span, origin);
1803 }
1804 let mut parser = Parser {
1805 tokens: &tokens,
1806 pos: 0,
1807 allow_macro_redeclaration: false,
1808 errors: Vec::new(),
1809 };
1810 let expression = parser.parse_expr().map_err(|()| {
1811 parser.errors.first().cloned().unwrap_or_else(|| {
1812 OpyError::at(
1813 "parse-error",
1814 "invalid f-string expression",
1815 Span::new(file, origin, origin),
1816 )
1817 })
1818 })?;
1819 if parser.peek_kind() != TokenKind::Eof {
1820 parser.error_at_current("unexpected tokens in f-string interpolation".to_string());
1821 }
1822 parser.errors.into_iter().next().map_or(Ok(expression), Err)
1823}
1824
1825fn shift_span(span: Span, origin: Position) -> Span {
1826 fn shift(position: Position, origin: Position) -> Position {
1827 Position::new(
1828 origin.line + position.line.saturating_sub(1),
1829 if position.line == 1 {
1830 origin.col + position.col.saturating_sub(1)
1831 } else {
1832 position.col
1833 },
1834 )
1835 }
1836 Span::new(
1837 span.file,
1838 shift(span.start, origin),
1839 shift(span.end, origin),
1840 )
1841}
1842
1843fn decode_string_escape(character: char) -> char {
1844 match character {
1845 'n' => '\n',
1846 't' => '\t',
1847 'r' => '\r',
1848 '\\' => '\\',
1849 '"' => '"',
1850 '\'' => '\'',
1851 other => other,
1852 }
1853}
1854
1855fn is_string_modifier(text: &str) -> bool {
1856 matches!(text, "f" | "w" | "l" | "b" | "c" | "t")
1857}
1858
1859#[cfg(test)]
1860mod tests {
1861 use super::*;
1862 use crate::lexer::{LexInput, lex};
1863
1864 fn parse_ok(text: &str) -> Program {
1865 let tokens = lex(LexInput { file_id: 0, text }).unwrap();
1866 let output = parse(&tokens);
1867 assert!(
1868 output.errors.is_empty(),
1869 "unexpected errors: {:?}",
1870 output.errors
1871 );
1872 output.program.unwrap()
1873 }
1874
1875 fn parse_err(text: &str) -> Vec<OpyError> {
1876 let tokens = lex(LexInput { file_id: 0, text }).unwrap();
1877 parse(&tokens).errors
1878 }
1879
1880 #[test]
1881 fn parses_basic_rule() {
1882 let program = parse_ok("rule \"setup\":\n @Event global\n disableInspector()\n");
1883 assert_eq!(program.rules.len(), 1);
1884 let RuleEntry::Rule(rule) = &program.rules[0] else {
1885 panic!("expected rule");
1886 };
1887 assert_eq!(rule.name, "setup");
1888 assert_eq!(rule.event.name, "global");
1889 assert_eq!(rule.actions.len(), 1);
1890 }
1891
1892 #[test]
1893 fn parses_power_augmented_assignment() {
1894 let program =
1897 parse_ok("globalvar a\nrule \"r\":\n @Event global\n a = 2\n a **= 3\n");
1898 let RuleEntry::Rule(rule) = &program.rules[0] else {
1899 panic!("expected rule");
1900 };
1901 let Stmt::Assign {
1902 value,
1903 target: assigned_target,
1904 ..
1905 } = &rule.actions[1]
1906 else {
1907 panic!("expected an assignment");
1908 };
1909 let Expr::Binary {
1910 op, left, right, ..
1911 } = value
1912 else {
1913 panic!("expected a binary modification, got {value:?}");
1914 };
1915 assert_eq!(op, "**");
1916 assert!(matches!(&**left, Expr::Name { .. }));
1917 assert!(matches!(
1918 assigned_target,
1919 Expr::Name { name, .. } if name == "a"
1920 ));
1921 assert!(matches!(right.as_ref(), Expr::Number { .. }));
1922 }
1923
1924 #[test]
1925 fn parses_control_flow() {
1926 let program = parse_ok(
1927 "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",
1928 );
1929 let RuleEntry::Rule(rule) = &program.rules[0] else {
1930 panic!();
1931 };
1932 assert!(matches!(rule.actions[0], Stmt::For { .. }));
1933 let Stmt::For { body, .. } = &rule.actions[0] else {
1934 panic!();
1935 };
1936 let Stmt::If {
1937 branches, r#else, ..
1938 } = &body[0]
1939 else {
1940 panic!();
1941 };
1942 assert_eq!(branches.len(), 2);
1943 assert!(r#else.is_some());
1944 let Stmt::While { body, .. } = &rule.actions[1] else {
1945 panic!();
1946 };
1947 assert_eq!(body.len(), 2);
1948 }
1949
1950 #[test]
1951 fn parses_issue_28_constructs() {
1952 let program = parse_ok(
1953 "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",
1954 );
1955 let RuleEntry::Rule(rule) = &program.rules[0] else {
1956 panic!("expected rule");
1957 };
1958 assert!(matches!(rule.actions[0], Stmt::Switch { .. }));
1959 assert!(matches!(rule.actions[1], Stmt::Assign { .. }));
1960 }
1961
1962 #[test]
1963 fn rejects_incomplete_do_while_and_dictionary_entries() {
1964 let errors = parse_err(
1965 "rule \"r\":\n @Event global\n do:\n pass\n while\n x = {\"x\"}\n",
1966 );
1967 assert!(!errors.is_empty());
1968 assert!(errors.iter().all(|error| error.code == "parse-error"));
1969 }
1970
1971 #[test]
1972 fn parses_multi_line_array() {
1973 let program = parse_ok(
1974 "globalvar p\nrule \"r\":\n @Event global\n p = [\n vect(1, 0, 0),\n vect(2, 0, 0),\n ]\n",
1975 );
1976 let RuleEntry::Rule(rule) = &program.rules[0] else {
1977 panic!();
1978 };
1979 let Stmt::Assign { value, .. } = &rule.actions[0] else {
1980 panic!();
1981 };
1982 let Expr::Array { elements, .. } = value else {
1983 panic!("expected array, got {value:?}");
1984 };
1985 assert_eq!(elements.len(), 2);
1986 }
1987
1988 #[test]
1989 fn missing_colon_is_a_structured_error() {
1990 let errors = parse_err("rule \"x\"\n @Event global\n");
1991 assert!(!errors.is_empty());
1992 assert_eq!(errors[0].code, "parse-error");
1993 assert!(errors[0].span.is_some());
1994 }
1995
1996 #[test]
1997 fn def_and_macro_parse() {
1998 let program = parse_ok(
1999 "subroutine showStatus\n\ndef showStatus():\n print(\"hi\")\n\nmacro double(value):\n value + value\n",
2000 );
2001 assert_eq!(program.declarations.len(), 2);
2002 assert!(matches!(program.declarations[1], Decl::Macro { .. }));
2003 let Decl::Macro { args, body, .. } = &program.declarations[1] else {
2004 panic!();
2005 };
2006 assert_eq!(args, &vec!["value".to_string()]);
2007 assert_eq!(body.len(), 1);
2008 }
2009
2010 #[test]
2011 fn macro_and_enum_redeclarations_are_checked_at_ast_surfaces() {
2012 let text = "enum Kind:\n First\n First\nmacro helper():\n pass\nmacro helper():\n pass\n";
2013 let errors = parse_err(text);
2014 assert_eq!(
2015 errors
2016 .iter()
2017 .filter(|error| error.code == "macro-redeclaration")
2018 .count(),
2019 2
2020 );
2021
2022 let tokens = lex(LexInput { file_id: 0, text }).unwrap();
2023 let output = parse_with_options(&tokens, true);
2024 assert!(
2025 output.errors.is_empty(),
2026 "unexpected errors: {:?}",
2027 output.errors
2028 );
2029 assert!(output.program.is_some());
2030 }
2031
2032 #[test]
2033 fn multiple_errors_are_reported() {
2034 let errors =
2035 parse_err("rule \"a\"\n bad statement here\nrule \"b\"\n @Event global\n");
2036 assert!(!errors.is_empty());
2037 }
2038
2039 #[test]
2040 fn precedence_parses_python_like() {
2041 let program = parse_ok("globalvar x\nrule \"r\":\n @Event global\n x = 1 + 2 * 3\n");
2042 let RuleEntry::Rule(rule) = &program.rules[0] else {
2043 panic!();
2044 };
2045 let Stmt::Assign { value, .. } = &rule.actions[0] else {
2046 panic!();
2047 };
2048 let Expr::Binary {
2049 op, left, right, ..
2050 } = value
2051 else {
2052 panic!();
2053 };
2054 assert_eq!(op, "+");
2055 let Expr::Binary { op: inner, .. } = right.as_ref() else {
2056 panic!();
2057 };
2058 assert_eq!(inner, "*");
2059 assert!(matches!(left.as_ref(), Expr::Number { .. }));
2060 }
2061
2062 #[test]
2063 fn parses_receiver_calls() {
2064 let program =
2067 parse_ok("rule \"r\":\n @Event eachPlayer\n eventPlayer.setMoveSpeed(100)\n");
2068 let RuleEntry::Rule(rule) = &program.rules[0] else {
2069 panic!("expected rule");
2070 };
2071 let Stmt::Expr { expr, .. } = &rule.actions[0] else {
2072 panic!("expected expression statement, got {:?}", rule.actions[0]);
2073 };
2074 let Expr::ReceiverCall {
2075 receiver,
2076 name,
2077 args,
2078 ..
2079 } = &expr
2080 else {
2081 panic!("expected receiver call, got {expr:?}");
2082 };
2083 assert_eq!(name, "setMoveSpeed");
2084 assert!(
2085 matches!(receiver.as_ref(), Expr::Name { name, .. } if name == "eventPlayer"),
2086 "receiver must be the eventPlayer name"
2087 );
2088 assert_eq!(args.len(), 1);
2089 assert!(args[0].keyword.is_none(), "positional argument");
2090 assert!(matches!(&args[0].value, Expr::Number { .. }));
2091 }
2092
2093 #[test]
2094 fn parses_keyword_arguments_with_name_spans() {
2095 let program =
2098 parse_ok("rule \"r\":\n @Event global\n wait(time=1)\n debug(g == 1)\n");
2099 let RuleEntry::Rule(rule) = &program.rules[0] else {
2100 panic!("expected rule");
2101 };
2102 let Stmt::Expr { expr, .. } = &rule.actions[0] else {
2103 panic!("expected expression statement");
2104 };
2105 let Expr::Call { args, .. } = expr else {
2106 panic!("expected a call, got {expr:?}");
2107 };
2108 let (keyword, span) = args[0].keyword.as_ref().expect("keyword argument");
2109 assert_eq!(keyword, "time");
2110 assert_eq!(span.start.line, 3);
2111 assert!(matches!(&args[0].value, Expr::Number { .. }));
2112
2113 let Stmt::Expr { expr, .. } = &rule.actions[1] else {
2114 panic!("expected expression statement");
2115 };
2116 let Expr::Call { args, .. } = expr else {
2117 panic!("expected a call, got {expr:?}");
2118 };
2119 assert!(args[0].keyword.is_none(), "comparisons are not keywords");
2120 assert!(matches!(&args[0].value, Expr::Binary { .. }));
2121 }
2122
2123 #[test]
2124 fn non_name_keyword_lhs_is_a_parse_error() {
2125 let errors = parse_err("rule \"r\":\n @Event global\n debug(1 = 2)\n");
2127 assert!(!errors.is_empty());
2128 assert_eq!(errors[0].code, "parse-error");
2129 }
2130
2131 #[test]
2132 fn parses_member_call_on_call_result() {
2133 let program = parse_ok(
2136 "rule \"r\":\n @Event eachPlayer\n getPlayersInRadius(eventPlayer, 10).setStatusEffect(eventPlayer, 30)\n",
2137 );
2138 let RuleEntry::Rule(rule) = &program.rules[0] else {
2139 panic!("expected rule");
2140 };
2141 let Stmt::Expr { expr, .. } = &rule.actions[0] else {
2142 panic!("expected expression statement");
2143 };
2144 let Expr::ReceiverCall {
2145 receiver,
2146 name,
2147 args,
2148 ..
2149 } = &expr
2150 else {
2151 panic!("expected receiver call, got {expr:?}");
2152 };
2153 assert_eq!(name, "setStatusEffect");
2154 assert!(
2155 matches!(receiver.as_ref(), Expr::Call { name, .. } if name == "getPlayersInRadius"),
2156 "receiver must be the preceding call"
2157 );
2158 assert_eq!(args.len(), 2);
2159 }
2160
2161 #[test]
2162 fn member_without_call_is_not_a_call() {
2163 let program =
2166 parse_ok("rule \"r\":\n @Event eachPlayer\n x = eventPlayer.moveSpeed\n");
2167 let RuleEntry::Rule(rule) = &program.rules[0] else {
2168 panic!("expected rule");
2169 };
2170 let Stmt::Assign { value, .. } = &rule.actions[0] else {
2171 panic!("expected assignment");
2172 };
2173 assert!(matches!(
2174 &value,
2175 Expr::Member { member, .. } if member == "moveSpeed"
2176 ));
2177 }
2178
2179 #[test]
2180 fn parses_advanced_rule_annotations_with_source_arguments() {
2181 let program = parse_ok(
2182 "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",
2183 );
2184 let RuleEntry::SubroutineDef { annotations, .. } = &program.rules[0] else {
2185 panic!("expected subroutine");
2186 };
2187 assert_eq!(annotations.len(), 2);
2188 let RuleEntry::Rule(rule) = &program.rules[1] else {
2189 panic!("expected rule");
2190 };
2191 assert!(rule.disabled);
2192 assert!(rule.delimiter);
2193 assert_eq!(rule.new_page.as_deref(), Some("Page"));
2194 assert_eq!(rule.annotations.len(), 7);
2195 assert_eq!(rule.annotations[1].args[0].text, "1");
2196 assert_eq!(rule.annotations[2].args[0].text, "dmon");
2197 }
2198}