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