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