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