1use crate::cst::{
11 Annotation, AnnotationArg, CallArg, Decl, DictEntry, Event, Expr, IfBranch, Program, Rule,
12 RuleEntry, Stmt, SwitchArm, TopLevel,
13};
14use crate::diag::{OpyError, Position, Span};
15use crate::lexer::{Token, TokenKind};
16
17#[derive(Debug, Default)]
19pub struct ParseOutput {
20 pub program: Option<Program>,
22 pub errors: Vec<OpyError>,
24}
25
26pub fn parse(tokens: &[Token]) -> ParseOutput {
28 parse_with_options(tokens, false)
29}
30
31pub fn parse_with_options(tokens: &[Token], allow_macro_redeclaration: bool) -> ParseOutput {
33 let mut parser = Parser {
34 tokens,
35 pos: 0,
36 errors: Vec::new(),
37 allow_macro_redeclaration,
38 last_statement_continued: false,
39 last_colon_body_continued: false,
40 open_if_indents: Vec::new(),
41 };
42 let program = parser.parse_program();
43 if parser.errors.is_empty() {
44 ParseOutput {
45 program: Some(program),
46 errors: Vec::new(),
47 }
48 } else {
49 ParseOutput {
50 program: None,
51 errors: parser.errors,
52 }
53 }
54}
55
56mod declarations;
57mod definitions;
58mod expressions;
59mod statements;
60
61struct Parser<'a> {
62 tokens: &'a [Token],
63 pos: usize,
64 errors: Vec<OpyError>,
65 allow_macro_redeclaration: bool,
66 last_statement_continued: bool,
67 last_colon_body_continued: bool,
68 open_if_indents: Vec<u32>,
70}
71
72fn is_identifier(text: &str) -> bool {
73 !text.is_empty()
74 && text.chars().enumerate().all(|(index, ch)| {
75 if index == 0 {
76 ch.is_ascii_alphabetic() || ch == '_'
77 } else {
78 ch.is_ascii_alphanumeric() || ch == '_'
79 }
80 })
81}
82
83fn unquote_annotation_arg(text: &str) -> String {
84 text.strip_prefix('"')
85 .and_then(|value| value.strip_suffix('"'))
86 .unwrap_or(text)
87 .to_string()
88}
89
90impl Parser<'_> {
91 fn peek(&self) -> &Token {
92 &self.tokens[self.pos.min(self.tokens.len() - 1)]
93 }
94
95 fn peek_kind(&self) -> TokenKind {
96 self.peek().kind
97 }
98
99 fn peek_at(&self, offset: usize) -> &Token {
100 &self.tokens[(self.pos + offset).min(self.tokens.len() - 1)]
101 }
102
103 fn advance(&mut self) -> Token {
104 let token = self.tokens[self.pos.min(self.tokens.len() - 1)].clone();
105 if self.pos < self.tokens.len() - 1 {
106 self.pos += 1;
107 }
108 token
109 }
110
111 fn skip_newlines(&mut self) {
112 while self.peek_kind() == TokenKind::Newline {
113 self.advance();
114 }
115 }
116
117 fn skip_expression_newlines(&mut self) {
118 if self.peek_kind() != TokenKind::Newline {
119 return;
120 }
121 let previous = self.tokens[..self.pos]
122 .iter()
123 .rev()
124 .find(|token| !matches!(token.kind, TokenKind::Indent(_)));
125 let previous_allows_continuation = previous.is_some_and(|token| {
126 matches!(
127 token.kind,
128 TokenKind::LParen
129 | TokenKind::LBracket
130 | TokenKind::LBrace
131 | TokenKind::Comma
132 | TokenKind::Colon
133 | TokenKind::Assign
134 | TokenKind::Plus
135 | TokenKind::Minus
136 | TokenKind::Star
137 | TokenKind::Slash
138 | TokenKind::Percent
139 | TokenKind::DoubleStar
140 | TokenKind::Eq
141 | TokenKind::Ne
142 | TokenKind::Lt
143 | TokenKind::Le
144 | TokenKind::Gt
145 | TokenKind::Ge
146 ) || (token.kind == TokenKind::Ident
147 && matches!(token.text.as_str(), "and" | "or" | "in" | "not" | "if"))
148 });
149 let mut next = self.pos;
150 while self.tokens[next].kind == TokenKind::Newline {
151 next += 1;
152 }
153 let inside_delimiter_group = self.inside_delimiter_group();
154 let next_allows_continuation = matches!(
155 self.tokens[next].kind,
156 TokenKind::Plus
157 | TokenKind::Minus
158 | TokenKind::Star
159 | TokenKind::Slash
160 | TokenKind::Percent
161 | TokenKind::DoubleStar
162 | TokenKind::Eq
163 | TokenKind::Ne
164 | TokenKind::Lt
165 | TokenKind::Le
166 | TokenKind::Gt
167 | TokenKind::Ge
168 ) || (inside_delimiter_group
169 && matches!(
170 self.tokens[next].kind,
171 TokenKind::LParen
172 | TokenKind::LBracket
173 | TokenKind::Dot
174 | TokenKind::RParen
175 | TokenKind::RBracket
176 | TokenKind::RBrace
177 ))
178 || (self.tokens[next].kind == TokenKind::Ident
179 && matches!(
180 self.tokens[next].text.as_str(),
181 "and" | "or" | "in" | "not" | "else"
182 ))
183 || (inside_delimiter_group
184 && self.tokens[next].kind == TokenKind::Ident
185 && matches!(self.tokens[next].text.as_str(), "if" | "for"));
186 if previous_allows_continuation || next_allows_continuation {
187 self.skip_newlines();
188 }
189 }
190
191 fn is_ident(&self, text: &str) -> bool {
192 self.peek_kind() == TokenKind::Ident && self.peek().text == text
193 }
194
195 fn expect_ident(&mut self, what: &str) -> Result<String, ()> {
196 if self.peek_kind() == TokenKind::Ident {
197 Ok(self.advance().text)
198 } else {
199 self.error_at_current(format!("expected {what}"));
200 Err(())
201 }
202 }
203
204 fn expect(&mut self, kind: TokenKind, what: &str) -> Result<Token, ()> {
205 if self.peek_kind() == kind {
206 Ok(self.advance())
207 } else {
208 self.error_at_current(format!("expected {what}"));
209 Err(())
210 }
211 }
212
213 fn error_at_current(&mut self, message: String) {
214 let span = self.peek().span;
215 self.errors.push(OpyError::at("parse-error", message, span));
216 }
217
218 fn parse_program(&mut self) -> Program {
221 let mut declarations = Vec::new();
222 let mut rules = Vec::new();
223 let mut top_level = Vec::new();
224 loop {
225 self.skip_newlines();
226 if self.peek_kind() == TokenKind::Eof {
227 break;
228 }
229 let rule_prefix = if self.peek_kind() == TokenKind::RulePrefixMarker {
230 Some(self.advance().text)
231 } else {
232 None
233 };
234 let declaration_count = declarations.len();
235 let rule_count = rules.len();
236 let ok = self.parse_top_level(&mut declarations, &mut rules, rule_prefix);
237 if ok {
238 if declarations.len() > declaration_count {
239 top_level.push(TopLevel::Declaration(
240 declarations
241 .last()
242 .expect("declaration was appended")
243 .clone(),
244 ));
245 } else if rules.len() > rule_count {
246 top_level.push(TopLevel::Rule(
247 rules.last().expect("rule was appended").clone(),
248 ));
249 }
250 }
251 if !ok {
252 self.recover_line();
253 }
254 }
255 Program {
256 declarations,
257 rules,
258 top_level,
259 settings: None,
260 }
261 }
262
263 fn parse_top_level(
264 &mut self,
265 declarations: &mut Vec<Decl>,
266 rules: &mut Vec<RuleEntry>,
267 rule_prefix: Option<String>,
268 ) -> bool {
269 let token = self.peek();
270 if token.kind == TokenKind::Ident {
271 match token.text.as_str() {
272 "rule" => return self.parse_rule(rules, rule_prefix),
273 "def" => return self.parse_def(rules, rule_prefix),
274 "globalvar" => return self.parse_variable(declarations, true),
275 "playervar" => return self.parse_variable(declarations, false),
276 "subroutine" => return self.parse_subroutine(declarations),
277 "enum" => return self.parse_enum(declarations),
278 "macro" => return self.parse_macro(declarations),
279 _ => {}
280 }
281 }
282 self.error_at_current(format!(
283 "expected a top-level declaration (rule/def/globalvar/playervar/subroutine/enum/macro) but found '{}'",
284 token.text
285 ));
286 false
287 }
288
289 fn recover_line(&mut self) {
291 while self.peek_kind() != TokenKind::Newline && self.peek_kind() != TokenKind::Eof {
292 self.advance();
293 }
294 }
295
296 fn block_indent(&mut self, line_indent: u32) -> Option<u32> {
299 self.skip_newlines();
300 if self.peek_kind() == TokenKind::Eof {
301 self.error_at_current("expected an indented block".to_string());
302 return None;
303 }
304 let indent = self.peek().span.start.col;
305 if indent <= line_indent {
306 self.error_at_current("expected an indented block after ':'".to_string());
307 return None;
308 }
309 Some(indent)
310 }
311
312 fn expect_statement_end(&mut self, what: &str) -> Result<(), ()> {
313 let continued_line = self
314 .tokens
315 .get(self.pos.saturating_sub(1))
316 .is_some_and(|previous| self.peek().span.start.line > previous.span.end.line);
317 self.last_statement_continued = continued_line;
318 if matches!(self.peek_kind(), TokenKind::Newline | TokenKind::Eof) || continued_line {
319 Ok(())
320 } else {
321 self.error_at_current(format!("expected the end of {what}"));
322 Err(())
323 }
324 }
325}
326
327pub(crate) fn parse_expression_fragment(
330 text: &str,
331 file: u32,
332 origin: Position,
333) -> Result<Expr, OpyError> {
334 let mut tokens = crate::lexer::lex(crate::lexer::LexInput {
335 file_id: file,
336 text,
337 })?;
338 for token in &mut tokens {
339 token.span = shift_span(token.span, origin);
340 }
341 let mut parser = Parser {
342 tokens: &tokens,
343 pos: 0,
344 allow_macro_redeclaration: false,
345 errors: Vec::new(),
346 last_statement_continued: false,
347 last_colon_body_continued: false,
348 open_if_indents: Vec::new(),
349 };
350 let expression = parser.parse_expr().map_err(|()| {
351 parser.errors.first().cloned().unwrap_or_else(|| {
352 OpyError::at(
353 "parse-error",
354 "invalid f-string expression",
355 Span::new(file, origin, origin),
356 )
357 })
358 })?;
359 if parser.peek_kind() != TokenKind::Eof {
360 parser.error_at_current("unexpected tokens in f-string interpolation".to_string());
361 }
362 parser.errors.into_iter().next().map_or(Ok(expression), Err)
363}
364
365fn shift_span(span: Span, origin: Position) -> Span {
366 fn shift(position: Position, origin: Position) -> Position {
367 Position::new(
368 origin.line + position.line.saturating_sub(1),
369 if position.line == 1 {
370 origin.col + position.col.saturating_sub(1)
371 } else {
372 position.col
373 },
374 )
375 }
376 Span::new(
377 span.file,
378 shift(span.start, origin),
379 shift(span.end, origin),
380 )
381}
382
383fn decode_string_escape(character: char) -> char {
384 match character {
385 'n' => '\n',
386 't' => '\t',
387 'r' => '\r',
388 '\\' => '\\',
389 '"' => '"',
390 '\'' => '\'',
391 other => other,
392 }
393}
394
395fn is_string_modifier(text: &str) -> bool {
396 matches!(text, "f" | "w" | "l" | "b" | "c" | "t")
397}
398
399#[cfg(test)]
400mod tests {
401 use super::*;
402 use crate::lexer::{LexInput, lex};
403
404 fn parse_ok(text: &str) -> Program {
405 let tokens = lex(LexInput { file_id: 0, text }).unwrap();
406 let output = parse(&tokens);
407 assert!(
408 output.errors.is_empty(),
409 "unexpected errors: {:?}",
410 output.errors
411 );
412 output.program.unwrap()
413 }
414
415 fn parse_err(text: &str) -> Vec<OpyError> {
416 let tokens = lex(LexInput { file_id: 0, text }).unwrap();
417 parse(&tokens).errors
418 }
419
420 #[test]
421 fn parses_basic_rule() {
422 let program = parse_ok("rule \"setup\":\n @Event global\n disableInspector()\n");
423 assert_eq!(program.rules.len(), 1);
424 let RuleEntry::Rule(rule) = &program.rules[0] else {
425 panic!("expected rule");
426 };
427 assert_eq!(rule.name, "setup");
428 assert_eq!(rule.event.name, "global");
429 assert_eq!(rule.actions.len(), 1);
430 }
431
432 #[test]
433 fn parses_power_augmented_assignment() {
434 let program =
437 parse_ok("globalvar a\nrule \"r\":\n @Event global\n a = 2\n a **= 3\n");
438 let RuleEntry::Rule(rule) = &program.rules[0] else {
439 panic!("expected rule");
440 };
441 let Stmt::Assign {
442 value,
443 target: assigned_target,
444 ..
445 } = &rule.actions[1]
446 else {
447 panic!("expected an assignment");
448 };
449 let Expr::Binary {
450 op, left, right, ..
451 } = value
452 else {
453 panic!("expected a binary modification, got {value:?}");
454 };
455 assert_eq!(op, "**");
456 assert!(matches!(&**left, Expr::Name { .. }));
457 assert!(matches!(
458 assigned_target,
459 Expr::Name { name, .. } if name == "a"
460 ));
461 assert!(matches!(right.as_ref(), Expr::Number { .. }));
462 }
463
464 #[test]
465 fn parses_postfix_increment_and_decrement_as_modifications() {
466 let program =
467 parse_ok("globalvar value\nrule \"r\":\n @Event global\n value++\n value--\n");
468 let RuleEntry::Rule(rule) = &program.rules[0] else {
469 panic!("expected a rule");
470 };
471 for (statement, expected_op) in [(&rule.actions[0], "+"), (&rule.actions[1], "-")] {
472 let Stmt::Assign { target, value, .. } = statement else {
473 panic!("expected a postfix assignment");
474 };
475 let Expr::Binary {
476 op, left, right, ..
477 } = value
478 else {
479 panic!("expected a synthetic modification value");
480 };
481 assert_eq!(op, expected_op);
482 let Expr::Name {
483 name: left_name, ..
484 } = left.as_ref()
485 else {
486 panic!("expected the target to be the modification's left operand");
487 };
488 let Expr::Name {
489 name: target_name, ..
490 } = target
491 else {
492 panic!("expected a name target");
493 };
494 assert_eq!(left_name, target_name);
495 assert!(
496 matches!(right.as_ref(), Expr::Number { value, text, .. } if *value == 1.0 && text == "1")
497 );
498 }
499 }
500
501 #[test]
502 fn rejects_prefix_increment_and_embedded_postfix_forms() {
503 for source in [
504 "globalvar value\nrule \"r\":\n @Event global\n ++value\n",
505 "globalvar value\nrule \"r\":\n @Event global\n value++++\n",
506 ] {
507 let errors = parse_err(source);
508 assert!(!errors.is_empty());
509 assert!(errors.iter().all(|error| error.code == "parse-error"));
510 assert!(errors.iter().all(|error| error.span.is_some()));
511 }
512 }
513
514 #[test]
515 fn preserves_consecutive_unary_minus_expressions() {
516 let source = concat!(
517 "globalvar value = 0\n",
518 "globalvar B = 1\n",
519 "rule \"r\":\n",
520 " @Event global\n",
521 " value = --1\n",
522 " value = --B\n",
523 " value = B--1\n",
524 );
525 parse_ok(source);
526 }
527
528 #[test]
529 fn parses_control_flow() {
530 let program = parse_ok(
531 "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",
532 );
533 let RuleEntry::Rule(rule) = &program.rules[0] else {
534 panic!();
535 };
536 assert!(matches!(rule.actions[0], Stmt::For { .. }));
537 let Stmt::For { body, .. } = &rule.actions[0] else {
538 panic!();
539 };
540 let Stmt::If {
541 branches, r#else, ..
542 } = &body[0]
543 else {
544 panic!();
545 };
546 assert_eq!(branches.len(), 2);
547 assert!(r#else.is_some());
548 let Stmt::While { body, .. } = &rule.actions[1] else {
549 panic!();
550 };
551 assert_eq!(body.len(), 2);
552 }
553
554 #[test]
555 fn parses_source_statement_surface() {
556 let program = parse_ok(concat!(
557 "globalvar value\n",
558 "rule \"r\":\n",
559 " @Event global\n",
560 " del value[1]\n",
561 " value min= 2\n",
562 " value max= 3\n",
563 " while value < 4:\n",
564 " continue\n",
565 " goto RULE_START\n",
566 " goto target\n",
567 " goto loc + value\n",
568 " target:\n",
569 ));
570 let RuleEntry::Rule(rule) = &program.rules[0] else {
571 panic!("expected rule");
572 };
573 assert!(matches!(rule.actions[0], Stmt::Delete { .. }));
574 for (statement, expected) in [(&rule.actions[1], "min"), (&rule.actions[2], "max")] {
575 let Stmt::Assign { value, .. } = statement else {
576 panic!("expected augmented assignment");
577 };
578 assert!(matches!(value, Expr::Binary { op, .. } if op == expected));
579 }
580 let Stmt::While { body, .. } = &rule.actions[3] else {
581 panic!("expected while");
582 };
583 assert!(matches!(body.as_slice(), [Stmt::Continue { .. }]));
584 assert!(matches!(
585 &rule.actions[4],
586 Stmt::Goto {
587 label: None,
588 offset: None,
589 rule_start: true,
590 ..
591 }
592 ));
593 assert!(matches!(
594 &rule.actions[5],
595 Stmt::Goto {
596 label: Some(label),
597 offset: None,
598 rule_start: false,
599 ..
600 } if label == "target"
601 ));
602 assert!(matches!(
603 &rule.actions[6],
604 Stmt::Goto {
605 label: None,
606 offset: Some(_),
607 rule_start: false,
608 ..
609 }
610 ));
611 assert!(matches!(&rule.actions[7], Stmt::Label { name, .. } if name == "target"));
612 }
613
614 #[test]
615 fn rejects_invalid_source_statement_forms() {
616 for source in [
617 "rule \"r\":\n @Event global\n del value\n",
618 "rule \"r\":\n @Event global\n goto\n",
619 "rule \"r\":\n @Event global\n goto loc\n",
620 "rule \"r\":\n @Event global\n goto target extra\n",
621 "rule \"r\":\n @Event global\n continue now\n",
622 "rule \"r\":\n @Event global\n A = 1; A = 2\n",
623 ] {
624 let errors = parse_err(source);
625 assert!(!errors.is_empty(), "invalid form parsed: {source}");
626 assert!(errors.iter().all(|error| error.code == "parse-error"));
627 assert!(errors.iter().all(|error| error.span.is_some()));
628 }
629 }
630
631 #[test]
632 fn rejects_continued_inline_if_without_else() {
633 let errors = parse_err(concat!(
634 "globalvar value\n",
635 "macro nextHero():\n",
636 " value = 1\\\n",
637 " if value == 0: value = 2\\\n",
638 " value = 3\\\n",
639 ));
640 assert!(errors.iter().any(|error| {
641 error.code == "parse-error" && error.message == "Found 'if', but no 'else'"
642 }));
643 }
644
645 #[test]
646 fn parses_syntax_constructs() {
647 let program = parse_ok(
648 "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",
649 );
650 let RuleEntry::Rule(rule) = &program.rules[0] else {
651 panic!("expected rule");
652 };
653 assert!(matches!(rule.actions[0], Stmt::Switch { .. }));
654 assert!(matches!(rule.actions[1], Stmt::Assign { .. }));
655 }
656
657 #[test]
658 fn rejects_incomplete_do_while_and_dictionary_entries() {
659 let errors = parse_err(
660 "rule \"r\":\n @Event global\n do:\n pass\n while\n x = {\"x\"}\n",
661 );
662 assert!(!errors.is_empty());
663 assert!(errors.iter().all(|error| error.code == "parse-error"));
664 }
665
666 #[test]
667 fn parses_explicit_enum_member_values() {
668 let program = parse_ok("enum EventType:\n BUFF = 0\n DEBUFF\n MECH = 2\n");
669 let Decl::Enum { members, .. } = &program.declarations[0] else {
670 panic!("expected enum");
671 };
672 assert_eq!(
673 members
674 .iter()
675 .map(|(name, _)| name.as_str())
676 .collect::<Vec<_>>(),
677 ["BUFF", "DEBUFF", "MECH"]
678 );
679 }
680
681 #[test]
682 fn parses_multi_line_array() {
683 let program = parse_ok(
684 "globalvar p\nrule \"r\":\n @Event global\n p = [\n vect(1, 0, 0),\n vect(2, 0, 0),\n ]\n",
685 );
686 let RuleEntry::Rule(rule) = &program.rules[0] else {
687 panic!();
688 };
689 let Stmt::Assign { value, .. } = &rule.actions[0] else {
690 panic!();
691 };
692 let Expr::Array { elements, .. } = value else {
693 panic!("expected array, got {value:?}");
694 };
695 assert_eq!(elements.len(), 2);
696 }
697
698 #[test]
699 fn missing_colon_is_a_structured_error() {
700 let errors = parse_err("rule \"x\"\n @Event global\n");
701 assert!(!errors.is_empty());
702 assert_eq!(errors[0].code, "parse-error");
703 assert!(errors[0].span.is_some());
704 }
705
706 #[test]
707 fn def_and_macro_parse() {
708 let program = parse_ok(
709 "subroutine showStatus\n\nmacro VERSION = \"1.4.3\"\n\ndef showStatus():\n print(\"hi\")\n\nmacro double(value):\n value + value\n",
710 );
711 assert_eq!(program.declarations.len(), 3);
712 assert!(matches!(program.declarations[1], Decl::Constant { .. }));
713 assert!(matches!(program.declarations[2], Decl::Macro { .. }));
714 let Decl::Macro { args, body, .. } = &program.declarations[2] else {
715 panic!();
716 };
717 assert_eq!(args, &vec!["value".to_string()]);
718 assert_eq!(body.len(), 1);
719 }
720
721 #[test]
722 fn macro_and_enum_redeclarations_are_checked_at_ast_surfaces() {
723 let text = "enum Kind:\n First\n First\nmacro helper():\n pass\nmacro helper():\n pass\n";
724 let errors = parse_err(text);
725 assert_eq!(
726 errors
727 .iter()
728 .filter(|error| error.code == "macro-redeclaration")
729 .count(),
730 2
731 );
732
733 let tokens = lex(LexInput { file_id: 0, text }).unwrap();
734 let output = parse_with_options(&tokens, true);
735 assert!(
736 output.errors.is_empty(),
737 "unexpected errors: {:?}",
738 output.errors
739 );
740 assert!(output.program.is_some());
741 }
742
743 #[test]
744 fn multiple_errors_are_reported() {
745 let errors =
746 parse_err("rule \"a\"\n bad statement here\nrule \"b\"\n @Event global\n");
747 assert!(!errors.is_empty());
748 }
749
750 #[test]
751 fn precedence_parses_python_like() {
752 let program = parse_ok("globalvar x\nrule \"r\":\n @Event global\n x = 1 + 2 * 3\n");
753 let RuleEntry::Rule(rule) = &program.rules[0] else {
754 panic!();
755 };
756 let Stmt::Assign { value, .. } = &rule.actions[0] else {
757 panic!();
758 };
759 let Expr::Binary {
760 op, left, right, ..
761 } = value
762 else {
763 panic!();
764 };
765 assert_eq!(op, "+");
766 let Expr::Binary { op: inner, .. } = right.as_ref() else {
767 panic!();
768 };
769 assert_eq!(inner, "*");
770 assert!(matches!(left.as_ref(), Expr::Number { .. }));
771 }
772
773 #[test]
774 fn parses_right_associative_conditional_expressions() {
775 let program = parse_ok(
776 "rule \"r\":\n @Event global\n debug(1 if true else 2 if false else 3)\n",
777 );
778 let RuleEntry::Rule(rule) = &program.rules[0] else {
779 panic!("expected a rule");
780 };
781 let Stmt::Expr { expr, .. } = &rule.actions[0] else {
782 panic!("expected an expression statement");
783 };
784 let Expr::Call { args, .. } = expr else {
785 panic!("expected a call");
786 };
787 let Expr::Conditional {
788 then_value,
789 condition,
790 else_value,
791 span,
792 } = &args[0].value
793 else {
794 panic!("expected a conditional expression");
795 };
796 assert!(matches!(then_value.as_ref(), Expr::Number { value, .. } if *value == 1.0));
797 assert!(matches!(condition.as_ref(), Expr::Bool { value: true, .. }));
798 assert!(matches!(
799 else_value.as_ref(),
800 Expr::Conditional { then_value, condition, else_value, .. }
801 if matches!(then_value.as_ref(), Expr::Number { value, .. } if *value == 2.0)
802 && matches!(condition.as_ref(), Expr::Bool { value: false, .. })
803 && matches!(else_value.as_ref(), Expr::Number { value, .. } if *value == 3.0)
804 ));
805 assert_eq!(span.start.line, 3);
806 assert_eq!(span.start.col, 11);
807 }
808
809 #[test]
810 fn parses_parenthesized_nested_conditional_and_rejects_missing_else() {
811 let program = parse_ok(
812 "rule \"r\":\n @Event global\n debug((1 if true else 2) if false else 3)\n",
813 );
814 let RuleEntry::Rule(rule) = &program.rules[0] else {
815 panic!("expected a rule");
816 };
817 let Stmt::Expr { expr, .. } = &rule.actions[0] else {
818 panic!("expected an expression statement");
819 };
820 let Expr::Call { args, .. } = expr else {
821 panic!("expected a call");
822 };
823 assert!(matches!(
824 &args[0].value,
825 Expr::Conditional {
826 then_value,
827 condition,
828 else_value,
829 ..
830 } if matches!(then_value.as_ref(), Expr::Conditional { .. })
831 && matches!(condition.as_ref(), Expr::Bool { value: false, .. })
832 && matches!(else_value.as_ref(), Expr::Number { value, .. } if *value == 3.0)
833 ));
834
835 let errors = parse_err("rule \"r\":\n @Event global\n debug(1 if true)\n");
836 assert_eq!(errors[0].code, "parse-error");
837 assert!(errors[0].message.contains("expected `else`"));
838 }
839
840 #[test]
841 fn parses_receiver_calls() {
842 let program =
845 parse_ok("rule \"r\":\n @Event eachPlayer\n eventPlayer.setMoveSpeed(100)\n");
846 let RuleEntry::Rule(rule) = &program.rules[0] else {
847 panic!("expected rule");
848 };
849 let Stmt::Expr { expr, .. } = &rule.actions[0] else {
850 panic!("expected expression statement, got {:?}", rule.actions[0]);
851 };
852 let Expr::ReceiverCall {
853 receiver,
854 name,
855 args,
856 ..
857 } = &expr
858 else {
859 panic!("expected receiver call, got {expr:?}");
860 };
861 assert_eq!(name, "setMoveSpeed");
862 assert!(
863 matches!(receiver.as_ref(), Expr::Name { name, .. } if name == "eventPlayer"),
864 "receiver must be the eventPlayer name"
865 );
866 assert_eq!(args.len(), 1);
867 assert!(args[0].keyword.is_none(), "positional argument");
868 assert!(matches!(&args[0].value, Expr::Number { .. }));
869 }
870
871 #[test]
872 fn parses_keyword_arguments_with_name_spans() {
873 let program =
876 parse_ok("rule \"r\":\n @Event global\n wait(time=1)\n debug(g == 1)\n");
877 let RuleEntry::Rule(rule) = &program.rules[0] else {
878 panic!("expected rule");
879 };
880 let Stmt::Expr { expr, .. } = &rule.actions[0] else {
881 panic!("expected expression statement");
882 };
883 let Expr::Call { args, .. } = expr else {
884 panic!("expected a call, got {expr:?}");
885 };
886 let (keyword, span) = args[0].keyword.as_ref().expect("keyword argument");
887 assert_eq!(keyword, "time");
888 assert_eq!(span.start.line, 3);
889 assert!(matches!(&args[0].value, Expr::Number { .. }));
890
891 let Stmt::Expr { expr, .. } = &rule.actions[1] else {
892 panic!("expected expression statement");
893 };
894 let Expr::Call { args, .. } = expr else {
895 panic!("expected a call, got {expr:?}");
896 };
897 assert!(args[0].keyword.is_none(), "comparisons are not keywords");
898 assert!(matches!(&args[0].value, Expr::Binary { .. }));
899 }
900
901 #[test]
902 fn adjacent_string_literals_concatenate_and_preserve_span() {
903 let program = parse_ok("rule \"r\":\n @Event global\n debug(\"one\" \"two\")\n");
904 let RuleEntry::Rule(rule) = &program.rules[0] else {
905 panic!("expected rule");
906 };
907 let Stmt::Expr { expr, .. } = &rule.actions[0] else {
908 panic!("expected expression statement");
909 };
910 let Expr::Call { args, .. } = expr else {
911 panic!("expected call");
912 };
913 let Expr::String { value, span } = &args[0].value else {
914 panic!("expected concatenated string");
915 };
916 assert_eq!(value, "onetwo");
917 assert_eq!(span.start.line, 3);
918 assert_eq!(span.start.col, 11);
919 assert_eq!(span.end.col, 22);
920 }
921
922 #[test]
923 fn multiline_adjacent_string_literals_concatenate_inside_group() {
924 let program =
925 parse_ok("rule \"r\":\n @Event global\n debug(\"one\"\n \"two\")\n");
926 let RuleEntry::Rule(rule) = &program.rules[0] else {
927 panic!("expected rule");
928 };
929 let Stmt::Expr { expr, .. } = &rule.actions[0] else {
930 panic!("expected expression statement");
931 };
932 let Expr::Call { args, .. } = expr else {
933 panic!("expected call");
934 };
935 assert!(matches!(
936 &args[0].value,
937 Expr::String { value, .. } if value == "onetwo"
938 ));
939 }
940
941 #[test]
942 fn newline_outside_group_keeps_adjacent_literals_as_statements() {
943 let program = parse_ok("rule \"r\":\n @Event global\n \"one\"\n \"two\"\n");
944 let RuleEntry::Rule(rule) = &program.rules[0] else {
945 panic!("expected rule");
946 };
947 assert_eq!(rule.actions.len(), 2);
948 }
949
950 #[test]
951 fn non_name_keyword_lhs_is_a_parse_error() {
952 let errors = parse_err("rule \"r\":\n @Event global\n debug(1 = 2)\n");
954 assert!(!errors.is_empty());
955 assert_eq!(errors[0].code, "parse-error");
956 }
957
958 #[test]
959 fn parses_member_call_on_call_result() {
960 let program = parse_ok(
963 "rule \"r\":\n @Event eachPlayer\n getPlayersInRadius(eventPlayer, 10).setStatusEffect(eventPlayer, 30)\n",
964 );
965 let RuleEntry::Rule(rule) = &program.rules[0] else {
966 panic!("expected rule");
967 };
968 let Stmt::Expr { expr, .. } = &rule.actions[0] else {
969 panic!("expected expression statement");
970 };
971 let Expr::ReceiverCall {
972 receiver,
973 name,
974 args,
975 ..
976 } = &expr
977 else {
978 panic!("expected receiver call, got {expr:?}");
979 };
980 assert_eq!(name, "setStatusEffect");
981 assert!(
982 matches!(receiver.as_ref(), Expr::Call { name, .. } if name == "getPlayersInRadius"),
983 "receiver must be the preceding call"
984 );
985 assert_eq!(args.len(), 2);
986 }
987
988 #[test]
989 fn member_without_call_is_not_a_call() {
990 let program =
993 parse_ok("rule \"r\":\n @Event eachPlayer\n x = eventPlayer.moveSpeed\n");
994 let RuleEntry::Rule(rule) = &program.rules[0] else {
995 panic!("expected rule");
996 };
997 let Stmt::Assign { value, .. } = &rule.actions[0] else {
998 panic!("expected assignment");
999 };
1000 assert!(matches!(
1001 &value,
1002 Expr::Member { member, .. } if member == "moveSpeed"
1003 ));
1004 }
1005
1006 #[test]
1007 fn parses_advanced_rule_annotations_with_source_arguments() {
1008 let program = parse_ok(
1009 "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",
1010 );
1011 let RuleEntry::SubroutineDef { annotations, .. } = &program.rules[0] else {
1012 panic!("expected subroutine");
1013 };
1014 assert_eq!(annotations.len(), 2);
1015 let RuleEntry::Rule(rule) = &program.rules[1] else {
1016 panic!("expected rule");
1017 };
1018 assert!(rule.disabled);
1019 assert!(rule.delimiter);
1020 assert_eq!(rule.new_page.as_deref(), Some("Page"));
1021 assert_eq!(rule.annotations.len(), 7);
1022 assert_eq!(rule.annotations[1].args[0].text, "1");
1023 assert_eq!(rule.annotations[2].args[0].text, "dmon");
1024 }
1025
1026 #[test]
1027 fn decodes_unicode_escapes_in_names_and_ordinary_strings() {
1028 let program = parse_ok(
1029 r#"subroutine helper
1030def helper():
1031 @Name "helper\ufeffname"
1032 pass
1033rule "pa\ufeffssed":
1034 @Event global
1035 debug("pa\ufeffssed")
1036 debug(f"pa\ufeffssed {1}")
1037"#,
1038 );
1039 let RuleEntry::SubroutineDef { annotations, .. } = &program.rules[0] else {
1040 panic!("expected subroutine");
1041 };
1042 assert_eq!(annotations[0].args[0].text, "\"helper\u{feff}name\"");
1043
1044 let RuleEntry::Rule(rule) = &program.rules[1] else {
1045 panic!("expected rule");
1046 };
1047 assert_eq!(rule.name, "pa\u{feff}ssed");
1048 assert_eq!(rule.name_span.start.col, 7);
1049 assert_eq!(rule.name_span.end.col, 19);
1050 let Stmt::Expr { expr, .. } = &rule.actions[0] else {
1051 panic!("expected debug expression");
1052 };
1053 let Expr::Call { args, .. } = expr else {
1054 panic!("expected debug call");
1055 };
1056 assert!(matches!(
1057 &args[0].value,
1058 Expr::String { value, .. } if value == "pa\u{feff}ssed"
1059 ));
1060 let Stmt::Expr { expr, .. } = &rule.actions[1] else {
1061 panic!("expected formatted debug expression");
1062 };
1063 let Expr::Call { args, .. } = expr else {
1064 panic!("expected formatted debug call");
1065 };
1066 assert!(matches!(
1067 &args[0].value,
1068 Expr::StringModifier {
1069 format_text: Some(text), ..
1070 } if text == "pa\u{feff}ssed {0}"
1071 ));
1072 }
1073}