1use crate::ast::*;
2use crate::error::{ErrorKind, PerlError, PerlResult};
3use crate::interpreter::Interpreter;
4use crate::lexer::{Lexer, LITERAL_DOLLAR_IN_DQUOTE};
5use crate::token::Token;
6
7fn postfix_lbracket_is_arrow_container(expr: &Expr) -> bool {
10 matches!(
11 expr.kind,
12 ExprKind::ArrayElement { .. }
13 | ExprKind::HashElement { .. }
14 | ExprKind::ArrowDeref { .. }
15 | ExprKind::Deref {
16 kind: Sigil::Scalar,
17 ..
18 }
19 )
20}
21
22fn destructure_stmt_from_var_decls(keyword: &str, decls: Vec<VarDecl>, line: usize) -> Statement {
23 let kind = match keyword {
24 "my" => StmtKind::My(decls),
25 "mysync" => StmtKind::MySync(decls),
26 "our" => StmtKind::Our(decls),
27 "local" => StmtKind::Local(decls),
28 "state" => StmtKind::State(decls),
29 _ => unreachable!("parse_my_our_local keyword"),
30 };
31 Statement {
32 label: None,
33 kind,
34 line,
35 }
36}
37
38fn destructure_stmt_die_string(line: usize, msg: &str) -> Statement {
39 Statement {
40 label: None,
41 kind: StmtKind::Expression(Expr {
42 kind: ExprKind::Die(vec![Expr {
43 kind: ExprKind::String(msg.to_string()),
44 line,
45 }]),
46 line,
47 }),
48 line,
49 }
50}
51
52fn destructure_stmt_unless_die(line: usize, cond: Expr, msg: &str) -> Statement {
53 Statement {
54 label: None,
55 kind: StmtKind::Unless {
56 condition: cond,
57 body: vec![destructure_stmt_die_string(line, msg)],
58 else_block: None,
59 },
60 line,
61 }
62}
63
64fn destructure_expr_scalar_tmp(name: &str, line: usize) -> Expr {
65 Expr {
66 kind: ExprKind::ScalarVar(name.to_string()),
67 line,
68 }
69}
70
71fn destructure_expr_array_len(tmp: &str, line: usize) -> Expr {
72 Expr {
73 kind: ExprKind::Deref {
74 expr: Box::new(destructure_expr_scalar_tmp(tmp, line)),
75 kind: Sigil::Array,
76 },
77 line,
78 }
79}
80
81pub struct Parser {
82 tokens: Vec<(Token, usize)>,
83 pos: usize,
84 next_rate_limit_slot: u32,
86 suppress_indirect_paren_call: u32,
89 pipe_rhs_depth: u32,
95 no_pipe_forward_depth: u32,
103 suppress_scalar_hash_brace: u32,
106 next_desugar_tmp: u32,
108 error_file: String,
110 declared_subs: std::collections::HashSet<String>,
112 suppress_parenless_call: u32,
116 suppress_slash_as_div: u32,
119 pub suppress_m_regex: u32,
122 suppress_colon_range: u32,
126 thread_last_mode: bool,
129}
130
131impl Parser {
132 pub fn new(tokens: Vec<(Token, usize)>) -> Self {
133 Self::new_with_file(tokens, "-e")
134 }
135
136 pub fn new_with_file(tokens: Vec<(Token, usize)>, file: impl Into<String>) -> Self {
137 Self {
138 tokens,
139 pos: 0,
140 next_rate_limit_slot: 0,
141 suppress_indirect_paren_call: 0,
142 pipe_rhs_depth: 0,
143 no_pipe_forward_depth: 0,
144 suppress_scalar_hash_brace: 0,
145 next_desugar_tmp: 0,
146 error_file: file.into(),
147 declared_subs: std::collections::HashSet::new(),
148 suppress_parenless_call: 0,
149 suppress_slash_as_div: 0,
150 suppress_m_regex: 0,
151 suppress_colon_range: 0,
152 thread_last_mode: false,
153 }
154 }
155
156 fn alloc_desugar_tmp(&mut self) -> u32 {
157 let n = self.next_desugar_tmp;
158 self.next_desugar_tmp = self.next_desugar_tmp.saturating_add(1);
159 n
160 }
161
162 #[inline]
166 fn in_pipe_rhs(&self) -> bool {
167 self.pipe_rhs_depth > 0
168 }
169
170 fn pipe_supplies_slurped_list_operand(&self) -> bool {
173 self.in_pipe_rhs()
174 && (matches!(
175 self.peek(),
176 Token::Semicolon
177 | Token::RBrace
178 | Token::RParen
179 | Token::Eof
180 | Token::Comma
181 | Token::PipeForward
182 ) || self.peek_line() > self.prev_line())
183 }
184
185 #[inline]
190 fn pipe_placeholder_list(&self, line: usize) -> Expr {
191 Expr {
192 kind: ExprKind::List(vec![]),
193 line,
194 }
195 }
196
197 fn lift_bareword_to_topic_call(expr: Expr) -> Expr {
208 let line = expr.line;
209 let topic = || Expr {
210 kind: ExprKind::ScalarVar("_".into()),
211 line,
212 };
213 match expr.kind {
214 ExprKind::Bareword(ref name) => Expr {
215 kind: ExprKind::FuncCall {
216 name: name.clone(),
217 args: vec![topic()],
218 },
219 line,
220 },
221 ExprKind::Unlink(ref args) if args.is_empty() => Expr {
223 kind: ExprKind::Unlink(vec![topic()]),
224 line,
225 },
226 ExprKind::Chmod(ref args) if args.is_empty() => Expr {
227 kind: ExprKind::Chmod(vec![topic()]),
228 line,
229 },
230 ExprKind::Stat(_) => expr,
232 ExprKind::Lstat(_) => expr,
233 ExprKind::Readlink(_) => expr,
234 ExprKind::Rev(ref inner) => {
236 if matches!(inner.kind, ExprKind::List(ref v) if v.is_empty()) {
237 Expr {
238 kind: ExprKind::Rev(Box::new(topic())),
239 line,
240 }
241 } else {
242 expr
243 }
244 }
245 _ => expr,
246 }
247 }
248
249 fn parse_assign_expr_stop_at_pipe(&mut self) -> PerlResult<Expr> {
257 self.no_pipe_forward_depth = self.no_pipe_forward_depth.saturating_add(1);
258 let r = self.parse_assign_expr();
259 self.no_pipe_forward_depth = self.no_pipe_forward_depth.saturating_sub(1);
260 r
261 }
262
263 fn syntax_err(&self, message: impl Into<String>, line: usize) -> PerlError {
264 PerlError::new(ErrorKind::Syntax, message, line, self.error_file.clone())
265 }
266
267 fn alloc_rate_limit_slot(&mut self) -> u32 {
268 let s = self.next_rate_limit_slot;
269 self.next_rate_limit_slot = self.next_rate_limit_slot.saturating_add(1);
270 s
271 }
272
273 fn peek(&self) -> &Token {
274 self.tokens
275 .get(self.pos)
276 .map(|(t, _)| t)
277 .unwrap_or(&Token::Eof)
278 }
279
280 fn peek_line(&self) -> usize {
281 self.tokens.get(self.pos).map(|(_, l)| *l).unwrap_or(0)
282 }
283
284 fn peek_at(&self, offset: usize) -> &Token {
285 self.tokens
286 .get(self.pos + offset)
287 .map(|(t, _)| t)
288 .unwrap_or(&Token::Eof)
289 }
290
291 fn advance(&mut self) -> (Token, usize) {
292 let tok = self
293 .tokens
294 .get(self.pos)
295 .cloned()
296 .unwrap_or((Token::Eof, 0));
297 self.pos += 1;
298 tok
299 }
300
301 fn prev_line(&self) -> usize {
303 if self.pos > 0 {
304 self.tokens.get(self.pos - 1).map(|(_, l)| *l).unwrap_or(0)
305 } else {
306 0
307 }
308 }
309
310 fn looks_like_hashref(&self) -> bool {
319 debug_assert!(matches!(self.peek(), Token::LBrace));
320 let tok1 = self.peek_at(1);
321 let tok2 = self.peek_at(2);
322 match tok1 {
323 Token::RBrace => true,
324 Token::Ident(_)
325 | Token::SingleString(_)
326 | Token::DoubleString(_)
327 | Token::ScalarVar(_)
328 | Token::Integer(_) => matches!(tok2, Token::FatArrow),
329 Token::HashVar(_) => matches!(tok2, Token::RBrace | Token::Comma),
330 _ => false,
331 }
332 }
333
334 fn expect(&mut self, expected: &Token) -> PerlResult<usize> {
335 let (tok, line) = self.advance();
336 if std::mem::discriminant(&tok) == std::mem::discriminant(expected) {
337 Ok(line)
338 } else {
339 Err(self.syntax_err(format!("Expected {:?}, got {:?}", expected, tok), line))
340 }
341 }
342
343 fn eat(&mut self, expected: &Token) -> bool {
344 if std::mem::discriminant(self.peek()) == std::mem::discriminant(expected) {
345 self.advance();
346 true
347 } else {
348 false
349 }
350 }
351
352 fn at_eof(&self) -> bool {
353 matches!(self.peek(), Token::Eof)
354 }
355
356 fn filetest_allows_implicit_topic(tok: &Token) -> bool {
358 matches!(
359 tok,
360 Token::RParen
361 | Token::Semicolon
362 | Token::Comma
363 | Token::RBrace
364 | Token::Eof
365 | Token::LogAnd
366 | Token::LogOr
367 | Token::LogAndWord
368 | Token::LogOrWord
369 | Token::PipeForward
370 )
371 }
372
373 fn next_is_new_stmt_keyword(&self, stmt_line: usize) -> bool {
377 if crate::compat_mode() {
379 return false;
380 }
381 if self.peek_line() == stmt_line {
382 return false;
383 }
384 matches!(
385 self.peek(),
386 Token::Ident(ref kw) if matches!(kw.as_str(),
387 "use" | "no" | "my" | "our" | "local" | "sub" | "struct" | "enum"
388 | "if" | "unless" | "while" | "until" | "for" | "foreach"
389 | "return" | "last" | "next" | "redo" | "package" | "require"
390 | "BEGIN" | "END" | "UNITCHECK" | "frozen" | "const" | "typed"
391 )
392 )
393 }
394
395 fn next_is_new_statement_start(&self, stmt_line: usize) -> bool {
399 if crate::compat_mode() {
400 return false;
401 }
402 if self.peek_line() == stmt_line {
403 return false;
404 }
405 matches!(
406 self.peek(),
407 Token::ScalarVar(_)
408 | Token::DerefScalarVar(_)
409 | Token::ArrayVar(_)
410 | Token::HashVar(_)
411 | Token::LBrace
412 ) || self.next_is_new_stmt_keyword(stmt_line)
413 }
414
415 pub fn parse_program(&mut self) -> PerlResult<Program> {
418 let statements = self.parse_statements()?;
419 Ok(Program { statements })
420 }
421
422 pub fn parse_statements(&mut self) -> PerlResult<Vec<Statement>> {
424 let mut statements = Vec::new();
425 while !self.at_eof() {
426 if matches!(self.peek(), Token::Semicolon) {
427 let line = self.peek_line();
428 self.advance();
429 statements.push(Statement {
430 label: None,
431 kind: StmtKind::Empty,
432 line,
433 });
434 continue;
435 }
436 statements.push(self.parse_statement()?);
437 }
438 Ok(statements)
439 }
440
441 fn parse_statement(&mut self) -> PerlResult<Statement> {
444 let line = self.peek_line();
445
446 let label = match self.peek().clone() {
449 Token::Ident(_) => {
450 if matches!(self.peek_at(1), Token::Colon)
451 && !matches!(self.peek_at(2), Token::Colon)
452 {
453 let (tok, _) = self.advance();
454 let l = match tok {
455 Token::Ident(l) => l,
456 _ => unreachable!(),
457 };
458 self.advance(); Some(l)
460 } else {
461 None
462 }
463 }
464 _ => None,
465 };
466
467 let mut stmt = match self.peek().clone() {
468 Token::FormatDecl { .. } => {
469 let tok_line = self.peek_line();
470 let (tok, _) = self.advance();
471 match tok {
472 Token::FormatDecl { name, lines } => Statement {
473 label: label.clone(),
474 kind: StmtKind::FormatDecl { name, lines },
475 line: tok_line,
476 },
477 _ => unreachable!(),
478 }
479 }
480 Token::Ident(ref kw) => match kw.as_str() {
481 "if" => self.parse_if()?,
482 "unless" => self.parse_unless()?,
483 "while" => {
484 let mut s = self.parse_while()?;
485 if let StmtKind::While {
486 label: ref mut lbl, ..
487 } = s.kind
488 {
489 *lbl = label.clone();
490 }
491 s
492 }
493 "until" => {
494 let mut s = self.parse_until()?;
495 if let StmtKind::Until {
496 label: ref mut lbl, ..
497 } = s.kind
498 {
499 *lbl = label.clone();
500 }
501 s
502 }
503 "for" => {
504 let mut s = self.parse_for_or_foreach()?;
505 match s.kind {
506 StmtKind::For {
507 label: ref mut lbl, ..
508 }
509 | StmtKind::Foreach {
510 label: ref mut lbl, ..
511 } => *lbl = label.clone(),
512 _ => {}
513 }
514 s
515 }
516 "foreach" => {
517 let mut s = self.parse_foreach()?;
518 if let StmtKind::Foreach {
519 label: ref mut lbl, ..
520 } = s.kind
521 {
522 *lbl = label.clone();
523 }
524 s
525 }
526 "sub" => {
527 if !crate::compat_mode() {
528 return Err(self.syntax_err(
529 "stryke uses `fn` instead of `sub` (this is not Perl 5)",
530 self.peek_line(),
531 ));
532 }
533 self.parse_sub_decl(true)?
534 }
535 "fn" => self.parse_sub_decl(false)?,
536 "struct" => {
537 if crate::compat_mode() {
538 return Err(self.syntax_err(
539 "`struct` is a stryke extension (disabled by --compat)",
540 self.peek_line(),
541 ));
542 }
543 self.parse_struct_decl()?
544 }
545 "enum" => {
546 if crate::compat_mode() {
547 return Err(self.syntax_err(
548 "`enum` is a stryke extension (disabled by --compat)",
549 self.peek_line(),
550 ));
551 }
552 self.parse_enum_decl()?
553 }
554 "class" => {
555 if crate::compat_mode() {
556 return Err(self.syntax_err(
558 "Perl 5.38 `class` syntax not yet implemented in --compat mode",
559 self.peek_line(),
560 ));
561 }
562 self.parse_class_decl(false, false)?
563 }
564 "abstract" => {
565 self.advance(); if !matches!(self.peek(), Token::Ident(ref s) if s == "class") {
567 return Err(self.syntax_err(
568 "`abstract` must be followed by `class`",
569 self.peek_line(),
570 ));
571 }
572 self.parse_class_decl(true, false)?
573 }
574 "final" => {
575 self.advance(); if !matches!(self.peek(), Token::Ident(ref s) if s == "class") {
577 return Err(self
578 .syntax_err("`final` must be followed by `class`", self.peek_line()));
579 }
580 self.parse_class_decl(false, true)?
581 }
582 "trait" => {
583 if crate::compat_mode() {
584 return Err(self.syntax_err(
585 "`trait` is a stryke extension (disabled by --compat)",
586 self.peek_line(),
587 ));
588 }
589 self.parse_trait_decl()?
590 }
591 "my" => self.parse_my_our_local("my", false)?,
592 "state" => self.parse_my_our_local("state", false)?,
593 "mysync" => {
594 if crate::compat_mode() {
595 return Err(self.syntax_err(
596 "`mysync` is a stryke extension (disabled by --compat)",
597 self.peek_line(),
598 ));
599 }
600 self.parse_my_our_local("mysync", false)?
601 }
602 "frozen" | "const" => {
603 let leading = kw.as_str().to_string();
604 if crate::compat_mode() {
605 return Err(self.syntax_err(
606 format!("`{leading}` is a stryke extension (disabled by --compat)"),
607 self.peek_line(),
608 ));
609 }
610 self.advance(); if let Token::Ident(ref kw) = self.peek().clone() {
616 if kw == "my" {
617 let mut stmt = self.parse_my_our_local("my", false)?;
618 if let StmtKind::My(ref mut decls) = stmt.kind {
619 for decl in decls.iter_mut() {
620 decl.frozen = true;
621 }
622 }
623 stmt
624 } else {
625 return Err(self.syntax_err(
626 format!("Expected 'my' after '{leading}'"),
627 self.peek_line(),
628 ));
629 }
630 } else {
631 return Err(self.syntax_err(
632 format!("Expected 'my' after '{leading}'"),
633 self.peek_line(),
634 ));
635 }
636 }
637 "typed" => {
638 if crate::compat_mode() {
639 return Err(self.syntax_err(
640 "`typed` is a stryke extension (disabled by --compat)",
641 self.peek_line(),
642 ));
643 }
644 self.advance();
645 if let Token::Ident(ref kw) = self.peek().clone() {
646 if kw == "my" {
647 self.parse_my_our_local("my", true)?
648 } else {
649 return Err(
650 self.syntax_err("Expected 'my' after 'typed'", self.peek_line())
651 );
652 }
653 } else {
654 return Err(
655 self.syntax_err("Expected 'my' after 'typed'", self.peek_line())
656 );
657 }
658 }
659 "our" => self.parse_my_our_local("our", false)?,
660 "local" => self.parse_my_our_local("local", false)?,
661 "package" => self.parse_package()?,
662 "use" => self.parse_use()?,
663 "no" => self.parse_no()?,
664 "return" => self.parse_return()?,
665 "last" => {
666 self.advance();
667 let lbl = if let Token::Ident(ref s) = self.peek() {
668 if s.chars().all(|c| c.is_uppercase() || c == '_') {
669 let (Token::Ident(l), _) = self.advance() else {
670 unreachable!()
671 };
672 Some(l)
673 } else {
674 None
675 }
676 } else {
677 None
678 };
679 let stmt = Statement {
680 label: None,
681 kind: StmtKind::Last(lbl.or(label.clone())),
682 line,
683 };
684 self.parse_stmt_postfix_modifier(stmt)?
685 }
686 "next" => {
687 self.advance();
688 let lbl = if let Token::Ident(ref s) = self.peek() {
689 if s.chars().all(|c| c.is_uppercase() || c == '_') {
690 let (Token::Ident(l), _) = self.advance() else {
691 unreachable!()
692 };
693 Some(l)
694 } else {
695 None
696 }
697 } else {
698 None
699 };
700 let stmt = Statement {
701 label: None,
702 kind: StmtKind::Next(lbl.or(label.clone())),
703 line,
704 };
705 self.parse_stmt_postfix_modifier(stmt)?
706 }
707 "redo" => {
708 self.advance();
709 self.eat(&Token::Semicolon);
710 Statement {
711 label: None,
712 kind: StmtKind::Redo(label.clone()),
713 line,
714 }
715 }
716 "BEGIN" => {
717 self.advance();
718 let block = self.parse_block()?;
719 Statement {
720 label: None,
721 kind: StmtKind::Begin(block),
722 line,
723 }
724 }
725 "END" => {
726 self.advance();
727 let block = self.parse_block()?;
728 Statement {
729 label: None,
730 kind: StmtKind::End(block),
731 line,
732 }
733 }
734 "UNITCHECK" => {
735 self.advance();
736 let block = self.parse_block()?;
737 Statement {
738 label: None,
739 kind: StmtKind::UnitCheck(block),
740 line,
741 }
742 }
743 "CHECK" => {
744 self.advance();
745 let block = self.parse_block()?;
746 Statement {
747 label: None,
748 kind: StmtKind::Check(block),
749 line,
750 }
751 }
752 "INIT" => {
753 self.advance();
754 let block = self.parse_block()?;
755 Statement {
756 label: None,
757 kind: StmtKind::Init(block),
758 line,
759 }
760 }
761 "goto" => {
762 self.advance();
763 let target = self.parse_expression()?;
764 let stmt = Statement {
765 label: None,
766 kind: StmtKind::Goto {
767 target: Box::new(target),
768 },
769 line,
770 };
771 self.parse_stmt_postfix_modifier(stmt)?
773 }
774 "continue" => {
775 self.advance();
776 let block = self.parse_block()?;
777 Statement {
778 label: None,
779 kind: StmtKind::Continue(block),
780 line,
781 }
782 }
783 "try" => self.parse_try_catch()?,
784 "defer" => self.parse_defer_stmt()?,
785 "tie" => self.parse_tie_stmt()?,
786 "given" => self.parse_given()?,
787 "when" => self.parse_when_stmt()?,
788 "default" => self.parse_default_stmt()?,
789 "eval_timeout" => self.parse_eval_timeout()?,
790 "do" => {
791 if matches!(self.peek_at(1), Token::LBrace) {
792 self.advance();
793 let body = self.parse_block()?;
794 if let Token::Ident(ref w) = self.peek().clone() {
795 if w == "while" {
796 self.advance();
797 self.expect(&Token::LParen)?;
798 let mut condition = self.parse_expression()?;
799 Self::mark_match_scalar_g_for_boolean_condition(&mut condition);
800 self.expect(&Token::RParen)?;
801 self.eat(&Token::Semicolon);
802 Statement {
803 label: label.clone(),
804 kind: StmtKind::DoWhile { body, condition },
805 line,
806 }
807 } else {
808 let inner_line = body.first().map(|s| s.line).unwrap_or(line);
809 let inner = Expr {
810 kind: ExprKind::CodeRef {
811 params: vec![],
812 body,
813 },
814 line: inner_line,
815 };
816 let expr = Expr {
817 kind: ExprKind::Do(Box::new(inner)),
818 line,
819 };
820 let stmt = Statement {
821 label: label.clone(),
822 kind: StmtKind::Expression(expr),
823 line,
824 };
825 self.parse_stmt_postfix_modifier(stmt)?
827 }
828 } else {
829 let inner_line = body.first().map(|s| s.line).unwrap_or(line);
830 let inner = Expr {
831 kind: ExprKind::CodeRef {
832 params: vec![],
833 body,
834 },
835 line: inner_line,
836 };
837 let expr = Expr {
838 kind: ExprKind::Do(Box::new(inner)),
839 line,
840 };
841 let stmt = Statement {
842 label: label.clone(),
843 kind: StmtKind::Expression(expr),
844 line,
845 };
846 self.parse_stmt_postfix_modifier(stmt)?
847 }
848 } else {
849 if let Some(expr) = self.try_parse_bareword_stmt_call() {
850 let stmt = self.maybe_postfix_modifier(expr)?;
851 self.parse_stmt_postfix_modifier(stmt)?
852 } else {
853 let expr = self.parse_expression()?;
854 let stmt = self.maybe_postfix_modifier(expr)?;
855 self.parse_stmt_postfix_modifier(stmt)?
856 }
857 }
858 }
859 _ => {
860 if let Some(expr) = self.try_parse_bareword_stmt_call() {
862 let stmt = self.maybe_postfix_modifier(expr)?;
863 self.parse_stmt_postfix_modifier(stmt)?
864 } else {
865 let expr = self.parse_expression()?;
866 let stmt = self.maybe_postfix_modifier(expr)?;
867 self.parse_stmt_postfix_modifier(stmt)?
868 }
869 }
870 },
871 Token::LBrace => {
872 if self.looks_like_hashref() {
875 let expr = self.parse_expression()?;
876 let stmt = self.maybe_postfix_modifier(expr)?;
877 self.parse_stmt_postfix_modifier(stmt)?
878 } else {
879 let block = self.parse_block()?;
880 let stmt = Statement {
881 label: None,
882 kind: StmtKind::Block(block),
883 line,
884 };
885 self.parse_stmt_postfix_modifier(stmt)?
887 }
888 }
889 _ => {
890 let expr = self.parse_expression()?;
891 let stmt = self.maybe_postfix_modifier(expr)?;
892 self.parse_stmt_postfix_modifier(stmt)?
893 }
894 };
895
896 stmt.label = label;
897 Ok(stmt)
898 }
899
900 fn parse_stmt_postfix_modifier(&mut self, stmt: Statement) -> PerlResult<Statement> {
902 let line = stmt.line;
903 if self.peek_line() > self.prev_line() {
908 self.eat(&Token::Semicolon);
909 return Ok(stmt);
910 }
911 if let Token::Ident(ref kw) = self.peek().clone() {
912 match kw.as_str() {
913 "if" => {
914 self.advance();
915 let mut cond = self.parse_expression()?;
916 Self::mark_match_scalar_g_for_boolean_condition(&mut cond);
917 self.eat(&Token::Semicolon);
918 return Ok(Statement {
919 label: None,
920 kind: StmtKind::If {
921 condition: cond,
922 body: vec![stmt],
923 elsifs: vec![],
924 else_block: None,
925 },
926 line,
927 });
928 }
929 "unless" => {
930 self.advance();
931 let mut cond = self.parse_expression()?;
932 Self::mark_match_scalar_g_for_boolean_condition(&mut cond);
933 self.eat(&Token::Semicolon);
934 return Ok(Statement {
935 label: None,
936 kind: StmtKind::Unless {
937 condition: cond,
938 body: vec![stmt],
939 else_block: None,
940 },
941 line,
942 });
943 }
944 "while" | "until" | "for" | "foreach" => {
945 if let Some(expr) = Self::stmt_into_postfix_body_expr(stmt) {
948 let out = self.maybe_postfix_modifier(expr)?;
949 self.eat(&Token::Semicolon);
950 return Ok(out);
951 }
952 return Err(self.syntax_err(
953 format!("postfix `{}` is not supported on this statement form", kw),
954 self.peek_line(),
955 ));
956 }
957 "pmap" | "pflat_map" | "pgrep" | "pfor" | "preduce" | "pcache" => {
959 let line = stmt.line;
960 let block = self.stmt_into_parallel_block(stmt)?;
961 let which = kw.as_str();
962 self.advance();
963 self.eat(&Token::Comma);
964 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
965 self.eat(&Token::Semicolon);
966 let list = Box::new(list);
967 let progress = progress.map(Box::new);
968 let kind = match which {
969 "pmap" => ExprKind::PMapExpr {
970 block,
971 list,
972 progress,
973 flat_outputs: false,
974 on_cluster: None,
975 stream: false,
976 },
977 "pflat_map" => ExprKind::PMapExpr {
978 block,
979 list,
980 progress,
981 flat_outputs: true,
982 on_cluster: None,
983 stream: false,
984 },
985 "pgrep" => ExprKind::PGrepExpr {
986 block,
987 list,
988 progress,
989 stream: false,
990 },
991 "pfor" => ExprKind::PForExpr {
992 block,
993 list,
994 progress,
995 },
996 "preduce" => ExprKind::PReduceExpr {
997 block,
998 list,
999 progress,
1000 },
1001 "pcache" => ExprKind::PcacheExpr {
1002 block,
1003 list,
1004 progress,
1005 },
1006 _ => unreachable!(),
1007 };
1008 return Ok(Statement {
1009 label: None,
1010 kind: StmtKind::Expression(Expr { kind, line }),
1011 line,
1012 });
1013 }
1014 _ => {}
1015 }
1016 }
1017 self.eat(&Token::Semicolon);
1018 Ok(stmt)
1019 }
1020
1021 fn stmt_into_parallel_block(&self, stmt: Statement) -> PerlResult<Block> {
1024 let line = stmt.line;
1025 match stmt.kind {
1026 StmtKind::Block(block) => Ok(block),
1027 StmtKind::Expression(expr) => {
1028 if let ExprKind::Do(ref inner) = expr.kind {
1029 if let ExprKind::CodeRef { ref body, .. } = inner.kind {
1030 return Ok(body.clone());
1031 }
1032 }
1033 Ok(vec![Statement {
1034 label: None,
1035 kind: StmtKind::Expression(expr),
1036 line,
1037 }])
1038 }
1039 _ => Err(self.syntax_err(
1040 "postfix parallel op expects `do { }`, a bare `{ }` block, or an expression statement",
1041 line,
1042 )),
1043 }
1044 }
1045
1046 fn stmt_into_postfix_body_expr(stmt: Statement) -> Option<Expr> {
1049 match stmt.kind {
1050 StmtKind::Expression(expr) => Some(expr),
1051 StmtKind::Block(block) => {
1052 let line = stmt.line;
1053 let inner = Expr {
1054 kind: ExprKind::CodeRef {
1055 params: vec![],
1056 body: block,
1057 },
1058 line,
1059 };
1060 Some(Expr {
1061 kind: ExprKind::Do(Box::new(inner)),
1062 line,
1063 })
1064 }
1065 _ => None,
1066 }
1067 }
1068
1069 fn peek_is_postfix_stmt_modifier_keyword(&self) -> bool {
1072 matches!(
1073 self.peek(),
1074 Token::Ident(ref kw)
1075 if matches!(
1076 kw.as_str(),
1077 "if" | "unless" | "while" | "until" | "for" | "foreach"
1078 )
1079 )
1080 }
1081
1082 fn maybe_postfix_modifier(&mut self, expr: Expr) -> PerlResult<Statement> {
1083 let line = expr.line;
1084 if self.peek_line() > self.prev_line() {
1086 return Ok(Statement {
1087 label: None,
1088 kind: StmtKind::Expression(expr),
1089 line,
1090 });
1091 }
1092 match self.peek() {
1093 Token::Ident(ref kw) => match kw.as_str() {
1094 "if" => {
1095 self.advance();
1096 let cond = self.parse_expression()?;
1097 Ok(Statement {
1098 label: None,
1099 kind: StmtKind::Expression(Expr {
1100 kind: ExprKind::PostfixIf {
1101 expr: Box::new(expr),
1102 condition: Box::new(cond),
1103 },
1104 line,
1105 }),
1106 line,
1107 })
1108 }
1109 "unless" => {
1110 self.advance();
1111 let cond = self.parse_expression()?;
1112 Ok(Statement {
1113 label: None,
1114 kind: StmtKind::Expression(Expr {
1115 kind: ExprKind::PostfixUnless {
1116 expr: Box::new(expr),
1117 condition: Box::new(cond),
1118 },
1119 line,
1120 }),
1121 line,
1122 })
1123 }
1124 "while" => {
1125 self.advance();
1126 let cond = self.parse_expression()?;
1127 Ok(Statement {
1128 label: None,
1129 kind: StmtKind::Expression(Expr {
1130 kind: ExprKind::PostfixWhile {
1131 expr: Box::new(expr),
1132 condition: Box::new(cond),
1133 },
1134 line,
1135 }),
1136 line,
1137 })
1138 }
1139 "until" => {
1140 self.advance();
1141 let cond = self.parse_expression()?;
1142 Ok(Statement {
1143 label: None,
1144 kind: StmtKind::Expression(Expr {
1145 kind: ExprKind::PostfixUntil {
1146 expr: Box::new(expr),
1147 condition: Box::new(cond),
1148 },
1149 line,
1150 }),
1151 line,
1152 })
1153 }
1154 "for" | "foreach" => {
1155 self.advance();
1156 let list = self.parse_expression()?;
1157 Ok(Statement {
1158 label: None,
1159 kind: StmtKind::Expression(Expr {
1160 kind: ExprKind::PostfixForeach {
1161 expr: Box::new(expr),
1162 list: Box::new(list),
1163 },
1164 line,
1165 }),
1166 line,
1167 })
1168 }
1169 _ => Ok(Statement {
1170 label: None,
1171 kind: StmtKind::Expression(expr),
1172 line,
1173 }),
1174 },
1175 _ => Ok(Statement {
1176 label: None,
1177 kind: StmtKind::Expression(expr),
1178 line,
1179 }),
1180 }
1181 }
1182
1183 fn try_parse_bareword_stmt_call(&mut self) -> Option<Expr> {
1185 let saved = self.pos;
1186 let line = self.peek_line();
1187 let mut name = match self.peek() {
1188 Token::Ident(n) => n.clone(),
1189 _ => return None,
1190 };
1191 if name.starts_with('\x00') || !Self::bareword_stmt_may_be_sub(&name) {
1193 return None;
1194 }
1195 self.advance();
1196 while self.eat(&Token::PackageSep) {
1197 match self.advance() {
1198 (Token::Ident(part), _) => {
1199 name = format!("{}::{}", name, part);
1200 }
1201 _ => {
1202 self.pos = saved;
1203 return None;
1204 }
1205 }
1206 }
1207 match self.peek() {
1208 Token::Semicolon | Token::RBrace => Some(Expr {
1209 kind: ExprKind::FuncCall { name, args: vec![] },
1210 line,
1211 }),
1212 _ => {
1213 self.pos = saved;
1214 None
1215 }
1216 }
1217 }
1218
1219 fn bareword_stmt_may_be_sub(name: &str) -> bool {
1221 !matches!(
1222 name,
1223 "__FILE__"
1224 | "__LINE__"
1225 | "abs"
1226 | "async"
1227 | "spawn"
1228 | "atan2"
1229 | "await"
1230 | "barrier"
1231 | "bless"
1232 | "caller"
1233 | "capture"
1234 | "cat"
1235 | "chdir"
1236 | "chmod"
1237 | "chomp"
1238 | "chop"
1239 | "chr"
1240 | "chown"
1241 | "closedir"
1242 | "close"
1243 | "collect"
1244 | "cos"
1245 | "crypt"
1246 | "defined"
1247 | "dec"
1248 | "delete"
1249 | "die"
1250 | "deque"
1251 | "do"
1252 | "each"
1253 | "eof"
1254 | "fore"
1255 | "eval"
1256 | "exec"
1257 | "exists"
1258 | "exit"
1259 | "exp"
1260 | "fan"
1261 | "fan_cap"
1262 | "fc"
1263 | "fetch_url"
1264 | "d"
1265 | "dirs"
1266 | "dr"
1267 | "f"
1268 | "fi"
1269 | "files"
1270 | "filesf"
1271 | "filter"
1272 | "fr"
1273 | "getcwd"
1274 | "glob_par"
1275 | "par_sed"
1276 | "glob"
1277 | "grep"
1278 | "greps"
1279 | "heap"
1280 | "hex"
1281 | "inc"
1282 | "index"
1283 | "int"
1284 | "join"
1285 | "keys"
1286 | "lcfirst"
1287 | "lc"
1288 | "length"
1289 | "link"
1290 | "log"
1291 | "lstat"
1292 | "map"
1293 | "flat_map"
1294 | "maps"
1295 | "flat_maps"
1296 | "flatten"
1297 | "frequencies"
1298 | "freq"
1299 | "interleave"
1300 | "ddump"
1301 | "stringify"
1302 | "str"
1303 | "s"
1304 | "input"
1305 | "lines"
1306 | "words"
1307 | "chars"
1308 | "digits"
1309 | "letters"
1310 | "letters_uc"
1311 | "letters_lc"
1312 | "punctuation"
1313 | "sentences"
1314 | "paragraphs"
1315 | "sections"
1316 | "numbers"
1317 | "graphemes"
1318 | "columns"
1319 | "trim"
1320 | "avg"
1321 | "top"
1322 | "pager"
1323 | "pg"
1324 | "less"
1325 | "count_by"
1326 | "to_file"
1327 | "to_json"
1328 | "to_csv"
1329 | "grep_v"
1330 | "select_keys"
1331 | "pluck"
1332 | "clamp"
1333 | "normalize"
1334 | "stddev"
1335 | "squared"
1336 | "square"
1337 | "cubed"
1338 | "cube"
1339 | "expt"
1340 | "pow"
1341 | "pw"
1342 | "snake_case"
1343 | "camel_case"
1344 | "kebab_case"
1345 | "to_toml"
1346 | "to_yaml"
1347 | "to_xml"
1348 | "to_html"
1349 | "to_markdown"
1350 | "xopen"
1351 | "clip"
1352 | "paste"
1353 | "to_table"
1354 | "sparkline"
1355 | "bar_chart"
1356 | "flame"
1357 | "set"
1358 | "list_count"
1359 | "list_size"
1360 | "count"
1361 | "size"
1362 | "cnt"
1363 | "len"
1364 | "all"
1365 | "any"
1366 | "none"
1367 | "take_while"
1368 | "drop_while"
1369 | "skip_while"
1370 | "skip"
1371 | "first_or"
1372 | "tap"
1373 | "peek"
1374 | "partition"
1375 | "min_by"
1376 | "max_by"
1377 | "zip_with"
1378 | "group_by"
1379 | "chunk_by"
1380 | "with_index"
1381 | "puniq"
1382 | "pfirst"
1383 | "pany"
1384 | "uniq"
1385 | "distinct"
1386 | "shuffle"
1387 | "shuffled"
1388 | "chunked"
1389 | "windowed"
1390 | "match"
1391 | "mkdir"
1392 | "every"
1393 | "gen"
1394 | "oct"
1395 | "open"
1396 | "p"
1397 | "opendir"
1398 | "ord"
1399 | "par_lines"
1400 | "par_walk"
1401 | "pipe"
1402 | "pipes"
1403 | "block_devices"
1404 | "char_devices"
1405 | "exe"
1406 | "executables"
1407 | "rate_limit"
1408 | "retry"
1409 | "pcache"
1410 | "pchannel"
1411 | "pfor"
1412 | "pgrep"
1413 | "pgreps"
1414 | "pipeline"
1415 | "pmap_chunked"
1416 | "pmap_reduce"
1417 | "pmap_on"
1418 | "pflat_map_on"
1419 | "pmap"
1420 | "pmaps"
1421 | "pflat_map"
1422 | "pflat_maps"
1423 | "pop"
1424 | "pos"
1425 | "ppool"
1426 | "preduce_init"
1427 | "preduce"
1428 | "pselect"
1429 | "printf"
1430 | "print"
1431 | "pr"
1432 | "psort"
1433 | "push"
1434 | "pwatch"
1435 | "rand"
1436 | "readdir"
1437 | "readlink"
1438 | "reduce"
1439 | "fold"
1440 | "inject"
1441 | "first"
1442 | "detect"
1443 | "find"
1444 | "find_all"
1445 | "ref"
1446 | "rename"
1447 | "require"
1448 | "rev"
1449 | "reverse"
1450 | "reversed"
1451 | "rewinddir"
1452 | "rindex"
1453 | "rmdir"
1454 | "rm"
1455 | "say"
1456 | "scalar"
1457 | "seekdir"
1458 | "shift"
1459 | "sin"
1460 | "slurp"
1461 | "sockets"
1462 | "sort"
1463 | "splice"
1464 | "split"
1465 | "sprintf"
1466 | "sqrt"
1467 | "srand"
1468 | "stat"
1469 | "study"
1470 | "substr"
1471 | "symlink"
1472 | "sym_links"
1473 | "system"
1474 | "telldir"
1475 | "timer"
1476 | "trace"
1477 | "ucfirst"
1478 | "uc"
1479 | "undef"
1480 | "umask"
1481 | "unlink"
1482 | "unshift"
1483 | "utime"
1484 | "values"
1485 | "wantarray"
1486 | "warn"
1487 | "watch"
1488 | "yield"
1489 | "sub"
1490 )
1491 }
1492
1493 fn parse_block(&mut self) -> PerlResult<Block> {
1494 self.expect(&Token::LBrace)?;
1495 let saved_pipe_rhs_depth = self.pipe_rhs_depth;
1498 self.pipe_rhs_depth = 0;
1499 let mut stmts = Vec::new();
1500 if let Some(param_stmts) = self.try_parse_block_params()? {
1504 stmts.extend(param_stmts);
1505 }
1506 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
1507 if self.eat(&Token::Semicolon) {
1508 continue;
1509 }
1510 stmts.push(self.parse_statement()?);
1511 }
1512 self.expect(&Token::RBrace)?;
1513 self.pipe_rhs_depth = saved_pipe_rhs_depth;
1514 Self::default_topic_for_sole_bareword(&mut stmts);
1515 Ok(stmts)
1516 }
1517
1518 fn try_parse_block_params(&mut self) -> PerlResult<Option<Vec<Statement>>> {
1523 if !matches!(self.peek(), Token::BitOr) {
1524 return Ok(None);
1525 }
1526 let mut i = 1; loop {
1529 match self.peek_at(i) {
1530 Token::ScalarVar(_) => i += 1,
1531 _ => return Ok(None), }
1533 match self.peek_at(i) {
1534 Token::BitOr => break, Token::Comma => i += 1, _ => return Ok(None), }
1538 }
1539 let line = self.peek_line();
1541 self.advance(); let mut names = Vec::new();
1543 loop {
1544 if let Token::ScalarVar(ref name) = self.peek().clone() {
1545 names.push(name.clone());
1546 self.advance();
1547 }
1548 if self.eat(&Token::BitOr) {
1549 break;
1550 }
1551 self.expect(&Token::Comma)?;
1552 }
1553 let sources: Vec<&str> = match names.len() {
1558 1 => vec!["_"],
1559 2 => vec!["a", "b"],
1560 n => {
1561 let _ = n;
1563 vec![] }
1565 };
1566 let mut stmts = Vec::with_capacity(names.len());
1567 if !sources.is_empty() {
1568 for (name, src) in names.iter().zip(sources.iter()) {
1569 stmts.push(Statement {
1570 label: None,
1571 kind: StmtKind::My(vec![VarDecl {
1572 sigil: Sigil::Scalar,
1573 name: name.clone(),
1574 initializer: Some(Expr {
1575 kind: ExprKind::ScalarVar(src.to_string()),
1576 line,
1577 }),
1578 frozen: false,
1579 type_annotation: None,
1580 }]),
1581 line,
1582 });
1583 }
1584 } else {
1585 for (idx, name) in names.iter().enumerate() {
1587 let src = if idx == 0 {
1588 "_".to_string()
1589 } else {
1590 format!("_{idx}")
1591 };
1592 stmts.push(Statement {
1593 label: None,
1594 kind: StmtKind::My(vec![VarDecl {
1595 sigil: Sigil::Scalar,
1596 name: name.clone(),
1597 initializer: Some(Expr {
1598 kind: ExprKind::ScalarVar(src),
1599 line,
1600 }),
1601 frozen: false,
1602 type_annotation: None,
1603 }]),
1604 line,
1605 });
1606 }
1607 }
1608 Ok(Some(stmts))
1609 }
1610
1611 fn default_topic_for_sole_bareword(stmts: &mut [Statement]) {
1626 let [only] = stmts else { return };
1627 let StmtKind::Expression(ref mut expr) = only.kind else {
1628 return;
1629 };
1630 let topic_line = expr.line;
1631 let topic_arg = || Expr {
1632 kind: ExprKind::ScalarVar("_".to_string()),
1633 line: topic_line,
1634 };
1635 match expr.kind {
1636 ExprKind::FuncCall {
1638 ref name,
1639 ref mut args,
1640 } if args.is_empty()
1641 && (Self::is_known_bareword(name) || Self::is_try_builtin_name(name)) =>
1642 {
1643 args.push(topic_arg());
1644 }
1645 ExprKind::Bareword(ref name)
1649 if (Self::is_known_bareword(name) || Self::is_try_builtin_name(name)) =>
1650 {
1651 let n = name.clone();
1652 expr.kind = ExprKind::FuncCall {
1653 name: n,
1654 args: vec![topic_arg()],
1655 };
1656 }
1657 _ => {}
1658 }
1659 }
1660
1661 fn parse_defer_stmt(&mut self) -> PerlResult<Statement> {
1665 let line = self.peek_line();
1666 self.advance(); let body = self.parse_block()?;
1668 self.eat(&Token::Semicolon);
1669 let coderef = Expr {
1671 kind: ExprKind::CodeRef {
1672 params: vec![],
1673 body,
1674 },
1675 line,
1676 };
1677 Ok(Statement {
1678 label: None,
1679 kind: StmtKind::Expression(Expr {
1680 kind: ExprKind::FuncCall {
1681 name: "defer__internal".to_string(),
1682 args: vec![coderef],
1683 },
1684 line,
1685 }),
1686 line,
1687 })
1688 }
1689
1690 fn parse_try_catch(&mut self) -> PerlResult<Statement> {
1692 let line = self.peek_line();
1693 self.advance(); let try_block = self.parse_block()?;
1695 match self.peek() {
1696 Token::Ident(ref k) if k == "catch" => {
1697 self.advance();
1698 }
1699 _ => {
1700 return Err(self.syntax_err("expected 'catch' after try block", self.peek_line()));
1701 }
1702 }
1703 self.expect(&Token::LParen)?;
1704 let catch_var = self.parse_scalar_var_name()?;
1705 self.expect(&Token::RParen)?;
1706 let catch_block = self.parse_block()?;
1707 let finally_block = match self.peek() {
1708 Token::Ident(ref k) if k == "finally" => {
1709 self.advance();
1710 Some(self.parse_block()?)
1711 }
1712 _ => None,
1713 };
1714 self.eat(&Token::Semicolon);
1715 Ok(Statement {
1716 label: None,
1717 kind: StmtKind::TryCatch {
1718 try_block,
1719 catch_var,
1720 catch_block,
1721 finally_block,
1722 },
1723 line,
1724 })
1725 }
1726
1727 fn parse_thread_macro(&mut self, _line: usize, thread_last: bool) -> PerlResult<Expr> {
1741 let saved_thread_last = self.thread_last_mode;
1743 self.thread_last_mode = thread_last;
1744
1745 let pipe_rhs_wrap = self.in_pipe_rhs();
1746 let mut result = if pipe_rhs_wrap {
1747 Expr {
1748 kind: ExprKind::ArrayElement {
1749 array: "_".to_string(),
1750 index: Box::new(Expr {
1751 kind: ExprKind::Integer(0),
1752 line: _line,
1753 }),
1754 },
1755 line: _line,
1756 }
1757 } else {
1758 self.suppress_parenless_call = self.suppress_parenless_call.saturating_add(1);
1761 let expr = self.parse_thread_input();
1762 self.suppress_parenless_call = self.suppress_parenless_call.saturating_sub(1);
1763 expr?
1764 };
1765
1766 let mut last_stage_end_line = self.prev_line();
1768
1769 loop {
1771 if self.peek_line() > last_stage_end_line {
1776 break;
1777 }
1778
1779 match self.peek() {
1783 Token::Semicolon
1784 | Token::RBrace
1785 | Token::RParen
1786 | Token::RBracket
1787 | Token::PipeForward
1788 | Token::Eof
1789 | Token::ScalarVar(_)
1790 | Token::ArrayVar(_)
1791 | Token::HashVar(_)
1792 | Token::Comma => break,
1793 Token::Ident(ref kw)
1794 if matches!(
1795 kw.as_str(),
1796 "my" | "our"
1797 | "local"
1798 | "state"
1799 | "if"
1800 | "unless"
1801 | "while"
1802 | "until"
1803 | "for"
1804 | "foreach"
1805 | "return"
1806 | "last"
1807 | "next"
1808 | "redo"
1809 ) =>
1810 {
1811 break
1812 }
1813 _ => {}
1814 }
1815
1816 let stage_line = self.peek_line();
1817
1818 match self.peek().clone() {
1820 Token::ArrowBrace => {
1822 self.advance(); let mut stmts = Vec::new();
1824 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
1825 if self.eat(&Token::Semicolon) {
1826 continue;
1827 }
1828 stmts.push(self.parse_statement()?);
1829 }
1830 self.expect(&Token::RBrace)?;
1831 let code_ref = Expr {
1832 kind: ExprKind::CodeRef {
1833 params: vec![],
1834 body: stmts,
1835 },
1836 line: stage_line,
1837 };
1838 result = self.pipe_forward_apply(result, code_ref, stage_line)?;
1839 }
1840 Token::Ident(ref name) if name == "sub" => {
1842 if !crate::compat_mode() {
1843 return Err(self.syntax_err(
1844 "stryke uses `fn {}` instead of `fn {}` (this is not Perl 5)",
1845 stage_line,
1846 ));
1847 }
1848 self.advance(); let (params, _prototype) = self.parse_sub_sig_or_prototype_opt()?;
1850 let body = self.parse_block()?;
1851 let code_ref = Expr {
1852 kind: ExprKind::CodeRef { params, body },
1853 line: stage_line,
1854 };
1855 result = self.pipe_forward_apply(result, code_ref, stage_line)?;
1856 }
1857 Token::Ident(ref name) if name == "fn" => {
1859 self.advance(); let (params, _prototype) = self.parse_sub_sig_or_prototype_opt()?;
1861 let body = self.parse_block()?;
1862 let code_ref = Expr {
1863 kind: ExprKind::CodeRef { params, body },
1864 line: stage_line,
1865 };
1866 result = self.pipe_forward_apply(result, code_ref, stage_line)?;
1867 }
1868 Token::Ident(ref name) => {
1870 let func_name = name.clone();
1871 self.advance();
1872
1873 if func_name.starts_with('\x00') {
1875 let parts: Vec<&str> = func_name.split('\x00').collect();
1876 if parts.len() >= 4 && parts[1] == "s" {
1877 let delim = parts.get(5).and_then(|s| s.chars().next()).unwrap_or('/');
1878 let stage = Expr {
1879 kind: ExprKind::Substitution {
1880 expr: Box::new(result.clone()),
1881 pattern: parts[2].to_string(),
1882 replacement: parts[3].to_string(),
1883 flags: format!("{}r", parts.get(4).unwrap_or(&"")),
1884 delim,
1885 },
1886 line: stage_line,
1887 };
1888 result = stage;
1889 last_stage_end_line = self.prev_line();
1890 continue;
1891 }
1892 if parts.len() >= 4 && parts[1] == "tr" {
1893 let delim = parts.get(5).and_then(|s| s.chars().next()).unwrap_or('/');
1894 let stage = Expr {
1895 kind: ExprKind::Transliterate {
1896 expr: Box::new(result.clone()),
1897 from: parts[2].to_string(),
1898 to: parts[3].to_string(),
1899 flags: format!("{}r", parts.get(4).unwrap_or(&"")),
1900 delim,
1901 },
1902 line: stage_line,
1903 };
1904 result = stage;
1905 last_stage_end_line = self.prev_line();
1906 continue;
1907 }
1908 return Err(
1909 self.syntax_err("Unexpected encoded token in thread", stage_line)
1910 );
1911 }
1912
1913 if matches!(self.peek(), Token::Plus)
1918 && matches!(self.peek_at(1), Token::LBrace)
1919 {
1920 self.advance(); self.expect(&Token::LBrace)?;
1922 let pairs = self.try_parse_hash_ref()?;
1924 let hashref_expr = Expr {
1925 kind: ExprKind::HashRef(pairs),
1926 line: stage_line,
1927 };
1928 let flatten_array_refs =
1929 matches!(func_name.as_str(), "flat_map" | "flat_maps");
1930 let stream = matches!(func_name.as_str(), "maps" | "flat_maps");
1931 let placeholder = Expr {
1933 kind: ExprKind::Undef,
1934 line: stage_line,
1935 };
1936 let map_node = Expr {
1937 kind: ExprKind::MapExprComma {
1938 expr: Box::new(hashref_expr),
1939 list: Box::new(placeholder),
1940 flatten_array_refs,
1941 stream,
1942 },
1943 line: stage_line,
1944 };
1945 result = self.pipe_forward_apply(result, map_node, stage_line)?;
1946 } else if func_name == "pmap_chunked" {
1948 let chunk_size = self.parse_assign_expr()?;
1949 let block = self.parse_block_or_bareword_block()?;
1950 let placeholder = self.pipe_placeholder_list(stage_line);
1951 let stage = Expr {
1952 kind: ExprKind::PMapChunkedExpr {
1953 chunk_size: Box::new(chunk_size),
1954 block,
1955 list: Box::new(placeholder),
1956 progress: None,
1957 },
1958 line: stage_line,
1959 };
1960 result = self.pipe_forward_apply(result, stage, stage_line)?;
1961 } else if func_name == "preduce_init" {
1963 let init = self.parse_assign_expr()?;
1964 let block = self.parse_block_or_bareword_block()?;
1965 let placeholder = self.pipe_placeholder_list(stage_line);
1966 let stage = Expr {
1967 kind: ExprKind::PReduceInitExpr {
1968 init: Box::new(init),
1969 block,
1970 list: Box::new(placeholder),
1971 progress: None,
1972 },
1973 line: stage_line,
1974 };
1975 result = self.pipe_forward_apply(result, stage, stage_line)?;
1976 } else if func_name == "pmap_reduce" {
1978 let map_block = self.parse_block_or_bareword_block()?;
1979 let reduce_block = if matches!(self.peek(), Token::LBrace) {
1980 self.parse_block()?
1981 } else {
1982 self.expect(&Token::Comma)?;
1983 self.parse_block_or_bareword_cmp_block()?
1984 };
1985 let placeholder = self.pipe_placeholder_list(stage_line);
1986 let stage = Expr {
1987 kind: ExprKind::PMapReduceExpr {
1988 map_block,
1989 reduce_block,
1990 list: Box::new(placeholder),
1991 progress: None,
1992 },
1993 line: stage_line,
1994 };
1995 result = self.pipe_forward_apply(result, stage, stage_line)?;
1996 } else if matches!(self.peek(), Token::LBrace) {
1998 self.pipe_rhs_depth = self.pipe_rhs_depth.saturating_add(1);
2000 let stage = self.parse_thread_stage_with_block(&func_name, stage_line)?;
2001 self.pipe_rhs_depth = self.pipe_rhs_depth.saturating_sub(1);
2002 result = self.pipe_forward_apply(result, stage, stage_line)?;
2003 } else if matches!(self.peek(), Token::LParen) {
2004 if func_name == "join" {
2007 self.advance(); let separator = self.parse_assign_expr()?;
2009 self.expect(&Token::RParen)?;
2010 let placeholder = self.pipe_placeholder_list(stage_line);
2011 let stage = Expr {
2012 kind: ExprKind::JoinExpr {
2013 separator: Box::new(separator),
2014 list: Box::new(placeholder),
2015 },
2016 line: stage_line,
2017 };
2018 result = self.pipe_forward_apply(result, stage, stage_line)?;
2019 } else if func_name == "split" {
2020 self.advance(); let pattern = self.parse_assign_expr()?;
2022 let limit = if self.eat(&Token::Comma) {
2023 Some(Box::new(self.parse_assign_expr()?))
2024 } else {
2025 None
2026 };
2027 self.expect(&Token::RParen)?;
2028 let placeholder = Expr {
2029 kind: ExprKind::ScalarVar("_".to_string()),
2030 line: stage_line,
2031 };
2032 let stage = Expr {
2033 kind: ExprKind::SplitExpr {
2034 pattern: Box::new(pattern),
2035 string: Box::new(placeholder),
2036 limit,
2037 },
2038 line: stage_line,
2039 };
2040 result = self.pipe_forward_apply(result, stage, stage_line)?;
2041 } else {
2042 self.advance(); let mut call_args = Vec::new();
2053 while !matches!(self.peek(), Token::RParen | Token::Eof) {
2054 call_args.push(self.parse_assign_expr()?);
2055 if !self.eat(&Token::Comma) {
2056 break;
2057 }
2058 }
2059 self.expect(&Token::RParen)?;
2060 if !call_args.iter().any(Self::expr_contains_topic_var) {
2064 let topic = Expr {
2065 kind: ExprKind::ScalarVar("_".to_string()),
2066 line: stage_line,
2067 };
2068 if self.thread_last_mode {
2069 call_args.push(topic);
2070 } else {
2071 call_args.insert(0, topic);
2072 }
2073 }
2074 let call_expr = Expr {
2075 kind: ExprKind::FuncCall {
2076 name: func_name.clone(),
2077 args: call_args,
2078 },
2079 line: stage_line,
2080 };
2081 let code_ref = Expr {
2082 kind: ExprKind::CodeRef {
2083 params: vec![],
2084 body: vec![Statement {
2085 label: None,
2086 kind: StmtKind::Expression(call_expr),
2087 line: stage_line,
2088 }],
2089 },
2090 line: stage_line,
2091 };
2092 result = self.pipe_forward_apply(result, code_ref, stage_line)?;
2093 }
2094 } else {
2095 result = self.thread_apply_bare_func(&func_name, result, stage_line)?;
2097 }
2098 }
2099 Token::Regex(ref pattern, ref flags, delim) => {
2101 let pattern = pattern.clone();
2102 let flags = flags.clone();
2103 self.advance();
2104 result =
2105 self.thread_regex_grep_stage(result, pattern, flags, delim, stage_line);
2106 }
2107 Token::Slash => {
2110 self.advance(); if let Token::Ident(ref ident_s) = self.peek().clone() {
2116 if matches!(ident_s.as_str(), "m" | "s" | "tr" | "y" | "qr")
2117 && matches!(self.peek_at(1), Token::Regex(..))
2118 {
2119 self.advance(); if let Token::Regex(ref misparsed_pattern, ref misparsed_flags, _) =
2126 self.peek().clone()
2127 {
2128 let _ = (misparsed_pattern, misparsed_flags);
2138 }
2139 }
2140 }
2141
2142 let mut pattern = String::new();
2144 loop {
2145 match self.peek().clone() {
2146 Token::Slash => {
2147 self.advance(); break;
2149 }
2150 Token::Eof | Token::Semicolon | Token::Newline => {
2151 return Err(self
2152 .syntax_err("Unterminated regex in thread stage", stage_line));
2153 }
2154 Token::Regex(ref inner_pattern, ref inner_flags, delim) => {
2156 if pattern.is_empty()
2164 || matches!(pattern.as_str(), "m" | "s" | "tr" | "y" | "qr")
2165 {
2166 let _ = (inner_pattern, inner_flags, delim);
2172 }
2173 return Err(self.syntax_err(
2175 "Complex regex in thread stage - use m/pattern/ syntax instead",
2176 stage_line,
2177 ));
2178 }
2179 Token::Ident(ref s) => {
2180 pattern.push_str(s);
2181 self.advance();
2182 }
2183 Token::Integer(n) => {
2184 pattern.push_str(&n.to_string());
2185 self.advance();
2186 }
2187 Token::ScalarVar(ref v) => {
2188 pattern.push('$');
2189 pattern.push_str(v);
2190 self.advance();
2191 }
2192 Token::Dot => {
2193 pattern.push('.');
2194 self.advance();
2195 }
2196 Token::Star => {
2197 pattern.push('*');
2198 self.advance();
2199 }
2200 Token::Plus => {
2201 pattern.push('+');
2202 self.advance();
2203 }
2204 Token::Question => {
2205 pattern.push('?');
2206 self.advance();
2207 }
2208 Token::LParen => {
2209 pattern.push('(');
2210 self.advance();
2211 }
2212 Token::RParen => {
2213 pattern.push(')');
2214 self.advance();
2215 }
2216 Token::LBracket => {
2217 pattern.push('[');
2218 self.advance();
2219 }
2220 Token::RBracket => {
2221 pattern.push(']');
2222 self.advance();
2223 }
2224 Token::Backslash => {
2225 pattern.push('\\');
2226 self.advance();
2227 }
2228 Token::BitOr => {
2229 pattern.push('|');
2230 self.advance();
2231 }
2232 Token::Power => {
2233 pattern.push_str("**");
2234 self.advance();
2235 }
2236 Token::BitXor => {
2237 pattern.push('^');
2238 self.advance();
2239 }
2240 Token::Minus => {
2241 pattern.push('-');
2242 self.advance();
2243 }
2244 _ => {
2245 return Err(self.syntax_err(
2246 format!("Unexpected token in regex pattern: {:?}", self.peek()),
2247 stage_line,
2248 ));
2249 }
2250 }
2251 }
2252 let mut flags = String::new();
2256 if let Token::Ident(ref s) = self.peek().clone() {
2257 let is_flag_only =
2258 s.chars().all(|c| "gimsxecor".contains(c)) && s.len() <= 6;
2259 let followed_by_brace = matches!(self.peek_at(1), Token::LBrace);
2260 if is_flag_only && !followed_by_brace {
2261 flags.push_str(s);
2262 self.advance();
2263 }
2264 }
2265 result = self.thread_regex_grep_stage(result, pattern, flags, '/', stage_line);
2266 }
2267 tok => {
2268 return Err(self.syntax_err(
2269 format!(
2270 "thread: expected stage (ident, fn {{}}, s///, tr///, or /re/), got {:?}",
2271 tok
2272 ),
2273 stage_line,
2274 ));
2275 }
2276 };
2277 last_stage_end_line = self.prev_line();
2278 }
2279
2280 self.thread_last_mode = saved_thread_last;
2282
2283 if pipe_rhs_wrap {
2284 let body_line = result.line;
2287 return Ok(Expr {
2288 kind: ExprKind::CodeRef {
2289 params: vec![],
2290 body: vec![Statement {
2291 label: None,
2292 kind: StmtKind::Expression(result),
2293 line: body_line,
2294 }],
2295 },
2296 line: _line,
2297 });
2298 }
2299 Ok(result)
2300 }
2301
2302 fn thread_regex_grep_stage(
2304 &self,
2305 list: Expr,
2306 pattern: String,
2307 flags: String,
2308 delim: char,
2309 line: usize,
2310 ) -> Expr {
2311 let topic = Expr {
2312 kind: ExprKind::ScalarVar("_".to_string()),
2313 line,
2314 };
2315 let match_expr = Expr {
2316 kind: ExprKind::Match {
2317 expr: Box::new(topic),
2318 pattern,
2319 flags,
2320 scalar_g: false,
2321 delim,
2322 },
2323 line,
2324 };
2325 let block = vec![Statement {
2326 label: None,
2327 kind: StmtKind::Expression(match_expr),
2328 line,
2329 }];
2330 Expr {
2331 kind: ExprKind::GrepExpr {
2332 block,
2333 list: Box::new(list),
2334 keyword: crate::ast::GrepBuiltinKeyword::Grep,
2335 },
2336 line,
2337 }
2338 }
2339
2340 fn expr_contains_topic_var(e: &Expr) -> bool {
2351 format!("{:?}", e).contains("ScalarVar(\"_\")")
2352 }
2353
2354 fn thread_apply_bare_func(&self, name: &str, arg: Expr, line: usize) -> PerlResult<Expr> {
2356 let kind = match name {
2357 "uc" => ExprKind::Uc(Box::new(arg)),
2359 "lc" => ExprKind::Lc(Box::new(arg)),
2360 "ucfirst" | "ufc" => ExprKind::Ucfirst(Box::new(arg)),
2361 "lcfirst" | "lfc" => ExprKind::Lcfirst(Box::new(arg)),
2362 "fc" => ExprKind::Fc(Box::new(arg)),
2363 "chomp" => ExprKind::Chomp(Box::new(arg)),
2364 "chop" => ExprKind::Chop(Box::new(arg)),
2365 "length" => ExprKind::Length(Box::new(arg)),
2366 "len" | "cnt" => ExprKind::FuncCall {
2367 name: "count".to_string(),
2368 args: vec![arg],
2369 },
2370 "quotemeta" | "qm" => ExprKind::FuncCall {
2371 name: "quotemeta".to_string(),
2372 args: vec![arg],
2373 },
2374 "abs" => ExprKind::Abs(Box::new(arg)),
2376 "int" => ExprKind::Int(Box::new(arg)),
2377 "sqrt" | "sq" => ExprKind::Sqrt(Box::new(arg)),
2378 "sin" => ExprKind::Sin(Box::new(arg)),
2379 "cos" => ExprKind::Cos(Box::new(arg)),
2380 "exp" => ExprKind::Exp(Box::new(arg)),
2381 "log" => ExprKind::Log(Box::new(arg)),
2382 "hex" => ExprKind::Hex(Box::new(arg)),
2383 "oct" => ExprKind::Oct(Box::new(arg)),
2384 "chr" => ExprKind::Chr(Box::new(arg)),
2385 "ord" => ExprKind::Ord(Box::new(arg)),
2386 "defined" | "def" => ExprKind::Defined(Box::new(arg)),
2388 "ref" => ExprKind::Ref(Box::new(arg)),
2389 "scalar" => ExprKind::ScalarContext(Box::new(arg)),
2390 "keys" => ExprKind::Keys(Box::new(arg)),
2392 "values" => ExprKind::Values(Box::new(arg)),
2393 "each" => ExprKind::Each(Box::new(arg)),
2394 "pop" => ExprKind::Pop(Box::new(arg)),
2395 "shift" => ExprKind::Shift(Box::new(arg)),
2396 "reverse" => {
2397 if !crate::compat_mode() {
2398 return Err(
2399 self.syntax_err("stryke uses `rev` instead of `reverse` (this is not Perl 5)", line)
2400 );
2401 }
2402 ExprKind::ReverseExpr(Box::new(arg))
2403 }
2404 "reversed" | "rv" | "rev" => ExprKind::Rev(Box::new(arg)),
2405 "sort" | "so" => ExprKind::SortExpr {
2406 cmp: None,
2407 list: Box::new(arg),
2408 },
2409 "uniq" | "distinct" | "uq" => ExprKind::FuncCall {
2410 name: "uniq".to_string(),
2411 args: vec![arg],
2412 },
2413 "trim" | "tm" => ExprKind::FuncCall {
2414 name: "trim".to_string(),
2415 args: vec![arg],
2416 },
2417 "flatten" | "fl" => ExprKind::FuncCall {
2418 name: "flatten".to_string(),
2419 args: vec![arg],
2420 },
2421 "compact" | "cpt" => ExprKind::FuncCall {
2422 name: "compact".to_string(),
2423 args: vec![arg],
2424 },
2425 "shuffle" | "shuf" => ExprKind::FuncCall {
2426 name: "shuffle".to_string(),
2427 args: vec![arg],
2428 },
2429 "frequencies" | "freq" | "frq" => ExprKind::FuncCall {
2430 name: "frequencies".to_string(),
2431 args: vec![arg],
2432 },
2433 "dedup" | "dup" => ExprKind::FuncCall {
2434 name: "dedup".to_string(),
2435 args: vec![arg],
2436 },
2437 "enumerate" | "en" => ExprKind::FuncCall {
2438 name: "enumerate".to_string(),
2439 args: vec![arg],
2440 },
2441 "lines" | "ln" => ExprKind::FuncCall {
2442 name: "lines".to_string(),
2443 args: vec![arg],
2444 },
2445 "words" | "wd" => ExprKind::FuncCall {
2446 name: "words".to_string(),
2447 args: vec![arg],
2448 },
2449 "chars" | "ch" => ExprKind::FuncCall {
2450 name: "chars".to_string(),
2451 args: vec![arg],
2452 },
2453 "digits" | "dg" => ExprKind::FuncCall {
2454 name: "digits".to_string(),
2455 args: vec![arg],
2456 },
2457 "letters" | "lts" => ExprKind::FuncCall {
2458 name: "letters".to_string(),
2459 args: vec![arg],
2460 },
2461 "letters_uc" => ExprKind::FuncCall {
2462 name: "letters_uc".to_string(),
2463 args: vec![arg],
2464 },
2465 "letters_lc" => ExprKind::FuncCall {
2466 name: "letters_lc".to_string(),
2467 args: vec![arg],
2468 },
2469 "punctuation" | "punct" => ExprKind::FuncCall {
2470 name: "punctuation".to_string(),
2471 args: vec![arg],
2472 },
2473 "sentences" | "sents" => ExprKind::FuncCall {
2474 name: "sentences".to_string(),
2475 args: vec![arg],
2476 },
2477 "paragraphs" | "paras" => ExprKind::FuncCall {
2478 name: "paragraphs".to_string(),
2479 args: vec![arg],
2480 },
2481 "sections" | "sects" => ExprKind::FuncCall {
2482 name: "sections".to_string(),
2483 args: vec![arg],
2484 },
2485 "numbers" | "nums" => ExprKind::FuncCall {
2486 name: "numbers".to_string(),
2487 args: vec![arg],
2488 },
2489 "graphemes" | "grs" => ExprKind::FuncCall {
2490 name: "graphemes".to_string(),
2491 args: vec![arg],
2492 },
2493 "columns" | "cols" => ExprKind::FuncCall {
2494 name: "columns".to_string(),
2495 args: vec![arg],
2496 },
2497 "slurp" | "sl" => ExprKind::Slurp(Box::new(arg)),
2499 "chdir" => ExprKind::Chdir(Box::new(arg)),
2500 "stat" => ExprKind::Stat(Box::new(arg)),
2501 "lstat" => ExprKind::Lstat(Box::new(arg)),
2502 "readlink" => ExprKind::Readlink(Box::new(arg)),
2503 "readdir" => ExprKind::Readdir(Box::new(arg)),
2504 "close" => ExprKind::Close(Box::new(arg)),
2505 "basename" | "bn" => ExprKind::FuncCall {
2506 name: "basename".to_string(),
2507 args: vec![arg],
2508 },
2509 "dirname" | "dn" => ExprKind::FuncCall {
2510 name: "dirname".to_string(),
2511 args: vec![arg],
2512 },
2513 "realpath" | "rp" => ExprKind::FuncCall {
2514 name: "realpath".to_string(),
2515 args: vec![arg],
2516 },
2517 "which" | "wh" => ExprKind::FuncCall {
2518 name: "which".to_string(),
2519 args: vec![arg],
2520 },
2521 "eval" => ExprKind::Eval(Box::new(arg)),
2523 "require" => ExprKind::Require(Box::new(arg)),
2524 "study" => ExprKind::Study(Box::new(arg)),
2525 "snake_case" | "sc" => ExprKind::FuncCall {
2527 name: "snake_case".to_string(),
2528 args: vec![arg],
2529 },
2530 "camel_case" | "cc" => ExprKind::FuncCall {
2531 name: "camel_case".to_string(),
2532 args: vec![arg],
2533 },
2534 "kebab_case" | "kc" => ExprKind::FuncCall {
2535 name: "kebab_case".to_string(),
2536 args: vec![arg],
2537 },
2538 "to_json" | "tj" => ExprKind::FuncCall {
2540 name: "to_json".to_string(),
2541 args: vec![arg],
2542 },
2543 "to_yaml" | "ty" => ExprKind::FuncCall {
2544 name: "to_yaml".to_string(),
2545 args: vec![arg],
2546 },
2547 "to_toml" | "tt" => ExprKind::FuncCall {
2548 name: "to_toml".to_string(),
2549 args: vec![arg],
2550 },
2551 "to_csv" | "tc" => ExprKind::FuncCall {
2552 name: "to_csv".to_string(),
2553 args: vec![arg],
2554 },
2555 "to_xml" | "tx" => ExprKind::FuncCall {
2556 name: "to_xml".to_string(),
2557 args: vec![arg],
2558 },
2559 "to_html" | "th" => ExprKind::FuncCall {
2560 name: "to_html".to_string(),
2561 args: vec![arg],
2562 },
2563 "to_markdown" | "to_md" | "tmd" => ExprKind::FuncCall {
2564 name: "to_markdown".to_string(),
2565 args: vec![arg],
2566 },
2567 "xopen" | "xo" => ExprKind::FuncCall {
2568 name: "xopen".to_string(),
2569 args: vec![arg],
2570 },
2571 "clip" | "clipboard" | "pbcopy" => ExprKind::FuncCall {
2572 name: "clip".to_string(),
2573 args: vec![arg],
2574 },
2575 "to_table" | "table" | "tbl" => ExprKind::FuncCall {
2576 name: "to_table".to_string(),
2577 args: vec![arg],
2578 },
2579 "sparkline" | "spark" => ExprKind::FuncCall {
2580 name: "sparkline".to_string(),
2581 args: vec![arg],
2582 },
2583 "bar_chart" | "bars" => ExprKind::FuncCall {
2584 name: "bar_chart".to_string(),
2585 args: vec![arg],
2586 },
2587 "flame" | "flamechart" => ExprKind::FuncCall {
2588 name: "flame".to_string(),
2589 args: vec![arg],
2590 },
2591 "ddump" | "dd" => ExprKind::FuncCall {
2592 name: "ddump".to_string(),
2593 args: vec![arg],
2594 },
2595 "say" => {
2596 if !crate::compat_mode() {
2597 return Err(self.syntax_err("stryke uses `p` instead of `say` (this is not Perl 5)", line));
2598 }
2599 ExprKind::Say {
2600 handle: None,
2601 args: vec![arg],
2602 }
2603 }
2604 "p" => ExprKind::Say {
2605 handle: None,
2606 args: vec![arg],
2607 },
2608 "print" => ExprKind::Print {
2609 handle: None,
2610 args: vec![arg],
2611 },
2612 "warn" => ExprKind::Warn(vec![arg]),
2613 "die" => ExprKind::Die(vec![arg]),
2614 "stringify" | "str" => ExprKind::FuncCall {
2615 name: "stringify".to_string(),
2616 args: vec![arg],
2617 },
2618 "json_decode" | "jd" => ExprKind::FuncCall {
2619 name: "json_decode".to_string(),
2620 args: vec![arg],
2621 },
2622 "yaml_decode" | "yd" => ExprKind::FuncCall {
2623 name: "yaml_decode".to_string(),
2624 args: vec![arg],
2625 },
2626 "toml_decode" | "td" => ExprKind::FuncCall {
2627 name: "toml_decode".to_string(),
2628 args: vec![arg],
2629 },
2630 "xml_decode" | "xd" => ExprKind::FuncCall {
2631 name: "xml_decode".to_string(),
2632 args: vec![arg],
2633 },
2634 "json_encode" | "je" => ExprKind::FuncCall {
2635 name: "json_encode".to_string(),
2636 args: vec![arg],
2637 },
2638 "yaml_encode" | "ye" => ExprKind::FuncCall {
2639 name: "yaml_encode".to_string(),
2640 args: vec![arg],
2641 },
2642 "toml_encode" | "te" => ExprKind::FuncCall {
2643 name: "toml_encode".to_string(),
2644 args: vec![arg],
2645 },
2646 "xml_encode" | "xe" => ExprKind::FuncCall {
2647 name: "xml_encode".to_string(),
2648 args: vec![arg],
2649 },
2650 "base64_encode" | "b64e" => ExprKind::FuncCall {
2652 name: "base64_encode".to_string(),
2653 args: vec![arg],
2654 },
2655 "base64_decode" | "b64d" => ExprKind::FuncCall {
2656 name: "base64_decode".to_string(),
2657 args: vec![arg],
2658 },
2659 "hex_encode" | "hxe" => ExprKind::FuncCall {
2660 name: "hex_encode".to_string(),
2661 args: vec![arg],
2662 },
2663 "hex_decode" | "hxd" => ExprKind::FuncCall {
2664 name: "hex_decode".to_string(),
2665 args: vec![arg],
2666 },
2667 "url_encode" | "uri_escape" | "ue" => ExprKind::FuncCall {
2668 name: "url_encode".to_string(),
2669 args: vec![arg],
2670 },
2671 "url_decode" | "uri_unescape" | "ud" => ExprKind::FuncCall {
2672 name: "url_decode".to_string(),
2673 args: vec![arg],
2674 },
2675 "gzip" | "gz" => ExprKind::FuncCall {
2676 name: "gzip".to_string(),
2677 args: vec![arg],
2678 },
2679 "gunzip" | "ugz" => ExprKind::FuncCall {
2680 name: "gunzip".to_string(),
2681 args: vec![arg],
2682 },
2683 "zstd" | "zst" => ExprKind::FuncCall {
2684 name: "zstd".to_string(),
2685 args: vec![arg],
2686 },
2687 "zstd_decode" | "uzst" => ExprKind::FuncCall {
2688 name: "zstd_decode".to_string(),
2689 args: vec![arg],
2690 },
2691 "sha256" | "s256" => ExprKind::FuncCall {
2693 name: "sha256".to_string(),
2694 args: vec![arg],
2695 },
2696 "sha1" | "s1" => ExprKind::FuncCall {
2697 name: "sha1".to_string(),
2698 args: vec![arg],
2699 },
2700 "md5" | "m5" => ExprKind::FuncCall {
2701 name: "md5".to_string(),
2702 args: vec![arg],
2703 },
2704 "uuid" | "uid" => ExprKind::FuncCall {
2705 name: "uuid".to_string(),
2706 args: vec![arg],
2707 },
2708 "datetime_utc" | "utc" => ExprKind::FuncCall {
2710 name: "datetime_utc".to_string(),
2711 args: vec![arg],
2712 },
2713 "e" | "fore" | "ep" => ExprKind::ForEachExpr {
2716 block: vec![Statement {
2717 label: None,
2718 kind: StmtKind::Expression(Expr {
2719 kind: ExprKind::Say {
2720 handle: None,
2721 args: vec![Expr {
2722 kind: ExprKind::ScalarVar("_".into()),
2723 line,
2724 }],
2725 },
2726 line,
2727 }),
2728 line,
2729 }],
2730 list: Box::new(arg),
2731 },
2732 _ => ExprKind::FuncCall {
2734 name: name.to_string(),
2735 args: vec![arg],
2736 },
2737 };
2738 Ok(Expr { kind, line })
2739 }
2740
2741 fn parse_thread_stage_with_block(&mut self, name: &str, line: usize) -> PerlResult<Expr> {
2744 let block = self.parse_block()?;
2745 let placeholder = self.pipe_placeholder_list(line);
2747
2748 match name {
2749 "map" | "flat_map" | "maps" | "flat_maps" => {
2750 let flatten_array_refs = matches!(name, "flat_map" | "flat_maps");
2751 let stream = matches!(name, "maps" | "flat_maps");
2752 Ok(Expr {
2753 kind: ExprKind::MapExpr {
2754 block,
2755 list: Box::new(placeholder),
2756 flatten_array_refs,
2757 stream,
2758 },
2759 line,
2760 })
2761 }
2762 "grep" | "greps" | "filter" | "fi" | "find_all" | "gr" => {
2763 let keyword = match name {
2764 "grep" | "gr" => crate::ast::GrepBuiltinKeyword::Grep,
2765 "greps" => crate::ast::GrepBuiltinKeyword::Greps,
2766 "filter" | "fi" => crate::ast::GrepBuiltinKeyword::Filter,
2767 "find_all" => crate::ast::GrepBuiltinKeyword::FindAll,
2768 _ => unreachable!(),
2769 };
2770 Ok(Expr {
2771 kind: ExprKind::GrepExpr {
2772 block,
2773 list: Box::new(placeholder),
2774 keyword,
2775 },
2776 line,
2777 })
2778 }
2779 "sort" | "so" => Ok(Expr {
2780 kind: ExprKind::SortExpr {
2781 cmp: Some(SortComparator::Block(block)),
2782 list: Box::new(placeholder),
2783 },
2784 line,
2785 }),
2786 "reduce" | "rd" => Ok(Expr {
2787 kind: ExprKind::ReduceExpr {
2788 block,
2789 list: Box::new(placeholder),
2790 },
2791 line,
2792 }),
2793 "fore" | "e" | "ep" => Ok(Expr {
2794 kind: ExprKind::ForEachExpr {
2795 block,
2796 list: Box::new(placeholder),
2797 },
2798 line,
2799 }),
2800 "pmap" | "pflat_map" | "pmaps" | "pflat_maps" => Ok(Expr {
2801 kind: ExprKind::PMapExpr {
2802 block,
2803 list: Box::new(placeholder),
2804 progress: None,
2805 flat_outputs: name == "pflat_map" || name == "pflat_maps",
2806 on_cluster: None,
2807 stream: name == "pmaps" || name == "pflat_maps",
2808 },
2809 line,
2810 }),
2811 "pgrep" | "pgreps" => Ok(Expr {
2812 kind: ExprKind::PGrepExpr {
2813 block,
2814 list: Box::new(placeholder),
2815 progress: None,
2816 stream: name == "pgreps",
2817 },
2818 line,
2819 }),
2820 "pfor" => Ok(Expr {
2821 kind: ExprKind::PForExpr {
2822 block,
2823 list: Box::new(placeholder),
2824 progress: None,
2825 },
2826 line,
2827 }),
2828 "preduce" => Ok(Expr {
2829 kind: ExprKind::PReduceExpr {
2830 block,
2831 list: Box::new(placeholder),
2832 progress: None,
2833 },
2834 line,
2835 }),
2836 "pcache" => Ok(Expr {
2837 kind: ExprKind::PcacheExpr {
2838 block,
2839 list: Box::new(placeholder),
2840 progress: None,
2841 },
2842 line,
2843 }),
2844 "psort" => Ok(Expr {
2845 kind: ExprKind::PSortExpr {
2846 cmp: Some(block),
2847 list: Box::new(placeholder),
2848 progress: None,
2849 },
2850 line,
2851 }),
2852 _ => {
2853 let code_ref = Expr {
2855 kind: ExprKind::CodeRef {
2856 params: vec![],
2857 body: block,
2858 },
2859 line,
2860 };
2861 Ok(Expr {
2862 kind: ExprKind::FuncCall {
2863 name: name.to_string(),
2864 args: vec![code_ref],
2865 },
2866 line,
2867 })
2868 }
2869 }
2870 }
2871
2872 fn parse_tie_stmt(&mut self) -> PerlResult<Statement> {
2874 let line = self.peek_line();
2875 self.advance(); let target = match self.peek().clone() {
2877 Token::HashVar(h) => {
2878 self.advance();
2879 TieTarget::Hash(h)
2880 }
2881 Token::ArrayVar(a) => {
2882 self.advance();
2883 TieTarget::Array(a)
2884 }
2885 Token::ScalarVar(s) => {
2886 self.advance();
2887 TieTarget::Scalar(s)
2888 }
2889 tok => {
2890 return Err(self.syntax_err(
2891 format!("tie expects $scalar, @array, or %hash, got {:?}", tok),
2892 self.peek_line(),
2893 ));
2894 }
2895 };
2896 self.expect(&Token::Comma)?;
2897 let class = self.parse_assign_expr()?;
2898 let mut args = Vec::new();
2899 while self.eat(&Token::Comma) {
2900 if matches!(self.peek(), Token::Semicolon | Token::RBrace | Token::Eof) {
2901 break;
2902 }
2903 args.push(self.parse_assign_expr()?);
2904 }
2905 self.eat(&Token::Semicolon);
2906 Ok(Statement {
2907 label: None,
2908 kind: StmtKind::Tie {
2909 target,
2910 class,
2911 args,
2912 },
2913 line,
2914 })
2915 }
2916
2917 fn parse_given(&mut self) -> PerlResult<Statement> {
2919 let line = self.peek_line();
2920 self.advance();
2921 self.expect(&Token::LParen)?;
2922 let topic = self.parse_expression()?;
2923 self.expect(&Token::RParen)?;
2924 let body = self.parse_block()?;
2925 self.eat(&Token::Semicolon);
2926 Ok(Statement {
2927 label: None,
2928 kind: StmtKind::Given { topic, body },
2929 line,
2930 })
2931 }
2932
2933 fn parse_when_stmt(&mut self) -> PerlResult<Statement> {
2935 let line = self.peek_line();
2936 self.advance();
2937 self.expect(&Token::LParen)?;
2938 let cond = self.parse_expression()?;
2939 self.expect(&Token::RParen)?;
2940 let body = self.parse_block()?;
2941 self.eat(&Token::Semicolon);
2942 Ok(Statement {
2943 label: None,
2944 kind: StmtKind::When { cond, body },
2945 line,
2946 })
2947 }
2948
2949 fn parse_default_stmt(&mut self) -> PerlResult<Statement> {
2951 let line = self.peek_line();
2952 self.advance();
2953 let body = self.parse_block()?;
2954 self.eat(&Token::Semicolon);
2955 Ok(Statement {
2956 label: None,
2957 kind: StmtKind::DefaultCase { body },
2958 line,
2959 })
2960 }
2961
2962 fn parse_cond_expr(&mut self, line: usize) -> PerlResult<Expr> {
2968 self.expect(&Token::LBrace)?;
2969
2970 let mut arms: Vec<(Expr, Block)> = Vec::new();
2971 let mut else_block: Option<Block> = None;
2972
2973 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
2974 let arm_line = self.peek_line();
2975
2976 let is_default = matches!(self.peek(), Token::Ident(ref s) if s == "default")
2978 && matches!(self.peek_at(1), Token::FatArrow);
2979
2980 if is_default {
2981 self.advance(); self.advance(); let body = if matches!(self.peek(), Token::LBrace) {
2984 self.parse_block()?
2985 } else {
2986 let expr = self.parse_assign_expr()?;
2987 vec![Statement {
2988 label: None,
2989 kind: StmtKind::Expression(expr),
2990 line: arm_line,
2991 }]
2992 };
2993 else_block = Some(body);
2994 self.eat(&Token::Comma);
2995 break; }
2997
2998 let condition = self.parse_assign_expr()?;
3000 self.expect(&Token::FatArrow)?;
3001
3002 let body = if matches!(self.peek(), Token::LBrace) {
3003 self.parse_block()?
3004 } else {
3005 let expr = self.parse_assign_expr()?;
3006 vec![Statement {
3007 label: None,
3008 kind: StmtKind::Expression(expr),
3009 line: arm_line,
3010 }]
3011 };
3012
3013 arms.push((condition, body));
3014 self.eat(&Token::Comma);
3015 }
3016
3017 self.expect(&Token::RBrace)?;
3018
3019 if arms.is_empty() {
3020 return Err(self.syntax_err("cond requires at least one condition arm", line));
3021 }
3022
3023 let (first_cond, first_body) = arms.remove(0);
3025 let elsifs: Vec<(Expr, Block)> = arms;
3026
3027 let if_stmt = Statement {
3029 label: None,
3030 kind: StmtKind::If {
3031 condition: first_cond,
3032 body: first_body,
3033 elsifs,
3034 else_block,
3035 },
3036 line,
3037 };
3038 let inner = Expr {
3039 kind: ExprKind::CodeRef {
3040 params: vec![],
3041 body: vec![if_stmt],
3042 },
3043 line,
3044 };
3045 Ok(Expr {
3046 kind: ExprKind::Do(Box::new(inner)),
3047 line,
3048 })
3049 }
3050
3051 fn parse_algebraic_match_expr(&mut self, line: usize) -> PerlResult<Expr> {
3053 self.expect(&Token::LParen)?;
3054 let subject = self.parse_expression()?;
3055 self.expect(&Token::RParen)?;
3056 self.expect(&Token::LBrace)?;
3057 let mut arms = Vec::new();
3058 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
3059 if self.eat(&Token::Semicolon) {
3060 continue;
3061 }
3062 let pattern = self.parse_match_pattern()?;
3063 let guard = if matches!(self.peek(), Token::Ident(ref s) if s == "if") {
3064 self.advance();
3065 Some(Box::new(self.parse_assign_expr()?))
3068 } else {
3069 None
3070 };
3071 self.expect(&Token::FatArrow)?;
3072 let body = self.parse_assign_expr()?;
3074 arms.push(MatchArm {
3075 pattern,
3076 guard,
3077 body,
3078 });
3079 self.eat(&Token::Comma);
3080 }
3081 self.expect(&Token::RBrace)?;
3082 Ok(Expr {
3083 kind: ExprKind::AlgebraicMatch {
3084 subject: Box::new(subject),
3085 arms,
3086 },
3087 line,
3088 })
3089 }
3090
3091 fn parse_match_pattern(&mut self) -> PerlResult<MatchPattern> {
3092 match self.peek().clone() {
3093 Token::Regex(pattern, flags, _delim) => {
3094 self.advance();
3095 Ok(MatchPattern::Regex { pattern, flags })
3096 }
3097 Token::Ident(ref s) if s == "_" => {
3098 self.advance();
3099 Ok(MatchPattern::Any)
3100 }
3101 Token::Ident(ref s) if s == "Some" => {
3102 self.advance();
3103 self.expect(&Token::LParen)?;
3104 let name = self.parse_scalar_var_name()?;
3105 self.expect(&Token::RParen)?;
3106 Ok(MatchPattern::OptionSome(name))
3107 }
3108 Token::LBracket => self.parse_match_array_pattern(),
3109 Token::LBrace => self.parse_match_hash_pattern(),
3110 Token::LParen => {
3111 self.advance();
3112 let e = self.parse_expression()?;
3113 self.expect(&Token::RParen)?;
3114 Ok(MatchPattern::Value(Box::new(e)))
3115 }
3116 _ => {
3117 let e = self.parse_assign_expr()?;
3118 Ok(MatchPattern::Value(Box::new(e)))
3119 }
3120 }
3121 }
3122
3123 fn parse_match_array_elems_until_rbracket(&mut self) -> PerlResult<Vec<MatchArrayElem>> {
3125 let mut elems = Vec::new();
3126 if self.eat(&Token::RBracket) {
3127 return Ok(vec![]);
3128 }
3129 loop {
3130 if matches!(self.peek(), Token::Star) {
3131 self.advance();
3132 elems.push(MatchArrayElem::Rest);
3133 self.eat(&Token::Comma);
3134 if !matches!(self.peek(), Token::RBracket) {
3135 return Err(self.syntax_err(
3136 "`*` must be the last element in an array match pattern",
3137 self.peek_line(),
3138 ));
3139 }
3140 self.expect(&Token::RBracket)?;
3141 return Ok(elems);
3142 }
3143 if let Token::ArrayVar(name) = self.peek().clone() {
3144 self.advance();
3145 elems.push(MatchArrayElem::RestBind(name));
3146 self.eat(&Token::Comma);
3147 if !matches!(self.peek(), Token::RBracket) {
3148 return Err(self.syntax_err(
3149 "`@name` rest bind must be the last element in an array match pattern",
3150 self.peek_line(),
3151 ));
3152 }
3153 self.expect(&Token::RBracket)?;
3154 return Ok(elems);
3155 }
3156 if let Token::ScalarVar(name) = self.peek().clone() {
3157 self.advance();
3158 elems.push(MatchArrayElem::CaptureScalar(name));
3159 if self.eat(&Token::Comma) {
3160 if matches!(self.peek(), Token::RBracket) {
3161 break;
3162 }
3163 continue;
3164 }
3165 break;
3166 }
3167 let e = self.parse_assign_expr()?;
3168 elems.push(MatchArrayElem::Expr(e));
3169 if self.eat(&Token::Comma) {
3170 if matches!(self.peek(), Token::RBracket) {
3171 break;
3172 }
3173 continue;
3174 }
3175 break;
3176 }
3177 self.expect(&Token::RBracket)?;
3178 Ok(elems)
3179 }
3180
3181 fn parse_match_array_pattern(&mut self) -> PerlResult<MatchPattern> {
3182 self.expect(&Token::LBracket)?;
3183 let elems = self.parse_match_array_elems_until_rbracket()?;
3184 Ok(MatchPattern::Array(elems))
3185 }
3186
3187 fn parse_match_hash_pattern(&mut self) -> PerlResult<MatchPattern> {
3188 self.expect(&Token::LBrace)?;
3189 let mut pairs = Vec::new();
3190 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
3191 if self.eat(&Token::Semicolon) {
3192 continue;
3193 }
3194 let key = self.parse_assign_expr()?;
3195 self.expect(&Token::FatArrow)?;
3196 match self.advance().0 {
3197 Token::Ident(ref s) if s == "_" => {
3198 pairs.push(MatchHashPair::KeyOnly { key });
3199 }
3200 Token::ScalarVar(name) => {
3201 pairs.push(MatchHashPair::Capture { key, name });
3202 }
3203 tok => {
3204 return Err(self.syntax_err(
3205 format!(
3206 "hash match pattern must bind with `=> $name` or `=> _`, got {:?}",
3207 tok
3208 ),
3209 self.peek_line(),
3210 ));
3211 }
3212 }
3213 self.eat(&Token::Comma);
3214 }
3215 self.expect(&Token::RBrace)?;
3216 Ok(MatchPattern::Hash(pairs))
3217 }
3218
3219 fn parse_eval_timeout(&mut self) -> PerlResult<Statement> {
3221 let line = self.peek_line();
3222 self.advance();
3223 let timeout = self.parse_postfix()?;
3224 let body = self.parse_block_or_bareword_block_no_args()?;
3225 self.eat(&Token::Semicolon);
3226 Ok(Statement {
3227 label: None,
3228 kind: StmtKind::EvalTimeout { timeout, body },
3229 line,
3230 })
3231 }
3232
3233 fn mark_match_scalar_g_for_boolean_condition(cond: &mut Expr) {
3234 match &mut cond.kind {
3235 ExprKind::Match {
3236 flags, scalar_g, ..
3237 } if flags.contains('g') => {
3238 *scalar_g = true;
3239 }
3240 ExprKind::UnaryOp {
3241 op: UnaryOp::LogNot,
3242 expr,
3243 } => {
3244 if let ExprKind::Match {
3245 flags, scalar_g, ..
3246 } = &mut expr.kind
3247 {
3248 if flags.contains('g') {
3249 *scalar_g = true;
3250 }
3251 }
3252 }
3253 _ => {}
3254 }
3255 }
3256
3257 fn parse_if(&mut self) -> PerlResult<Statement> {
3258 let line = self.peek_line();
3259 self.advance(); if matches!(self.peek(), Token::Ident(ref s) if s == "let") {
3261 if crate::compat_mode() {
3262 return Err(self.syntax_err(
3263 "`if let` is a stryke extension (disabled by --compat)",
3264 line,
3265 ));
3266 }
3267 return self.parse_if_let(line);
3268 }
3269 self.expect(&Token::LParen)?;
3270 let mut cond = self.parse_expression()?;
3271 Self::mark_match_scalar_g_for_boolean_condition(&mut cond);
3272 self.expect(&Token::RParen)?;
3273 let body = self.parse_block()?;
3274
3275 let mut elsifs = Vec::new();
3276 let mut else_block = None;
3277
3278 loop {
3279 if let Token::Ident(ref kw) = self.peek().clone() {
3280 if kw == "elsif" {
3281 self.advance();
3282 self.expect(&Token::LParen)?;
3283 let mut c = self.parse_expression()?;
3284 Self::mark_match_scalar_g_for_boolean_condition(&mut c);
3285 self.expect(&Token::RParen)?;
3286 let b = self.parse_block()?;
3287 elsifs.push((c, b));
3288 continue;
3289 }
3290 if kw == "else" {
3291 self.advance();
3292 else_block = Some(self.parse_block()?);
3293 }
3294 }
3295 break;
3296 }
3297
3298 Ok(Statement {
3299 label: None,
3300 kind: StmtKind::If {
3301 condition: cond,
3302 body,
3303 elsifs,
3304 else_block,
3305 },
3306 line,
3307 })
3308 }
3309
3310 fn parse_if_let(&mut self, line: usize) -> PerlResult<Statement> {
3312 self.advance(); let pattern = self.parse_match_pattern()?;
3314 self.expect(&Token::Assign)?;
3315 self.suppress_scalar_hash_brace = self.suppress_scalar_hash_brace.saturating_add(1);
3317 let rhs = self.parse_assign_expr();
3318 self.suppress_scalar_hash_brace = self.suppress_scalar_hash_brace.saturating_sub(1);
3319 let rhs = rhs?;
3320 let then_block = self.parse_block()?;
3321 let else_block_opt = match self.peek().clone() {
3322 Token::Ident(ref kw) if kw == "else" => {
3323 self.advance();
3324 Some(self.parse_block()?)
3325 }
3326 Token::Ident(ref kw) if kw == "elsif" => {
3327 return Err(self.syntax_err(
3328 "`if let` does not support `elsif`; use `else { }` or a full `match`",
3329 self.peek_line(),
3330 ));
3331 }
3332 _ => None,
3333 };
3334 let then_expr = Self::expr_do_anon_block(then_block, line);
3335 let else_expr = if let Some(eb) = else_block_opt {
3336 Self::expr_do_anon_block(eb, line)
3337 } else {
3338 Expr {
3339 kind: ExprKind::Undef,
3340 line,
3341 }
3342 };
3343 let arms = vec![
3344 MatchArm {
3345 pattern,
3346 guard: None,
3347 body: then_expr,
3348 },
3349 MatchArm {
3350 pattern: MatchPattern::Any,
3351 guard: None,
3352 body: else_expr,
3353 },
3354 ];
3355 Ok(Statement {
3356 label: None,
3357 kind: StmtKind::Expression(Expr {
3358 kind: ExprKind::AlgebraicMatch {
3359 subject: Box::new(rhs),
3360 arms,
3361 },
3362 line,
3363 }),
3364 line,
3365 })
3366 }
3367
3368 fn expr_do_anon_block(block: Block, outer_line: usize) -> Expr {
3369 let inner_line = block.first().map(|s| s.line).unwrap_or(outer_line);
3370 Expr {
3371 kind: ExprKind::Do(Box::new(Expr {
3372 kind: ExprKind::CodeRef {
3373 params: vec![],
3374 body: block,
3375 },
3376 line: inner_line,
3377 })),
3378 line: outer_line,
3379 }
3380 }
3381
3382 fn parse_unless(&mut self) -> PerlResult<Statement> {
3383 let line = self.peek_line();
3384 self.advance(); self.expect(&Token::LParen)?;
3386 let mut cond = self.parse_expression()?;
3387 Self::mark_match_scalar_g_for_boolean_condition(&mut cond);
3388 self.expect(&Token::RParen)?;
3389 let body = self.parse_block()?;
3390 let else_block = if let Token::Ident(ref kw) = self.peek().clone() {
3391 if kw == "else" {
3392 self.advance();
3393 Some(self.parse_block()?)
3394 } else {
3395 None
3396 }
3397 } else {
3398 None
3399 };
3400 Ok(Statement {
3401 label: None,
3402 kind: StmtKind::Unless {
3403 condition: cond,
3404 body,
3405 else_block,
3406 },
3407 line,
3408 })
3409 }
3410
3411 fn parse_while(&mut self) -> PerlResult<Statement> {
3412 let line = self.peek_line();
3413 self.advance(); if matches!(self.peek(), Token::Ident(ref s) if s == "let") {
3415 if crate::compat_mode() {
3416 return Err(self.syntax_err(
3417 "`while let` is a stryke extension (disabled by --compat)",
3418 line,
3419 ));
3420 }
3421 return self.parse_while_let(line);
3422 }
3423 self.expect(&Token::LParen)?;
3424 let mut cond = self.parse_expression()?;
3425 Self::mark_match_scalar_g_for_boolean_condition(&mut cond);
3426 self.expect(&Token::RParen)?;
3427 let body = self.parse_block()?;
3428 let continue_block = self.parse_optional_continue_block()?;
3429 Ok(Statement {
3430 label: None,
3431 kind: StmtKind::While {
3432 condition: cond,
3433 body,
3434 label: None,
3435 continue_block,
3436 },
3437 line,
3438 })
3439 }
3440
3441 fn parse_while_let(&mut self, line: usize) -> PerlResult<Statement> {
3444 self.advance(); let pattern = self.parse_match_pattern()?;
3446 self.expect(&Token::Assign)?;
3447 self.suppress_scalar_hash_brace = self.suppress_scalar_hash_brace.saturating_add(1);
3448 let rhs = self.parse_assign_expr();
3449 self.suppress_scalar_hash_brace = self.suppress_scalar_hash_brace.saturating_sub(1);
3450 let rhs = rhs?;
3451 let mut user_body = self.parse_block()?;
3452 let continue_block = self.parse_optional_continue_block()?;
3453 user_body.push(Statement::new(
3454 StmtKind::Expression(Expr {
3455 kind: ExprKind::Integer(1),
3456 line,
3457 }),
3458 line,
3459 ));
3460 let tmp = format!("__while_let_{}", self.alloc_desugar_tmp());
3461 let match_expr = Expr {
3462 kind: ExprKind::AlgebraicMatch {
3463 subject: Box::new(rhs),
3464 arms: vec![
3465 MatchArm {
3466 pattern,
3467 guard: None,
3468 body: Self::expr_do_anon_block(user_body, line),
3469 },
3470 MatchArm {
3471 pattern: MatchPattern::Any,
3472 guard: None,
3473 body: Expr {
3474 kind: ExprKind::Integer(0),
3475 line,
3476 },
3477 },
3478 ],
3479 },
3480 line,
3481 };
3482 let my_stmt = Statement::new(
3483 StmtKind::My(vec![VarDecl {
3484 sigil: Sigil::Scalar,
3485 name: tmp.clone(),
3486 initializer: Some(match_expr),
3487 frozen: false,
3488 type_annotation: None,
3489 }]),
3490 line,
3491 );
3492 let unless_last = Statement::new(
3493 StmtKind::Unless {
3494 condition: Expr {
3495 kind: ExprKind::ScalarVar(tmp),
3496 line,
3497 },
3498 body: vec![Statement::new(StmtKind::Last(None), line)],
3499 else_block: None,
3500 },
3501 line,
3502 );
3503 Ok(Statement::new(
3504 StmtKind::While {
3505 condition: Expr {
3506 kind: ExprKind::Integer(1),
3507 line,
3508 },
3509 body: vec![my_stmt, unless_last],
3510 label: None,
3511 continue_block,
3512 },
3513 line,
3514 ))
3515 }
3516
3517 fn parse_until(&mut self) -> PerlResult<Statement> {
3518 let line = self.peek_line();
3519 self.advance(); self.expect(&Token::LParen)?;
3521 let mut cond = self.parse_expression()?;
3522 Self::mark_match_scalar_g_for_boolean_condition(&mut cond);
3523 self.expect(&Token::RParen)?;
3524 let body = self.parse_block()?;
3525 let continue_block = self.parse_optional_continue_block()?;
3526 Ok(Statement {
3527 label: None,
3528 kind: StmtKind::Until {
3529 condition: cond,
3530 body,
3531 label: None,
3532 continue_block,
3533 },
3534 line,
3535 })
3536 }
3537
3538 fn parse_optional_continue_block(&mut self) -> PerlResult<Option<Block>> {
3540 if let Token::Ident(ref kw) = self.peek().clone() {
3541 if kw == "continue" {
3542 self.advance();
3543 return Ok(Some(self.parse_block()?));
3544 }
3545 }
3546 Ok(None)
3547 }
3548
3549 fn parse_for_or_foreach(&mut self) -> PerlResult<Statement> {
3550 let line = self.peek_line();
3551 self.advance(); match self.peek() {
3557 Token::LParen => {
3558 let saved = self.pos;
3563 self.advance(); let mut depth = 1;
3566 let mut has_semi = false;
3567 let mut scan = self.pos;
3568 while scan < self.tokens.len() {
3569 match &self.tokens[scan].0 {
3570 Token::LParen => depth += 1,
3571 Token::RParen => {
3572 depth -= 1;
3573 if depth == 0 {
3574 break;
3575 }
3576 }
3577 Token::Semicolon if depth == 1 => {
3578 has_semi = true;
3579 break;
3580 }
3581 _ => {}
3582 }
3583 scan += 1;
3584 }
3585 self.pos = saved;
3586
3587 if has_semi {
3588 self.parse_c_style_for(line)
3589 } else {
3590 self.expect(&Token::LParen)?;
3592 let list = self.parse_expression()?;
3593 self.expect(&Token::RParen)?;
3594 let body = self.parse_block()?;
3595 let continue_block = self.parse_optional_continue_block()?;
3596 Ok(Statement {
3597 label: None,
3598 kind: StmtKind::Foreach {
3599 var: "_".to_string(),
3600 list,
3601 body,
3602 label: None,
3603 continue_block,
3604 },
3605 line,
3606 })
3607 }
3608 }
3609 Token::Ident(ref kw) if kw == "my" => {
3610 self.advance(); let var = self.parse_scalar_var_name()?;
3612 self.expect(&Token::LParen)?;
3613 let list = self.parse_expression()?;
3614 self.expect(&Token::RParen)?;
3615 let body = self.parse_block()?;
3616 let continue_block = self.parse_optional_continue_block()?;
3617 Ok(Statement {
3618 label: None,
3619 kind: StmtKind::Foreach {
3620 var,
3621 list,
3622 body,
3623 label: None,
3624 continue_block,
3625 },
3626 line,
3627 })
3628 }
3629 Token::ScalarVar(_) => {
3630 let var = self.parse_scalar_var_name()?;
3631 self.expect(&Token::LParen)?;
3632 let list = self.parse_expression()?;
3633 self.expect(&Token::RParen)?;
3634 let body = self.parse_block()?;
3635 let continue_block = self.parse_optional_continue_block()?;
3636 Ok(Statement {
3637 label: None,
3638 kind: StmtKind::Foreach {
3639 var,
3640 list,
3641 body,
3642 label: None,
3643 continue_block,
3644 },
3645 line,
3646 })
3647 }
3648 _ => self.parse_c_style_for(line),
3649 }
3650 }
3651
3652 fn parse_c_style_for(&mut self, line: usize) -> PerlResult<Statement> {
3653 self.expect(&Token::LParen)?;
3654 let init = if self.eat(&Token::Semicolon) {
3655 None
3656 } else {
3657 let s = self.parse_statement()?;
3658 self.eat(&Token::Semicolon);
3659 Some(Box::new(s))
3660 };
3661 let mut condition = if matches!(self.peek(), Token::Semicolon) {
3662 None
3663 } else {
3664 Some(self.parse_expression()?)
3665 };
3666 if let Some(ref mut c) = condition {
3667 Self::mark_match_scalar_g_for_boolean_condition(c);
3668 }
3669 self.expect(&Token::Semicolon)?;
3670 let step = if matches!(self.peek(), Token::RParen) {
3671 None
3672 } else {
3673 Some(self.parse_expression()?)
3674 };
3675 self.expect(&Token::RParen)?;
3676 let body = self.parse_block()?;
3677 let continue_block = self.parse_optional_continue_block()?;
3678 Ok(Statement {
3679 label: None,
3680 kind: StmtKind::For {
3681 init,
3682 condition,
3683 step,
3684 body,
3685 label: None,
3686 continue_block,
3687 },
3688 line,
3689 })
3690 }
3691
3692 fn parse_foreach(&mut self) -> PerlResult<Statement> {
3693 let line = self.peek_line();
3694 self.advance(); let var = match self.peek() {
3696 Token::Ident(ref kw) if kw == "my" => {
3697 self.advance();
3698 self.parse_scalar_var_name()?
3699 }
3700 Token::ScalarVar(_) => self.parse_scalar_var_name()?,
3701 _ => "_".to_string(),
3702 };
3703 self.expect(&Token::LParen)?;
3704 let list = self.parse_expression()?;
3705 self.expect(&Token::RParen)?;
3706 let body = self.parse_block()?;
3707 let continue_block = self.parse_optional_continue_block()?;
3708 Ok(Statement {
3709 label: None,
3710 kind: StmtKind::Foreach {
3711 var,
3712 list,
3713 body,
3714 label: None,
3715 continue_block,
3716 },
3717 line,
3718 })
3719 }
3720
3721 fn parse_scalar_var_name(&mut self) -> PerlResult<String> {
3722 match self.advance() {
3723 (Token::ScalarVar(name), _) => Ok(name),
3724 (tok, line) => {
3725 Err(self.syntax_err(format!("Expected scalar variable, got {:?}", tok), line))
3726 }
3727 }
3728 }
3729
3730 fn parse_legacy_sub_prototype_tail(&mut self) -> PerlResult<String> {
3732 let mut s = String::new();
3733 loop {
3734 match self.peek().clone() {
3735 Token::RParen => {
3736 self.advance();
3737 break;
3738 }
3739 Token::Eof => {
3740 return Err(self.syntax_err(
3741 "Unterminated sub prototype (expected ')' before end of input)",
3742 self.peek_line(),
3743 ));
3744 }
3745 Token::ScalarVar(v) if v == ")" => {
3746 self.advance();
3749 s.push('$');
3750 if matches!(self.peek(), Token::LBrace) {
3751 break;
3752 }
3753 }
3754 Token::Ident(i) => {
3755 let i = i.clone();
3756 self.advance();
3757 s.push_str(&i);
3758 }
3759 Token::Semicolon => {
3760 self.advance();
3761 s.push(';');
3762 }
3763 Token::LParen => {
3764 self.advance();
3765 s.push('(');
3766 }
3767 Token::LBracket => {
3768 self.advance();
3769 s.push('[');
3770 }
3771 Token::RBracket => {
3772 self.advance();
3773 s.push(']');
3774 }
3775 Token::Backslash => {
3776 self.advance();
3777 s.push('\\');
3778 }
3779 Token::Comma => {
3780 self.advance();
3781 s.push(',');
3782 }
3783 Token::ScalarVar(v) => {
3784 let v = v.clone();
3785 self.advance();
3786 s.push('$');
3787 s.push_str(&v);
3788 }
3789 Token::ArrayVar(v) => {
3790 let v = v.clone();
3791 self.advance();
3792 s.push('@');
3793 s.push_str(&v);
3794 }
3795 Token::ArrayAt => {
3797 self.advance();
3798 s.push('@');
3799 }
3800 Token::HashVar(v) => {
3801 let v = v.clone();
3802 self.advance();
3803 s.push('%');
3804 s.push_str(&v);
3805 }
3806 Token::HashPercent => {
3807 self.advance();
3808 s.push('%');
3809 }
3810 Token::Plus => {
3811 self.advance();
3812 s.push('+');
3813 }
3814 Token::Minus => {
3815 self.advance();
3816 s.push('-');
3817 }
3818 Token::BitAnd => {
3819 self.advance();
3820 s.push('&');
3821 }
3822 tok => {
3823 return Err(self.syntax_err(
3824 format!("Unexpected token in sub prototype: {:?}", tok),
3825 self.peek_line(),
3826 ));
3827 }
3828 }
3829 }
3830 Ok(s)
3831 }
3832
3833 fn sub_signature_list_starts_here(&self) -> bool {
3834 match self.peek() {
3835 Token::LBrace | Token::LBracket => true,
3836 Token::ScalarVar(name) if name != "$$" && name != ")" => true,
3837 Token::ArrayVar(_) | Token::HashVar(_) => true,
3838 _ => false,
3839 }
3840 }
3841
3842 fn parse_sub_signature_hash_key(&mut self) -> PerlResult<String> {
3843 let (tok, line) = self.advance();
3844 match tok {
3845 Token::Ident(i) => Ok(i),
3846 Token::SingleString(s) | Token::DoubleString(s) => Ok(s),
3847 tok => Err(self.syntax_err(
3848 format!(
3849 "sub signature: expected hash key (identifier or string), got {:?}",
3850 tok
3851 ),
3852 line,
3853 )),
3854 }
3855 }
3856
3857 fn parse_sub_signature_param_list(&mut self) -> PerlResult<Vec<SubSigParam>> {
3858 let mut params = Vec::new();
3859 loop {
3860 if matches!(self.peek(), Token::RParen) {
3861 break;
3862 }
3863 match self.peek().clone() {
3864 Token::ScalarVar(name) => {
3865 if name == "$$" || name == ")" {
3866 return Err(self.syntax_err(
3867 format!(
3868 "`{name}` cannot start a stryke sub signature (use legacy prototype `($$)` etc.)"
3869 ),
3870 self.peek_line(),
3871 ));
3872 }
3873 self.advance();
3874 let ty = if self.eat(&Token::Colon) {
3875 match self.peek() {
3876 Token::Ident(ref tname) => {
3877 let tname = tname.clone();
3878 self.advance();
3879 Some(match tname.as_str() {
3880 "Int" => PerlTypeName::Int,
3881 "Str" => PerlTypeName::Str,
3882 "Float" => PerlTypeName::Float,
3883 "Bool" => PerlTypeName::Bool,
3884 "Array" => PerlTypeName::Array,
3885 "Hash" => PerlTypeName::Hash,
3886 "Ref" => PerlTypeName::Ref,
3887 "Any" => PerlTypeName::Any,
3888 _ => PerlTypeName::Struct(tname),
3889 })
3890 }
3891 _ => {
3892 return Err(self.syntax_err(
3893 "expected type name after `:` in sub signature",
3894 self.peek_line(),
3895 ));
3896 }
3897 }
3898 } else {
3899 None
3900 };
3901 let default = if self.eat(&Token::Assign) {
3903 Some(Box::new(self.parse_ternary()?))
3904 } else {
3905 None
3906 };
3907 params.push(SubSigParam::Scalar(name, ty, default));
3908 }
3909 Token::ArrayVar(name) => {
3910 self.advance();
3911 let default = if self.eat(&Token::Assign) {
3912 Some(Box::new(self.parse_ternary()?))
3913 } else {
3914 None
3915 };
3916 params.push(SubSigParam::Array(name, default));
3917 }
3918 Token::HashVar(name) => {
3919 self.advance();
3920 let default = if self.eat(&Token::Assign) {
3921 Some(Box::new(self.parse_ternary()?))
3922 } else {
3923 None
3924 };
3925 params.push(SubSigParam::Hash(name, default));
3926 }
3927 Token::LBracket => {
3928 self.advance();
3929 let elems = self.parse_match_array_elems_until_rbracket()?;
3930 params.push(SubSigParam::ArrayDestruct(elems));
3931 }
3932 Token::LBrace => {
3933 self.advance();
3934 let mut pairs = Vec::new();
3935 loop {
3936 if matches!(self.peek(), Token::RBrace | Token::Eof) {
3937 break;
3938 }
3939 if self.eat(&Token::Comma) {
3940 continue;
3941 }
3942 let key = self.parse_sub_signature_hash_key()?;
3943 self.expect(&Token::FatArrow)?;
3944 let bind = self.parse_scalar_var_name()?;
3945 pairs.push((key, bind));
3946 self.eat(&Token::Comma);
3947 }
3948 self.expect(&Token::RBrace)?;
3949 params.push(SubSigParam::HashDestruct(pairs));
3950 }
3951 tok => {
3952 return Err(self.syntax_err(
3953 format!(
3954 "expected `$name`, `[ ... ]`, or `{{ ... }}` in sub signature, got {:?}",
3955 tok
3956 ),
3957 self.peek_line(),
3958 ));
3959 }
3960 }
3961 match self.peek() {
3962 Token::Comma => {
3963 self.advance();
3964 if matches!(self.peek(), Token::RParen) {
3965 return Err(self.syntax_err(
3966 "trailing `,` before `)` in sub signature",
3967 self.peek_line(),
3968 ));
3969 }
3970 }
3971 Token::RParen => break,
3972 _ => {
3973 return Err(self.syntax_err(
3974 format!(
3975 "expected `,` or `)` after sub signature parameter, got {:?}",
3976 self.peek()
3977 ),
3978 self.peek_line(),
3979 ));
3980 }
3981 }
3982 }
3983 Ok(params)
3984 }
3985
3986 fn parse_sub_sig_or_prototype_opt(&mut self) -> PerlResult<(Vec<SubSigParam>, Option<String>)> {
3988 if !matches!(self.peek(), Token::LParen) {
3989 return Ok((vec![], None));
3990 }
3991 self.advance();
3992 if matches!(self.peek(), Token::RParen) {
3993 self.advance();
3994 return Ok((vec![], Some(String::new())));
3995 }
3996 if self.sub_signature_list_starts_here() {
3997 let params = self.parse_sub_signature_param_list()?;
3998 self.expect(&Token::RParen)?;
3999 return Ok((params, None));
4000 }
4001 let proto = self.parse_legacy_sub_prototype_tail()?;
4002 Ok((vec![], Some(proto)))
4003 }
4004
4005 fn parse_sub_attributes(&mut self) -> PerlResult<()> {
4007 while self.eat(&Token::Colon) {
4008 match self.advance() {
4009 (Token::Ident(_), _) => {}
4010 (tok, line) => {
4011 return Err(self.syntax_err(
4012 format!("Expected attribute name after `:`, got {:?}", tok),
4013 line,
4014 ));
4015 }
4016 }
4017 if self.eat(&Token::LParen) {
4018 let mut depth = 1usize;
4019 while depth > 0 {
4020 match self.advance().0 {
4021 Token::LParen => depth += 1,
4022 Token::RParen => {
4023 depth -= 1;
4024 }
4025 Token::Eof => {
4026 return Err(self.syntax_err(
4027 "Unterminated sub attribute argument list",
4028 self.peek_line(),
4029 ));
4030 }
4031 _ => {}
4032 }
4033 }
4034 }
4035 }
4036 Ok(())
4037 }
4038
4039 fn parse_sub_decl(&mut self, is_sub_keyword: bool) -> PerlResult<Statement> {
4040 let line = self.peek_line();
4041 self.advance(); match self.peek().clone() {
4043 Token::Ident(_) => {
4044 let name = self.parse_package_qualified_identifier()?;
4045 if !crate::compat_mode() {
4046 self.check_udf_shadows_builtin(&name, line)?;
4047 }
4048 self.declared_subs.insert(name.clone());
4049 let (params, prototype) = self.parse_sub_sig_or_prototype_opt()?;
4050 self.parse_sub_attributes()?;
4051 let body = self.parse_block()?;
4052 Ok(Statement {
4053 label: None,
4054 kind: StmtKind::SubDecl {
4055 name,
4056 params,
4057 body,
4058 prototype,
4059 },
4060 line,
4061 })
4062 }
4063 Token::LParen | Token::LBrace | Token::Colon => {
4064 if is_sub_keyword && !crate::compat_mode() {
4066 return Err(self.syntax_err(
4067 "stryke uses `fn {}` instead of `fn {}` (this is not Perl 5)",
4068 line,
4069 ));
4070 }
4071 let (params, _prototype) = self.parse_sub_sig_or_prototype_opt()?;
4073 self.parse_sub_attributes()?;
4074 let body = self.parse_block()?;
4075 Ok(Statement {
4076 label: None,
4077 kind: StmtKind::Expression(Expr {
4078 kind: ExprKind::CodeRef { params, body },
4079 line,
4080 }),
4081 line,
4082 })
4083 }
4084 tok => Err(self.syntax_err(
4085 format!("Expected sub name, `(`, `{{`, or `:`, got {:?}", tok),
4086 self.peek_line(),
4087 )),
4088 }
4089 }
4090
4091 fn parse_struct_decl(&mut self) -> PerlResult<Statement> {
4093 let line = self.peek_line();
4094 self.advance(); let name = match self.advance() {
4096 (Token::Ident(n), _) => n,
4097 (tok, err_line) => {
4098 return Err(
4099 self.syntax_err(format!("Expected struct name, got {:?}", tok), err_line)
4100 )
4101 }
4102 };
4103 self.expect(&Token::LBrace)?;
4104 let mut fields = Vec::new();
4105 let mut methods = Vec::new();
4106 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
4107 let is_method = match self.peek() {
4109 Token::Ident(s) => s == "fn" || s == "sub",
4110 _ => false,
4111 };
4112 if is_method {
4113 self.advance(); let method_name = match self.advance() {
4115 (Token::Ident(n), _) => n,
4116 (tok, err_line) => {
4117 return Err(self
4118 .syntax_err(format!("Expected method name, got {:?}", tok), err_line))
4119 }
4120 };
4121 let params = if self.eat(&Token::LParen) {
4123 let p = self.parse_sub_signature_param_list()?;
4124 self.expect(&Token::RParen)?;
4125 p
4126 } else {
4127 Vec::new()
4128 };
4129 let body = self.parse_block()?;
4131 methods.push(crate::ast::StructMethod {
4132 name: method_name,
4133 params,
4134 body,
4135 });
4136 self.eat(&Token::Comma);
4138 self.eat(&Token::Semicolon);
4139 continue;
4140 }
4141
4142 let field_name = match self.advance() {
4143 (Token::Ident(n), _) => n,
4144 (tok, err_line) => {
4145 return Err(
4146 self.syntax_err(format!("Expected field name, got {:?}", tok), err_line)
4147 )
4148 }
4149 };
4150 let ty = if self.eat(&Token::FatArrow) {
4152 self.parse_type_name()?
4153 } else {
4154 crate::ast::PerlTypeName::Any
4155 };
4156 let default = if self.eat(&Token::Assign) {
4157 Some(self.parse_ternary()?)
4159 } else {
4160 None
4161 };
4162 fields.push(StructField {
4163 name: field_name,
4164 ty,
4165 default,
4166 });
4167 if !self.eat(&Token::Comma) {
4168 self.eat(&Token::Semicolon);
4170 }
4171 }
4172 self.expect(&Token::RBrace)?;
4173 self.eat(&Token::Semicolon);
4174 Ok(Statement {
4175 label: None,
4176 kind: StmtKind::StructDecl {
4177 def: StructDef {
4178 name,
4179 fields,
4180 methods,
4181 },
4182 },
4183 line,
4184 })
4185 }
4186
4187 fn parse_enum_decl(&mut self) -> PerlResult<Statement> {
4189 let line = self.peek_line();
4190 self.advance(); let name = match self.advance() {
4192 (Token::Ident(n), _) => n,
4193 (tok, err_line) => {
4194 return Err(self.syntax_err(format!("Expected enum name, got {:?}", tok), err_line))
4195 }
4196 };
4197 self.expect(&Token::LBrace)?;
4198 let mut variants = Vec::new();
4199 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
4200 let variant_name = match self.advance() {
4201 (Token::Ident(n), _) => n,
4202 (tok, err_line) => {
4203 return Err(
4204 self.syntax_err(format!("Expected variant name, got {:?}", tok), err_line)
4205 )
4206 }
4207 };
4208 let ty = if self.eat(&Token::FatArrow) {
4209 Some(self.parse_type_name()?)
4210 } else {
4211 None
4212 };
4213 variants.push(EnumVariant {
4214 name: variant_name,
4215 ty,
4216 });
4217 if !self.eat(&Token::Comma) {
4218 self.eat(&Token::Semicolon);
4219 }
4220 }
4221 self.expect(&Token::RBrace)?;
4222 self.eat(&Token::Semicolon);
4223 Ok(Statement {
4224 label: None,
4225 kind: StmtKind::EnumDecl {
4226 def: EnumDef { name, variants },
4227 },
4228 line,
4229 })
4230 }
4231
4232 fn parse_class_decl(&mut self, is_abstract: bool, is_final: bool) -> PerlResult<Statement> {
4234 use crate::ast::{ClassDef, ClassField, ClassMethod, ClassStaticField, Visibility};
4235 let line = self.peek_line();
4236 self.advance(); let name = match self.advance() {
4238 (Token::Ident(n), _) => n,
4239 (tok, err_line) => {
4240 return Err(self.syntax_err(format!("Expected class name, got {:?}", tok), err_line))
4241 }
4242 };
4243
4244 let mut extends = Vec::new();
4246 if matches!(self.peek(), Token::Ident(ref s) if s == "extends") {
4247 self.advance(); loop {
4249 match self.advance() {
4250 (Token::Ident(parent), _) => extends.push(parent),
4251 (tok, err_line) => {
4252 return Err(self.syntax_err(
4253 format!("Expected parent class name after `extends`, got {:?}", tok),
4254 err_line,
4255 ))
4256 }
4257 }
4258 if !self.eat(&Token::Comma) {
4259 break;
4260 }
4261 }
4262 }
4263
4264 let mut implements = Vec::new();
4266 if matches!(self.peek(), Token::Ident(ref s) if s == "impl") {
4267 self.advance(); loop {
4269 match self.advance() {
4270 (Token::Ident(trait_name), _) => implements.push(trait_name),
4271 (tok, err_line) => {
4272 return Err(self.syntax_err(
4273 format!("Expected trait name after `impl`, got {:?}", tok),
4274 err_line,
4275 ))
4276 }
4277 }
4278 if !self.eat(&Token::Comma) {
4279 break;
4280 }
4281 }
4282 }
4283
4284 self.expect(&Token::LBrace)?;
4285 let mut fields = Vec::new();
4286 let mut methods = Vec::new();
4287 let mut static_fields = Vec::new();
4288
4289 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
4290 let visibility = match self.peek() {
4292 Token::Ident(ref s) if s == "pub" => {
4293 self.advance();
4294 Visibility::Public
4295 }
4296 Token::Ident(ref s) if s == "priv" => {
4297 self.advance();
4298 Visibility::Private
4299 }
4300 Token::Ident(ref s) if s == "prot" => {
4301 self.advance();
4302 Visibility::Protected
4303 }
4304 _ => Visibility::Public, };
4306
4307 if matches!(self.peek(), Token::Ident(ref s) if s == "static") {
4309 self.advance(); if matches!(self.peek(), Token::Ident(ref s) if s == "fn" || s == "sub") {
4313 return Err(self.syntax_err(
4315 "use `fn Self.name` for static methods, not `static fn`",
4316 self.peek_line(),
4317 ));
4318 }
4319
4320 let field_name = match self.advance() {
4321 (Token::Ident(n), _) => n,
4322 (tok, err_line) => {
4323 return Err(self.syntax_err(
4324 format!("Expected static field name, got {:?}", tok),
4325 err_line,
4326 ))
4327 }
4328 };
4329
4330 let ty = if self.eat(&Token::Colon) {
4331 self.parse_type_name()?
4332 } else {
4333 crate::ast::PerlTypeName::Any
4334 };
4335
4336 let default = if self.eat(&Token::Assign) {
4337 Some(self.parse_ternary()?)
4338 } else {
4339 None
4340 };
4341
4342 static_fields.push(ClassStaticField {
4343 name: field_name,
4344 ty,
4345 visibility,
4346 default,
4347 });
4348
4349 if !self.eat(&Token::Comma) {
4350 self.eat(&Token::Semicolon);
4351 }
4352 continue;
4353 }
4354
4355 let method_is_final = matches!(self.peek(), Token::Ident(ref s) if s == "final");
4357 if method_is_final {
4358 self.advance(); }
4360
4361 let is_method = matches!(self.peek(), Token::Ident(ref s) if s == "fn" || s == "sub");
4363 if is_method {
4364 self.advance(); let is_static = matches!(self.peek(), Token::Ident(ref s) if s == "Self");
4368 if is_static {
4369 self.advance(); self.expect(&Token::Dot)?;
4371 }
4372
4373 let method_name = match self.advance() {
4374 (Token::Ident(n), _) => n,
4375 (tok, err_line) => {
4376 return Err(self
4377 .syntax_err(format!("Expected method name, got {:?}", tok), err_line))
4378 }
4379 };
4380
4381 let params = if self.eat(&Token::LParen) {
4383 let p = self.parse_sub_signature_param_list()?;
4384 self.expect(&Token::RParen)?;
4385 p
4386 } else {
4387 Vec::new()
4388 };
4389
4390 let body = if matches!(self.peek(), Token::LBrace) {
4392 Some(self.parse_block()?)
4393 } else {
4394 None
4395 };
4396
4397 methods.push(ClassMethod {
4398 name: method_name,
4399 params,
4400 body,
4401 visibility,
4402 is_static,
4403 is_final: method_is_final,
4404 });
4405 self.eat(&Token::Comma);
4406 self.eat(&Token::Semicolon);
4407 continue;
4408 } else if method_is_final {
4409 return Err(self.syntax_err("`final` must be followed by `fn`", self.peek_line()));
4410 }
4411
4412 let field_name = match self.advance() {
4414 (Token::Ident(n), _) => n,
4415 (tok, err_line) => {
4416 return Err(
4417 self.syntax_err(format!("Expected field name, got {:?}", tok), err_line)
4418 )
4419 }
4420 };
4421
4422 let ty = if self.eat(&Token::Colon) {
4424 self.parse_type_name()?
4425 } else {
4426 crate::ast::PerlTypeName::Any
4427 };
4428
4429 let default = if self.eat(&Token::Assign) {
4431 Some(self.parse_ternary()?)
4432 } else {
4433 None
4434 };
4435
4436 fields.push(ClassField {
4437 name: field_name,
4438 ty,
4439 visibility,
4440 default,
4441 });
4442
4443 if !self.eat(&Token::Comma) {
4444 self.eat(&Token::Semicolon);
4445 }
4446 }
4447
4448 self.expect(&Token::RBrace)?;
4449 self.eat(&Token::Semicolon);
4450
4451 Ok(Statement {
4452 label: None,
4453 kind: StmtKind::ClassDecl {
4454 def: ClassDef {
4455 name,
4456 is_abstract,
4457 is_final,
4458 extends,
4459 implements,
4460 fields,
4461 methods,
4462 static_fields,
4463 },
4464 },
4465 line,
4466 })
4467 }
4468
4469 fn parse_trait_decl(&mut self) -> PerlResult<Statement> {
4471 use crate::ast::{ClassMethod, TraitDef, Visibility};
4472 let line = self.peek_line();
4473 self.advance(); let name = match self.advance() {
4475 (Token::Ident(n), _) => n,
4476 (tok, err_line) => {
4477 return Err(self.syntax_err(format!("Expected trait name, got {:?}", tok), err_line))
4478 }
4479 };
4480
4481 self.expect(&Token::LBrace)?;
4482 let mut methods = Vec::new();
4483
4484 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
4485 let visibility = match self.peek() {
4487 Token::Ident(ref s) if s == "pub" => {
4488 self.advance();
4489 Visibility::Public
4490 }
4491 Token::Ident(ref s) if s == "priv" => {
4492 self.advance();
4493 Visibility::Private
4494 }
4495 Token::Ident(ref s) if s == "prot" => {
4496 self.advance();
4497 Visibility::Protected
4498 }
4499 _ => Visibility::Public,
4500 };
4501
4502 if !matches!(self.peek(), Token::Ident(ref s) if s == "fn" || s == "sub") {
4504 return Err(self.syntax_err("Expected `fn` in trait definition", self.peek_line()));
4505 }
4506 self.advance(); let method_name = match self.advance() {
4509 (Token::Ident(n), _) => n,
4510 (tok, err_line) => {
4511 return Err(
4512 self.syntax_err(format!("Expected method name, got {:?}", tok), err_line)
4513 )
4514 }
4515 };
4516
4517 let params = if self.eat(&Token::LParen) {
4519 let p = self.parse_sub_signature_param_list()?;
4520 self.expect(&Token::RParen)?;
4521 p
4522 } else {
4523 Vec::new()
4524 };
4525
4526 let body = if matches!(self.peek(), Token::LBrace) {
4528 Some(self.parse_block()?)
4529 } else {
4530 None
4531 };
4532
4533 methods.push(ClassMethod {
4534 name: method_name,
4535 params,
4536 body,
4537 visibility,
4538 is_static: false,
4539 is_final: false,
4540 });
4541
4542 self.eat(&Token::Comma);
4543 self.eat(&Token::Semicolon);
4544 }
4545
4546 self.expect(&Token::RBrace)?;
4547 self.eat(&Token::Semicolon);
4548
4549 Ok(Statement {
4550 label: None,
4551 kind: StmtKind::TraitDecl {
4552 def: TraitDef { name, methods },
4553 },
4554 line,
4555 })
4556 }
4557
4558 fn local_simple_target_to_var_decl(target: &Expr) -> Option<VarDecl> {
4559 match &target.kind {
4560 ExprKind::ScalarVar(name) => Some(VarDecl {
4561 sigil: Sigil::Scalar,
4562 name: name.clone(),
4563 initializer: None,
4564 frozen: false,
4565 type_annotation: None,
4566 }),
4567 ExprKind::ArrayVar(name) => Some(VarDecl {
4568 sigil: Sigil::Array,
4569 name: name.clone(),
4570 initializer: None,
4571 frozen: false,
4572 type_annotation: None,
4573 }),
4574 ExprKind::HashVar(name) => Some(VarDecl {
4575 sigil: Sigil::Hash,
4576 name: name.clone(),
4577 initializer: None,
4578 frozen: false,
4579 type_annotation: None,
4580 }),
4581 ExprKind::Typeglob(name) => Some(VarDecl {
4582 sigil: Sigil::Typeglob,
4583 name: name.clone(),
4584 initializer: None,
4585 frozen: false,
4586 type_annotation: None,
4587 }),
4588 _ => None,
4589 }
4590 }
4591
4592 fn parse_decl_array_destructure(
4593 &mut self,
4594 keyword: &str,
4595 line: usize,
4596 ) -> PerlResult<Statement> {
4597 self.expect(&Token::LBracket)?;
4598 let elems = self.parse_match_array_elems_until_rbracket()?;
4599 self.expect(&Token::Assign)?;
4600 self.suppress_scalar_hash_brace += 1;
4601 let rhs = self.parse_expression()?;
4602 self.suppress_scalar_hash_brace -= 1;
4603 let stmt = self.desugar_array_destructure(keyword, line, elems, rhs)?;
4604 self.parse_stmt_postfix_modifier(stmt)
4605 }
4606
4607 fn parse_decl_hash_destructure(&mut self, keyword: &str, line: usize) -> PerlResult<Statement> {
4608 let MatchPattern::Hash(pairs) = self.parse_match_hash_pattern()? else {
4609 unreachable!("parse_match_hash_pattern returns Hash");
4610 };
4611 self.expect(&Token::Assign)?;
4612 self.suppress_scalar_hash_brace += 1;
4613 let rhs = self.parse_expression()?;
4614 self.suppress_scalar_hash_brace -= 1;
4615 let stmt = self.desugar_hash_destructure(keyword, line, pairs, rhs)?;
4616 self.parse_stmt_postfix_modifier(stmt)
4617 }
4618
4619 fn desugar_array_destructure(
4620 &mut self,
4621 keyword: &str,
4622 line: usize,
4623 elems: Vec<MatchArrayElem>,
4624 rhs: Expr,
4625 ) -> PerlResult<Statement> {
4626 let tmp = format!("__stryke_ds_{}", self.alloc_desugar_tmp());
4627 let mut stmts: Vec<Statement> = Vec::new();
4628 stmts.push(destructure_stmt_from_var_decls(
4629 keyword,
4630 vec![VarDecl {
4631 sigil: Sigil::Scalar,
4632 name: tmp.clone(),
4633 initializer: Some(rhs),
4634 frozen: false,
4635 type_annotation: None,
4636 }],
4637 line,
4638 ));
4639
4640 let has_rest = elems
4641 .iter()
4642 .any(|e| matches!(e, MatchArrayElem::Rest | MatchArrayElem::RestBind(_)));
4643 let fixed_slots = elems
4644 .iter()
4645 .filter(|e| {
4646 matches!(
4647 e,
4648 MatchArrayElem::CaptureScalar(_) | MatchArrayElem::Expr(_)
4649 )
4650 })
4651 .count();
4652 if !has_rest {
4653 let cond = Expr {
4654 kind: ExprKind::BinOp {
4655 left: Box::new(destructure_expr_array_len(&tmp, line)),
4656 op: BinOp::NumEq,
4657 right: Box::new(Expr {
4658 kind: ExprKind::Integer(fixed_slots as i64),
4659 line,
4660 }),
4661 },
4662 line,
4663 };
4664 stmts.push(destructure_stmt_unless_die(
4665 line,
4666 cond,
4667 "array destructure: length mismatch",
4668 ));
4669 }
4670
4671 let mut idx: i64 = 0;
4672 for elem in elems {
4673 match elem {
4674 MatchArrayElem::Rest => break,
4675 MatchArrayElem::RestBind(name) => {
4676 let list_source = Expr {
4677 kind: ExprKind::Deref {
4678 expr: Box::new(destructure_expr_scalar_tmp(&tmp, line)),
4679 kind: Sigil::Array,
4680 },
4681 line,
4682 };
4683 let last_ix = Expr {
4684 kind: ExprKind::BinOp {
4685 left: Box::new(destructure_expr_array_len(&tmp, line)),
4686 op: BinOp::Sub,
4687 right: Box::new(Expr {
4688 kind: ExprKind::Integer(1),
4689 line,
4690 }),
4691 },
4692 line,
4693 };
4694 let range = Expr {
4695 kind: ExprKind::Range {
4696 from: Box::new(Expr {
4697 kind: ExprKind::Integer(idx),
4698 line,
4699 }),
4700 to: Box::new(last_ix),
4701 exclusive: false,
4702 step: None,
4703 },
4704 line,
4705 };
4706 let slice = Expr {
4707 kind: ExprKind::AnonymousListSlice {
4708 source: Box::new(list_source),
4709 indices: vec![range],
4710 },
4711 line,
4712 };
4713 stmts.push(destructure_stmt_from_var_decls(
4714 keyword,
4715 vec![VarDecl {
4716 sigil: Sigil::Array,
4717 name,
4718 initializer: Some(slice),
4719 frozen: false,
4720 type_annotation: None,
4721 }],
4722 line,
4723 ));
4724 break;
4725 }
4726 MatchArrayElem::CaptureScalar(name) => {
4727 let arrow = Expr {
4728 kind: ExprKind::ArrowDeref {
4729 expr: Box::new(destructure_expr_scalar_tmp(&tmp, line)),
4730 index: Box::new(Expr {
4731 kind: ExprKind::Integer(idx),
4732 line,
4733 }),
4734 kind: DerefKind::Array,
4735 },
4736 line,
4737 };
4738 stmts.push(destructure_stmt_from_var_decls(
4739 keyword,
4740 vec![VarDecl {
4741 sigil: Sigil::Scalar,
4742 name,
4743 initializer: Some(arrow),
4744 frozen: false,
4745 type_annotation: None,
4746 }],
4747 line,
4748 ));
4749 idx += 1;
4750 }
4751 MatchArrayElem::Expr(e) => {
4752 let elem_subj = Expr {
4753 kind: ExprKind::ArrowDeref {
4754 expr: Box::new(destructure_expr_scalar_tmp(&tmp, line)),
4755 index: Box::new(Expr {
4756 kind: ExprKind::Integer(idx),
4757 line,
4758 }),
4759 kind: DerefKind::Array,
4760 },
4761 line,
4762 };
4763 let match_expr = Expr {
4764 kind: ExprKind::AlgebraicMatch {
4765 subject: Box::new(elem_subj),
4766 arms: vec![
4767 MatchArm {
4768 pattern: MatchPattern::Value(Box::new(e.clone())),
4769 guard: None,
4770 body: Expr {
4771 kind: ExprKind::Integer(0),
4772 line,
4773 },
4774 },
4775 MatchArm {
4776 pattern: MatchPattern::Any,
4777 guard: None,
4778 body: Expr {
4779 kind: ExprKind::Die(vec![Expr {
4780 kind: ExprKind::String(
4781 "array destructure: element pattern mismatch"
4782 .to_string(),
4783 ),
4784 line,
4785 }]),
4786 line,
4787 },
4788 },
4789 ],
4790 },
4791 line,
4792 };
4793 stmts.push(Statement {
4794 label: None,
4795 kind: StmtKind::Expression(match_expr),
4796 line,
4797 });
4798 idx += 1;
4799 }
4800 }
4801 }
4802
4803 Ok(Statement {
4804 label: None,
4805 kind: StmtKind::StmtGroup(stmts),
4806 line,
4807 })
4808 }
4809
4810 fn desugar_hash_destructure(
4811 &mut self,
4812 keyword: &str,
4813 line: usize,
4814 pairs: Vec<MatchHashPair>,
4815 rhs: Expr,
4816 ) -> PerlResult<Statement> {
4817 let tmp = format!("__stryke_ds_{}", self.alloc_desugar_tmp());
4818 let mut stmts: Vec<Statement> = Vec::new();
4819 stmts.push(destructure_stmt_from_var_decls(
4820 keyword,
4821 vec![VarDecl {
4822 sigil: Sigil::Scalar,
4823 name: tmp.clone(),
4824 initializer: Some(rhs),
4825 frozen: false,
4826 type_annotation: None,
4827 }],
4828 line,
4829 ));
4830
4831 for pair in pairs {
4832 match pair {
4833 MatchHashPair::KeyOnly { key } => {
4834 let exists_op = Expr {
4835 kind: ExprKind::Exists(Box::new(Expr {
4836 kind: ExprKind::ArrowDeref {
4837 expr: Box::new(destructure_expr_scalar_tmp(&tmp, line)),
4838 index: Box::new(key),
4839 kind: DerefKind::Hash,
4840 },
4841 line,
4842 })),
4843 line,
4844 };
4845 stmts.push(destructure_stmt_unless_die(
4846 line,
4847 exists_op,
4848 "hash destructure: missing required key",
4849 ));
4850 }
4851 MatchHashPair::Capture { key, name } => {
4852 let init = Expr {
4853 kind: ExprKind::ArrowDeref {
4854 expr: Box::new(destructure_expr_scalar_tmp(&tmp, line)),
4855 index: Box::new(key),
4856 kind: DerefKind::Hash,
4857 },
4858 line,
4859 };
4860 stmts.push(destructure_stmt_from_var_decls(
4861 keyword,
4862 vec![VarDecl {
4863 sigil: Sigil::Scalar,
4864 name,
4865 initializer: Some(init),
4866 frozen: false,
4867 type_annotation: None,
4868 }],
4869 line,
4870 ));
4871 }
4872 }
4873 }
4874
4875 Ok(Statement {
4876 label: None,
4877 kind: StmtKind::StmtGroup(stmts),
4878 line,
4879 })
4880 }
4881
4882 fn parse_my_our_local(
4883 &mut self,
4884 keyword: &str,
4885 allow_type_annotation: bool,
4886 ) -> PerlResult<Statement> {
4887 let line = self.peek_line();
4888 self.advance(); if keyword == "local"
4891 && !matches!(self.peek(), Token::LParen | Token::LBracket | Token::LBrace)
4892 {
4893 let target = self.parse_postfix()?;
4894 let mut initializer: Option<Expr> = None;
4895 if self.eat(&Token::Assign) {
4896 initializer = Some(self.parse_expression()?);
4897 } else if matches!(
4898 self.peek(),
4899 Token::OrAssign | Token::DefinedOrAssign | Token::AndAssign
4900 ) {
4901 if matches!(&target.kind, ExprKind::Typeglob(_)) {
4902 return Err(self.syntax_err(
4903 "compound assignment on typeglob declaration is not supported",
4904 self.peek_line(),
4905 ));
4906 }
4907 let op = match self.peek().clone() {
4908 Token::OrAssign => BinOp::LogOr,
4909 Token::DefinedOrAssign => BinOp::DefinedOr,
4910 Token::AndAssign => BinOp::LogAnd,
4911 _ => unreachable!(),
4912 };
4913 self.advance();
4914 let rhs = self.parse_assign_expr()?;
4915 let tgt_line = target.line;
4916 initializer = Some(Expr {
4917 kind: ExprKind::CompoundAssign {
4918 target: Box::new(target.clone()),
4919 op,
4920 value: Box::new(rhs),
4921 },
4922 line: tgt_line,
4923 });
4924 }
4925
4926 let kind = if let Some(mut decl) = Self::local_simple_target_to_var_decl(&target) {
4927 decl.initializer = initializer;
4928 StmtKind::Local(vec![decl])
4929 } else {
4930 StmtKind::LocalExpr {
4931 target,
4932 initializer,
4933 }
4934 };
4935 let stmt = Statement {
4936 label: None,
4937 kind,
4938 line,
4939 };
4940 return self.parse_stmt_postfix_modifier(stmt);
4941 }
4942
4943 if matches!(self.peek(), Token::LBracket) {
4944 return self.parse_decl_array_destructure(keyword, line);
4945 }
4946 if matches!(self.peek(), Token::LBrace) {
4947 return self.parse_decl_hash_destructure(keyword, line);
4948 }
4949
4950 let mut decls = Vec::new();
4951
4952 if self.eat(&Token::LParen) {
4953 while !matches!(self.peek(), Token::RParen | Token::Eof) {
4955 let decl = self.parse_var_decl(allow_type_annotation)?;
4956 decls.push(decl);
4957 if !self.eat(&Token::Comma) {
4958 break;
4959 }
4960 }
4961 self.expect(&Token::RParen)?;
4962 } else {
4963 decls.push(self.parse_var_decl(allow_type_annotation)?);
4964 }
4965
4966 if self.eat(&Token::Assign) {
4968 if keyword == "our" && decls.len() == 1 {
4969 while matches!(self.peek(), Token::Ident(ref i) if i == "our") {
4970 self.advance();
4971 decls.push(self.parse_var_decl(allow_type_annotation)?);
4972 if !self.eat(&Token::Assign) {
4973 return Err(self.syntax_err(
4974 "expected `=` after `our` in chained our-declaration",
4975 self.peek_line(),
4976 ));
4977 }
4978 }
4979 }
4980 let val = self.parse_expression()?;
4981 if !crate::compat_mode() && decls.len() == 1 {
4984 let decl = &decls[0];
4985 let target_kind = match decl.sigil {
4986 Sigil::Scalar => ExprKind::ScalarVar(decl.name.clone()),
4987 Sigil::Array => ExprKind::ArrayVar(decl.name.clone()),
4988 Sigil::Hash => ExprKind::HashVar(decl.name.clone()),
4989 Sigil::Typeglob => {
4990 if decls.len() == 1 {
4992 decls[0].initializer = Some(val);
4993 } else {
4994 for d in &mut decls {
4995 d.initializer = Some(val.clone());
4996 }
4997 }
4998 return Ok(Statement {
4999 label: None,
5000 kind: match keyword {
5001 "my" => StmtKind::My(decls),
5002 "mysync" => StmtKind::MySync(decls),
5003 "our" => StmtKind::Our(decls),
5004 "local" => StmtKind::Local(decls),
5005 "state" => StmtKind::State(decls),
5006 _ => unreachable!(),
5007 },
5008 line,
5009 });
5010 }
5011 };
5012 let target = Expr {
5013 kind: target_kind,
5014 line,
5015 };
5016 self.validate_assignment(&target, &val, line)?;
5017 }
5018 if decls.len() == 1 {
5019 decls[0].initializer = Some(val);
5020 } else {
5021 for decl in &mut decls {
5022 decl.initializer = Some(val.clone());
5023 }
5024 }
5025 } else if decls.len() == 1 {
5026 let op = match self.peek().clone() {
5028 Token::OrAssign => Some(BinOp::LogOr),
5029 Token::DefinedOrAssign => Some(BinOp::DefinedOr),
5030 Token::AndAssign => Some(BinOp::LogAnd),
5031 _ => None,
5032 };
5033 if let Some(op) = op {
5034 let d = &decls[0];
5035 if matches!(d.sigil, Sigil::Typeglob) {
5036 return Err(self.syntax_err(
5037 "compound assignment on typeglob declaration is not supported",
5038 self.peek_line(),
5039 ));
5040 }
5041 self.advance();
5042 let rhs = self.parse_assign_expr()?;
5043 let target = Expr {
5044 kind: match d.sigil {
5045 Sigil::Scalar => ExprKind::ScalarVar(d.name.clone()),
5046 Sigil::Array => ExprKind::ArrayVar(d.name.clone()),
5047 Sigil::Hash => ExprKind::HashVar(d.name.clone()),
5048 Sigil::Typeglob => unreachable!(),
5049 },
5050 line,
5051 };
5052 decls[0].initializer = Some(Expr {
5053 kind: ExprKind::CompoundAssign {
5054 target: Box::new(target),
5055 op,
5056 value: Box::new(rhs),
5057 },
5058 line,
5059 });
5060 }
5061 }
5062
5063 let kind = match keyword {
5064 "my" => StmtKind::My(decls),
5065 "mysync" => StmtKind::MySync(decls),
5066 "our" => StmtKind::Our(decls),
5067 "local" => StmtKind::Local(decls),
5068 "state" => StmtKind::State(decls),
5069 _ => unreachable!(),
5070 };
5071 let stmt = Statement {
5072 label: None,
5073 kind,
5074 line,
5075 };
5076 self.parse_stmt_postfix_modifier(stmt)
5078 }
5079
5080 fn parse_var_decl(&mut self, allow_type_annotation: bool) -> PerlResult<VarDecl> {
5081 let mut decl = match self.advance() {
5082 (Token::ScalarVar(name), _) => VarDecl {
5083 sigil: Sigil::Scalar,
5084 name,
5085 initializer: None,
5086 frozen: false,
5087 type_annotation: None,
5088 },
5089 (Token::ArrayVar(name), _) => VarDecl {
5090 sigil: Sigil::Array,
5091 name,
5092 initializer: None,
5093 frozen: false,
5094 type_annotation: None,
5095 },
5096 (Token::HashVar(name), line) => {
5097 if !crate::compat_mode() {
5098 self.check_hash_shadows_reserved(&name, line)?;
5099 }
5100 VarDecl {
5101 sigil: Sigil::Hash,
5102 name,
5103 initializer: None,
5104 frozen: false,
5105 type_annotation: None,
5106 }
5107 }
5108 (Token::Star, _line) => {
5109 let name = match self.advance() {
5110 (Token::Ident(n), _) => n,
5111 (tok, l) => {
5112 return Err(self
5113 .syntax_err(format!("Expected identifier after *, got {:?}", tok), l));
5114 }
5115 };
5116 VarDecl {
5117 sigil: Sigil::Typeglob,
5118 name,
5119 initializer: None,
5120 frozen: false,
5121 type_annotation: None,
5122 }
5123 }
5124 (Token::Ident(ref kw), _) if kw == "undef" => VarDecl {
5129 sigil: Sigil::Scalar,
5130 name: format!("__undef_sink_{}", self.pos),
5134 initializer: None,
5135 frozen: false,
5136 type_annotation: None,
5137 },
5138 (tok, line) => {
5139 return Err(self.syntax_err(
5140 format!("Expected variable in declaration, got {:?}", tok),
5141 line,
5142 ));
5143 }
5144 };
5145 if allow_type_annotation && self.eat(&Token::Colon) {
5146 let ty = self.parse_type_name()?;
5147 if decl.sigil != Sigil::Scalar {
5148 return Err(self.syntax_err(
5149 "`: Type` is only valid for scalar declarations (typed my $name : Int)",
5150 self.peek_line(),
5151 ));
5152 }
5153 decl.type_annotation = Some(ty);
5154 }
5155 Ok(decl)
5156 }
5157
5158 fn parse_type_name(&mut self) -> PerlResult<PerlTypeName> {
5159 match self.advance() {
5160 (Token::Ident(name), _) => match name.as_str() {
5161 "Int" => Ok(PerlTypeName::Int),
5162 "Str" => Ok(PerlTypeName::Str),
5163 "Float" => Ok(PerlTypeName::Float),
5164 "Bool" => Ok(PerlTypeName::Bool),
5165 "Array" => Ok(PerlTypeName::Array),
5166 "Hash" => Ok(PerlTypeName::Hash),
5167 "Ref" => Ok(PerlTypeName::Ref),
5168 "Any" => Ok(PerlTypeName::Any),
5169 _ => Ok(PerlTypeName::Struct(name)),
5170 },
5171 (tok, err_line) => Err(self.syntax_err(
5172 format!("Expected type name after `:`, got {:?}", tok),
5173 err_line,
5174 )),
5175 }
5176 }
5177
5178 fn parse_package(&mut self) -> PerlResult<Statement> {
5179 let line = self.peek_line();
5180 self.advance(); let name = match self.advance() {
5182 (Token::Ident(n), _) => n,
5183 (tok, line) => {
5184 return Err(self.syntax_err(format!("Expected package name, got {:?}", tok), line))
5185 }
5186 };
5187 let mut full_name = name;
5189 while self.eat(&Token::PackageSep) {
5190 if let (Token::Ident(part), _) = self.advance() {
5191 full_name = format!("{}::{}", full_name, part);
5192 }
5193 }
5194 self.eat(&Token::Semicolon);
5195 Ok(Statement {
5196 label: None,
5197 kind: StmtKind::Package { name: full_name },
5198 line,
5199 })
5200 }
5201
5202 fn parse_use(&mut self) -> PerlResult<Statement> {
5203 let line = self.peek_line();
5204 self.advance(); let (tok, tok_line) = self.advance();
5206 match tok {
5207 Token::Float(v) => {
5208 self.eat(&Token::Semicolon);
5209 Ok(Statement {
5210 label: None,
5211 kind: StmtKind::UsePerlVersion { version: v },
5212 line,
5213 })
5214 }
5215 Token::Integer(n) => {
5216 if matches!(self.peek(), Token::Semicolon | Token::Eof) {
5217 self.eat(&Token::Semicolon);
5218 Ok(Statement {
5219 label: None,
5220 kind: StmtKind::UsePerlVersion { version: n as f64 },
5221 line,
5222 })
5223 } else {
5224 Err(self.syntax_err(
5225 format!("Expected ';' after use VERSION (got {:?})", self.peek()),
5226 line,
5227 ))
5228 }
5229 }
5230 Token::Ident(n) => {
5231 let mut full_name = n;
5232 while self.eat(&Token::PackageSep) {
5233 if let (Token::Ident(part), _) = self.advance() {
5234 full_name = format!("{}::{}", full_name, part);
5235 }
5236 }
5237 if full_name == "overload" {
5238 let mut pairs = Vec::new();
5239 let mut parse_overload_pairs = |this: &mut Self| -> PerlResult<()> {
5240 loop {
5241 if matches!(this.peek(), Token::RParen | Token::Semicolon | Token::Eof)
5242 {
5243 break;
5244 }
5245 let key_e = this.parse_assign_expr()?;
5246 this.expect(&Token::FatArrow)?;
5247 let val_e = this.parse_assign_expr()?;
5248 let key = this.expr_to_overload_key(&key_e)?;
5249 let val = this.expr_to_overload_sub(&val_e)?;
5250 pairs.push((key, val));
5251 if !this.eat(&Token::Comma) {
5252 break;
5253 }
5254 }
5255 Ok(())
5256 };
5257 if self.eat(&Token::LParen) {
5258 parse_overload_pairs(self)?;
5260 self.expect(&Token::RParen)?;
5261 } else if !matches!(self.peek(), Token::Semicolon | Token::Eof) {
5262 parse_overload_pairs(self)?;
5263 }
5264 self.eat(&Token::Semicolon);
5265 return Ok(Statement {
5266 label: None,
5267 kind: StmtKind::UseOverload { pairs },
5268 line,
5269 });
5270 }
5271 let mut imports = Vec::new();
5272 if !matches!(self.peek(), Token::Semicolon | Token::Eof)
5273 && !self.next_is_new_statement_start(tok_line)
5274 {
5275 loop {
5276 if matches!(self.peek(), Token::Semicolon | Token::Eof) {
5277 break;
5278 }
5279 imports.push(self.parse_expression()?);
5280 if !self.eat(&Token::Comma) {
5281 break;
5282 }
5283 }
5284 }
5285 self.eat(&Token::Semicolon);
5286 Ok(Statement {
5287 label: None,
5288 kind: StmtKind::Use {
5289 module: full_name,
5290 imports,
5291 },
5292 line,
5293 })
5294 }
5295 other => Err(self.syntax_err(
5296 format!("Expected module name or version after use, got {:?}", other),
5297 tok_line,
5298 )),
5299 }
5300 }
5301
5302 fn parse_no(&mut self) -> PerlResult<Statement> {
5303 let line = self.peek_line();
5304 self.advance(); let module = match self.advance() {
5306 (Token::Ident(n), tok_line) => (n, tok_line),
5307 (tok, line) => {
5308 return Err(self.syntax_err(
5309 format!("Expected module name after no, got {:?}", tok),
5310 line,
5311 ))
5312 }
5313 };
5314 let (module_name, tok_line) = module;
5315 let mut full_name = module_name;
5316 while self.eat(&Token::PackageSep) {
5317 if let (Token::Ident(part), _) = self.advance() {
5318 full_name = format!("{}::{}", full_name, part);
5319 }
5320 }
5321 let mut imports = Vec::new();
5322 if !matches!(self.peek(), Token::Semicolon | Token::Eof)
5323 && !self.next_is_new_statement_start(tok_line)
5324 {
5325 loop {
5326 if matches!(self.peek(), Token::Semicolon | Token::Eof) {
5327 break;
5328 }
5329 imports.push(self.parse_expression()?);
5330 if !self.eat(&Token::Comma) {
5331 break;
5332 }
5333 }
5334 }
5335 self.eat(&Token::Semicolon);
5336 Ok(Statement {
5337 label: None,
5338 kind: StmtKind::No {
5339 module: full_name,
5340 imports,
5341 },
5342 line,
5343 })
5344 }
5345
5346 fn parse_return(&mut self) -> PerlResult<Statement> {
5347 let line = self.peek_line();
5348 self.advance(); let val = if matches!(self.peek(), Token::Semicolon | Token::RBrace | Token::Eof) {
5350 None
5351 } else {
5352 Some(self.parse_assign_expr()?)
5354 };
5355 let stmt = Statement {
5357 label: None,
5358 kind: StmtKind::Return(val),
5359 line,
5360 };
5361 if let Token::Ident(ref kw) = self.peek().clone() {
5362 match kw.as_str() {
5363 "if" => {
5364 self.advance();
5365 let cond = self.parse_expression()?;
5366 self.eat(&Token::Semicolon);
5367 return Ok(Statement {
5368 label: None,
5369 kind: StmtKind::If {
5370 condition: cond,
5371 body: vec![stmt],
5372 elsifs: vec![],
5373 else_block: None,
5374 },
5375 line,
5376 });
5377 }
5378 "unless" => {
5379 self.advance();
5380 let cond = self.parse_expression()?;
5381 self.eat(&Token::Semicolon);
5382 return Ok(Statement {
5383 label: None,
5384 kind: StmtKind::Unless {
5385 condition: cond,
5386 body: vec![stmt],
5387 else_block: None,
5388 },
5389 line,
5390 });
5391 }
5392 _ => {}
5393 }
5394 }
5395 self.eat(&Token::Semicolon);
5396 Ok(stmt)
5397 }
5398
5399 fn parse_expression(&mut self) -> PerlResult<Expr> {
5402 self.parse_comma_expr()
5403 }
5404
5405 fn parse_comma_expr(&mut self) -> PerlResult<Expr> {
5406 let expr = self.parse_assign_expr()?;
5407 let mut exprs = vec![expr];
5408 while self.eat(&Token::Comma) || self.eat(&Token::FatArrow) {
5409 if matches!(
5410 self.peek(),
5411 Token::RParen | Token::RBracket | Token::RBrace | Token::Semicolon | Token::Eof
5412 ) {
5413 break; }
5415 exprs.push(self.parse_assign_expr()?);
5416 }
5417 if exprs.len() == 1 {
5418 return Ok(exprs.pop().unwrap());
5419 }
5420 let line = exprs[0].line;
5421 Ok(Expr {
5422 kind: ExprKind::List(exprs),
5423 line,
5424 })
5425 }
5426
5427 fn parse_assign_expr(&mut self) -> PerlResult<Expr> {
5428 let expr = self.parse_ternary()?;
5429 let line = expr.line;
5430
5431 match self.peek().clone() {
5432 Token::Assign => {
5433 self.advance();
5434 let right = self.parse_assign_expr()?;
5435 if let ExprKind::MethodCall { ref args, .. } = expr.kind {
5437 if args.is_empty() {
5438 let ExprKind::MethodCall {
5440 object,
5441 method,
5442 super_call,
5443 ..
5444 } = expr.kind
5445 else {
5446 unreachable!()
5447 };
5448 return Ok(Expr {
5449 kind: ExprKind::MethodCall {
5450 object,
5451 method,
5452 args: vec![right],
5453 super_call,
5454 },
5455 line,
5456 });
5457 }
5458 }
5459 self.validate_assignment(&expr, &right, line)?;
5460 Ok(Expr {
5461 kind: ExprKind::Assign {
5462 target: Box::new(expr),
5463 value: Box::new(right),
5464 },
5465 line,
5466 })
5467 }
5468 Token::PlusAssign => {
5469 self.advance();
5470 let r = self.parse_assign_expr()?;
5471 Ok(Expr {
5472 kind: ExprKind::CompoundAssign {
5473 target: Box::new(expr),
5474 op: BinOp::Add,
5475 value: Box::new(r),
5476 },
5477 line,
5478 })
5479 }
5480 Token::MinusAssign => {
5481 self.advance();
5482 let r = self.parse_assign_expr()?;
5483 Ok(Expr {
5484 kind: ExprKind::CompoundAssign {
5485 target: Box::new(expr),
5486 op: BinOp::Sub,
5487 value: Box::new(r),
5488 },
5489 line,
5490 })
5491 }
5492 Token::MulAssign => {
5493 self.advance();
5494 let r = self.parse_assign_expr()?;
5495 Ok(Expr {
5496 kind: ExprKind::CompoundAssign {
5497 target: Box::new(expr),
5498 op: BinOp::Mul,
5499 value: Box::new(r),
5500 },
5501 line,
5502 })
5503 }
5504 Token::DivAssign => {
5505 self.advance();
5506 let r = self.parse_assign_expr()?;
5507 Ok(Expr {
5508 kind: ExprKind::CompoundAssign {
5509 target: Box::new(expr),
5510 op: BinOp::Div,
5511 value: Box::new(r),
5512 },
5513 line,
5514 })
5515 }
5516 Token::ModAssign => {
5517 self.advance();
5518 let r = self.parse_assign_expr()?;
5519 Ok(Expr {
5520 kind: ExprKind::CompoundAssign {
5521 target: Box::new(expr),
5522 op: BinOp::Mod,
5523 value: Box::new(r),
5524 },
5525 line,
5526 })
5527 }
5528 Token::PowAssign => {
5529 self.advance();
5530 let r = self.parse_assign_expr()?;
5531 Ok(Expr {
5532 kind: ExprKind::CompoundAssign {
5533 target: Box::new(expr),
5534 op: BinOp::Pow,
5535 value: Box::new(r),
5536 },
5537 line,
5538 })
5539 }
5540 Token::DotAssign => {
5541 self.advance();
5542 let r = self.parse_assign_expr()?;
5543 Ok(Expr {
5544 kind: ExprKind::CompoundAssign {
5545 target: Box::new(expr),
5546 op: BinOp::Concat,
5547 value: Box::new(r),
5548 },
5549 line,
5550 })
5551 }
5552 Token::BitAndAssign => {
5553 self.advance();
5554 let r = self.parse_assign_expr()?;
5555 Ok(Expr {
5556 kind: ExprKind::CompoundAssign {
5557 target: Box::new(expr),
5558 op: BinOp::BitAnd,
5559 value: Box::new(r),
5560 },
5561 line,
5562 })
5563 }
5564 Token::BitOrAssign => {
5565 self.advance();
5566 let r = self.parse_assign_expr()?;
5567 Ok(Expr {
5568 kind: ExprKind::CompoundAssign {
5569 target: Box::new(expr),
5570 op: BinOp::BitOr,
5571 value: Box::new(r),
5572 },
5573 line,
5574 })
5575 }
5576 Token::XorAssign => {
5577 self.advance();
5578 let r = self.parse_assign_expr()?;
5579 Ok(Expr {
5580 kind: ExprKind::CompoundAssign {
5581 target: Box::new(expr),
5582 op: BinOp::BitXor,
5583 value: Box::new(r),
5584 },
5585 line,
5586 })
5587 }
5588 Token::ShiftLeftAssign => {
5589 self.advance();
5590 let r = self.parse_assign_expr()?;
5591 Ok(Expr {
5592 kind: ExprKind::CompoundAssign {
5593 target: Box::new(expr),
5594 op: BinOp::ShiftLeft,
5595 value: Box::new(r),
5596 },
5597 line,
5598 })
5599 }
5600 Token::ShiftRightAssign => {
5601 self.advance();
5602 let r = self.parse_assign_expr()?;
5603 Ok(Expr {
5604 kind: ExprKind::CompoundAssign {
5605 target: Box::new(expr),
5606 op: BinOp::ShiftRight,
5607 value: Box::new(r),
5608 },
5609 line,
5610 })
5611 }
5612 Token::OrAssign => {
5613 self.advance();
5614 let r = self.parse_assign_expr()?;
5615 Ok(Expr {
5616 kind: ExprKind::CompoundAssign {
5617 target: Box::new(expr),
5618 op: BinOp::LogOr,
5619 value: Box::new(r),
5620 },
5621 line,
5622 })
5623 }
5624 Token::DefinedOrAssign => {
5625 self.advance();
5626 let r = self.parse_assign_expr()?;
5627 Ok(Expr {
5628 kind: ExprKind::CompoundAssign {
5629 target: Box::new(expr),
5630 op: BinOp::DefinedOr,
5631 value: Box::new(r),
5632 },
5633 line,
5634 })
5635 }
5636 Token::AndAssign => {
5637 self.advance();
5638 let r = self.parse_assign_expr()?;
5639 Ok(Expr {
5640 kind: ExprKind::CompoundAssign {
5641 target: Box::new(expr),
5642 op: BinOp::LogAnd,
5643 value: Box::new(r),
5644 },
5645 line,
5646 })
5647 }
5648 _ => Ok(expr),
5649 }
5650 }
5651
5652 fn parse_ternary(&mut self) -> PerlResult<Expr> {
5653 let expr = self.parse_pipe_forward()?;
5654 if self.eat(&Token::Question) {
5655 let line = expr.line;
5656 self.suppress_colon_range = self.suppress_colon_range.saturating_add(1);
5657 let then_expr = self.parse_assign_expr();
5658 self.suppress_colon_range = self.suppress_colon_range.saturating_sub(1);
5659 let then_expr = then_expr?;
5660 self.expect(&Token::Colon)?;
5661 let else_expr = self.parse_assign_expr()?;
5662 return Ok(Expr {
5663 kind: ExprKind::Ternary {
5664 condition: Box::new(expr),
5665 then_expr: Box::new(then_expr),
5666 else_expr: Box::new(else_expr),
5667 },
5668 line,
5669 });
5670 }
5671 Ok(expr)
5672 }
5673
5674 fn parse_pipe_forward(&mut self) -> PerlResult<Expr> {
5680 let mut left = self.parse_or_word()?;
5681 if self.no_pipe_forward_depth > 0 {
5687 return Ok(left);
5688 }
5689 while matches!(self.peek(), Token::PipeForward) {
5690 if crate::compat_mode() {
5691 return Err(self.syntax_err(
5692 "pipe-forward operator `|>` is a stryke extension (disabled by --compat)",
5693 left.line,
5694 ));
5695 }
5696 let line = left.line;
5697 self.advance();
5698 self.pipe_rhs_depth = self.pipe_rhs_depth.saturating_add(1);
5701 let right_result = self.parse_or_word();
5702 self.pipe_rhs_depth = self.pipe_rhs_depth.saturating_sub(1);
5703 let right = right_result?;
5704 left = self.pipe_forward_apply(left, right, line)?;
5705 }
5706 Ok(left)
5707 }
5708
5709 fn pipe_forward_apply(&self, lhs: Expr, rhs: Expr, line: usize) -> PerlResult<Expr> {
5731 let Expr { kind, line: rline } = rhs;
5732 let new_kind = match kind {
5733 ExprKind::FuncCall { name, mut args } => {
5735 match name.as_str() {
5736 "puniq" | "uniq" | "distinct" | "flatten" | "set" | "list_count"
5737 | "list_size" | "count" | "size" | "cnt" | "len" | "with_index" | "shuffle"
5738 | "shuffled" | "frequencies" | "freq" | "interleave" | "ddump"
5739 | "stringify" | "str" | "lines" | "words" | "chars" | "digits" | "letters"
5740 | "letters_uc" | "letters_lc" | "punctuation" | "numbers" | "graphemes"
5741 | "columns" | "sentences" | "paragraphs" | "sections" | "trim" | "avg"
5742 | "to_json" | "to_csv" | "to_toml" | "to_yaml" | "to_xml" | "to_html"
5743 | "from_json" | "from_csv" | "from_toml" | "from_yaml" | "from_xml"
5744 | "to_markdown" | "to_table" | "xopen" | "clip" | "sparkline" | "bar_chart"
5745 | "flame" | "stddev" | "squared" | "sq" | "square" | "cubed" | "cb"
5746 | "cube" | "normalize" | "snake_case" | "camel_case" | "kebab_case" => {
5747 if args.is_empty() {
5748 args.push(lhs);
5749 } else {
5750 args[0] = lhs;
5751 }
5752 }
5753 "chunked" | "windowed" => {
5754 if args.is_empty() {
5755 return Err(self.syntax_err(
5756 "|>: chunked(N) / windowed(N) needs size — e.g. `@a |> windowed(2)`",
5757 line,
5758 ));
5759 }
5760 args.insert(0, lhs);
5761 }
5762 "List::Util::reduce" | "List::Util::fold" => {
5763 args.push(lhs);
5764 }
5765 "grep_v" | "pluck" | "tee" | "nth" | "chunk" => {
5766 args.push(lhs);
5772 }
5773 "enumerate" | "dedup" => {
5774 args.insert(0, lhs);
5777 }
5778 "clamp" => {
5779 args.push(lhs);
5781 }
5782 "pfirst" | "pany" | "any" | "all" | "none" | "first" | "take_while"
5783 | "drop_while" | "skip_while" | "reject" | "tap" | "peek" | "group_by"
5784 | "chunk_by" | "partition" | "min_by" | "max_by" | "zip_with" | "count_by" => {
5785 if args.len() < 2 {
5786 return Err(self.syntax_err(
5787 format!(
5788 "|>: `{name}` needs {{ BLOCK }}, LIST so the list can receive the pipe"
5789 ),
5790 line,
5791 ));
5792 }
5793 args[1] = lhs;
5794 }
5795 "take" | "head" | "tail" | "drop" | "List::Util::head" | "List::Util::tail" => {
5796 if args.is_empty() {
5797 return Err(self.syntax_err(
5798 "|>: `{name}` needs N last — e.g. `@a |> take(3)` for `take(@a, 3)`",
5799 line,
5800 ));
5801 }
5802 args.insert(0, lhs);
5804 }
5805 _ => {
5806 if self.thread_last_mode {
5807 args.push(lhs);
5808 } else {
5809 args.insert(0, lhs);
5810 }
5811 }
5812 }
5813 ExprKind::FuncCall { name, args }
5814 }
5815 ExprKind::MethodCall {
5816 object,
5817 method,
5818 mut args,
5819 super_call,
5820 } => {
5821 if self.thread_last_mode {
5822 args.push(lhs);
5823 } else {
5824 args.insert(0, lhs);
5825 }
5826 ExprKind::MethodCall {
5827 object,
5828 method,
5829 args,
5830 super_call,
5831 }
5832 }
5833 ExprKind::IndirectCall {
5834 target,
5835 mut args,
5836 ampersand,
5837 pass_caller_arglist: _,
5838 } => {
5839 if self.thread_last_mode {
5840 args.push(lhs);
5841 } else {
5842 args.insert(0, lhs);
5843 }
5844 ExprKind::IndirectCall {
5845 target,
5846 args,
5847 ampersand,
5848 pass_caller_arglist: false,
5851 }
5852 }
5853
5854 ExprKind::Print { handle, mut args } => {
5856 if self.thread_last_mode {
5857 args.push(lhs);
5858 } else {
5859 args.insert(0, lhs);
5860 }
5861 ExprKind::Print { handle, args }
5862 }
5863 ExprKind::Say { handle, mut args } => {
5864 if self.thread_last_mode {
5865 args.push(lhs);
5866 } else {
5867 args.insert(0, lhs);
5868 }
5869 ExprKind::Say { handle, args }
5870 }
5871 ExprKind::Printf { handle, mut args } => {
5872 if self.thread_last_mode {
5873 args.push(lhs);
5874 } else {
5875 args.insert(0, lhs);
5876 }
5877 ExprKind::Printf { handle, args }
5878 }
5879 ExprKind::Die(mut args) => {
5880 if self.thread_last_mode {
5881 args.push(lhs);
5882 } else {
5883 args.insert(0, lhs);
5884 }
5885 ExprKind::Die(args)
5886 }
5887 ExprKind::Warn(mut args) => {
5888 if self.thread_last_mode {
5889 args.push(lhs);
5890 } else {
5891 args.insert(0, lhs);
5892 }
5893 ExprKind::Warn(args)
5894 }
5895
5896 ExprKind::Sprintf { format, mut args } => {
5902 if self.thread_last_mode {
5903 args.push(lhs);
5904 } else {
5905 args.insert(0, lhs);
5906 }
5907 ExprKind::Sprintf { format, args }
5908 }
5909
5910 ExprKind::System(mut args) => {
5912 if self.thread_last_mode {
5913 args.push(lhs);
5914 } else {
5915 args.insert(0, lhs);
5916 }
5917 ExprKind::System(args)
5918 }
5919 ExprKind::Exec(mut args) => {
5920 if self.thread_last_mode {
5921 args.push(lhs);
5922 } else {
5923 args.insert(0, lhs);
5924 }
5925 ExprKind::Exec(args)
5926 }
5927 ExprKind::Unlink(mut args) => {
5928 if self.thread_last_mode {
5929 args.push(lhs);
5930 } else {
5931 args.insert(0, lhs);
5932 }
5933 ExprKind::Unlink(args)
5934 }
5935 ExprKind::Chmod(mut args) => {
5936 if self.thread_last_mode {
5937 args.push(lhs);
5938 } else {
5939 args.insert(0, lhs);
5940 }
5941 ExprKind::Chmod(args)
5942 }
5943 ExprKind::Chown(mut args) => {
5944 if self.thread_last_mode {
5945 args.push(lhs);
5946 } else {
5947 args.insert(0, lhs);
5948 }
5949 ExprKind::Chown(args)
5950 }
5951 ExprKind::Glob(mut args) => {
5952 if self.thread_last_mode {
5953 args.push(lhs);
5954 } else {
5955 args.insert(0, lhs);
5956 }
5957 ExprKind::Glob(args)
5958 }
5959 ExprKind::Files(mut args) => {
5960 if self.thread_last_mode {
5961 args.push(lhs);
5962 } else {
5963 args.insert(0, lhs);
5964 }
5965 ExprKind::Files(args)
5966 }
5967 ExprKind::Filesf(mut args) => {
5968 if self.thread_last_mode {
5969 args.push(lhs);
5970 } else {
5971 args.insert(0, lhs);
5972 }
5973 ExprKind::Filesf(args)
5974 }
5975 ExprKind::FilesfRecursive(mut args) => {
5976 if self.thread_last_mode {
5977 args.push(lhs);
5978 } else {
5979 args.insert(0, lhs);
5980 }
5981 ExprKind::FilesfRecursive(args)
5982 }
5983 ExprKind::Dirs(mut args) => {
5984 if self.thread_last_mode {
5985 args.push(lhs);
5986 } else {
5987 args.insert(0, lhs);
5988 }
5989 ExprKind::Dirs(args)
5990 }
5991 ExprKind::DirsRecursive(mut args) => {
5992 if self.thread_last_mode {
5993 args.push(lhs);
5994 } else {
5995 args.insert(0, lhs);
5996 }
5997 ExprKind::DirsRecursive(args)
5998 }
5999 ExprKind::SymLinks(mut args) => {
6000 if self.thread_last_mode {
6001 args.push(lhs);
6002 } else {
6003 args.insert(0, lhs);
6004 }
6005 ExprKind::SymLinks(args)
6006 }
6007 ExprKind::Sockets(mut args) => {
6008 if self.thread_last_mode {
6009 args.push(lhs);
6010 } else {
6011 args.insert(0, lhs);
6012 }
6013 ExprKind::Sockets(args)
6014 }
6015 ExprKind::Pipes(mut args) => {
6016 if self.thread_last_mode {
6017 args.push(lhs);
6018 } else {
6019 args.insert(0, lhs);
6020 }
6021 ExprKind::Pipes(args)
6022 }
6023 ExprKind::BlockDevices(mut args) => {
6024 if self.thread_last_mode {
6025 args.push(lhs);
6026 } else {
6027 args.insert(0, lhs);
6028 }
6029 ExprKind::BlockDevices(args)
6030 }
6031 ExprKind::CharDevices(mut args) => {
6032 if self.thread_last_mode {
6033 args.push(lhs);
6034 } else {
6035 args.insert(0, lhs);
6036 }
6037 ExprKind::CharDevices(args)
6038 }
6039 ExprKind::GlobPar { mut args, progress } => {
6040 if self.thread_last_mode {
6041 args.push(lhs);
6042 } else {
6043 args.insert(0, lhs);
6044 }
6045 ExprKind::GlobPar { args, progress }
6046 }
6047 ExprKind::ParSed { mut args, progress } => {
6048 if self.thread_last_mode {
6049 args.push(lhs);
6050 } else {
6051 args.insert(0, lhs);
6052 }
6053 ExprKind::ParSed { args, progress }
6054 }
6055
6056 ExprKind::Length(_) => ExprKind::Length(Box::new(lhs)),
6058 ExprKind::Abs(_) => ExprKind::Abs(Box::new(lhs)),
6059 ExprKind::Int(_) => ExprKind::Int(Box::new(lhs)),
6060 ExprKind::Sqrt(_) => ExprKind::Sqrt(Box::new(lhs)),
6061 ExprKind::Sin(_) => ExprKind::Sin(Box::new(lhs)),
6062 ExprKind::Cos(_) => ExprKind::Cos(Box::new(lhs)),
6063 ExprKind::Exp(_) => ExprKind::Exp(Box::new(lhs)),
6064 ExprKind::Log(_) => ExprKind::Log(Box::new(lhs)),
6065 ExprKind::Hex(_) => ExprKind::Hex(Box::new(lhs)),
6066 ExprKind::Oct(_) => ExprKind::Oct(Box::new(lhs)),
6067 ExprKind::Lc(_) => ExprKind::Lc(Box::new(lhs)),
6068 ExprKind::Uc(_) => ExprKind::Uc(Box::new(lhs)),
6069 ExprKind::Lcfirst(_) => ExprKind::Lcfirst(Box::new(lhs)),
6070 ExprKind::Ucfirst(_) => ExprKind::Ucfirst(Box::new(lhs)),
6071 ExprKind::Fc(_) => ExprKind::Fc(Box::new(lhs)),
6072 ExprKind::Chr(_) => ExprKind::Chr(Box::new(lhs)),
6073 ExprKind::Ord(_) => ExprKind::Ord(Box::new(lhs)),
6074 ExprKind::Chomp(_) => ExprKind::Chomp(Box::new(lhs)),
6075 ExprKind::Chop(_) => ExprKind::Chop(Box::new(lhs)),
6076 ExprKind::Defined(_) => ExprKind::Defined(Box::new(lhs)),
6077 ExprKind::Ref(_) => ExprKind::Ref(Box::new(lhs)),
6078 ExprKind::ScalarContext(_) => ExprKind::ScalarContext(Box::new(lhs)),
6079 ExprKind::Keys(_) => ExprKind::Keys(Box::new(lhs)),
6080 ExprKind::Values(_) => ExprKind::Values(Box::new(lhs)),
6081 ExprKind::Each(_) => ExprKind::Each(Box::new(lhs)),
6082 ExprKind::Pop(_) => ExprKind::Pop(Box::new(lhs)),
6083 ExprKind::Shift(_) => ExprKind::Shift(Box::new(lhs)),
6084 ExprKind::Delete(_) => ExprKind::Delete(Box::new(lhs)),
6085 ExprKind::Exists(_) => ExprKind::Exists(Box::new(lhs)),
6086 ExprKind::ReverseExpr(_) => ExprKind::ReverseExpr(Box::new(lhs)),
6087 ExprKind::Rev(_) => ExprKind::Rev(Box::new(lhs)),
6088 ExprKind::Slurp(_) => ExprKind::Slurp(Box::new(lhs)),
6089 ExprKind::Capture(_) => ExprKind::Capture(Box::new(lhs)),
6090 ExprKind::Qx(_) => ExprKind::Qx(Box::new(lhs)),
6091 ExprKind::FetchUrl(_) => ExprKind::FetchUrl(Box::new(lhs)),
6092 ExprKind::Close(_) => ExprKind::Close(Box::new(lhs)),
6093 ExprKind::Chdir(_) => ExprKind::Chdir(Box::new(lhs)),
6094 ExprKind::Readdir(_) => ExprKind::Readdir(Box::new(lhs)),
6095 ExprKind::Closedir(_) => ExprKind::Closedir(Box::new(lhs)),
6096 ExprKind::Rewinddir(_) => ExprKind::Rewinddir(Box::new(lhs)),
6097 ExprKind::Telldir(_) => ExprKind::Telldir(Box::new(lhs)),
6098 ExprKind::Stat(_) => ExprKind::Stat(Box::new(lhs)),
6099 ExprKind::Lstat(_) => ExprKind::Lstat(Box::new(lhs)),
6100 ExprKind::Readlink(_) => ExprKind::Readlink(Box::new(lhs)),
6101 ExprKind::Study(_) => ExprKind::Study(Box::new(lhs)),
6102 ExprKind::Await(_) => ExprKind::Await(Box::new(lhs)),
6103 ExprKind::Eval(_) => ExprKind::Eval(Box::new(lhs)),
6104 ExprKind::Rand(_) => ExprKind::Rand(Some(Box::new(lhs))),
6105 ExprKind::Srand(_) => ExprKind::Srand(Some(Box::new(lhs))),
6106 ExprKind::Pos(_) => ExprKind::Pos(Some(Box::new(lhs))),
6107 ExprKind::Exit(_) => ExprKind::Exit(Some(Box::new(lhs))),
6108
6109 ExprKind::MapExpr {
6111 block,
6112 list: _,
6113 flatten_array_refs,
6114 stream,
6115 } => ExprKind::MapExpr {
6116 block,
6117 list: Box::new(lhs),
6118 flatten_array_refs,
6119 stream,
6120 },
6121 ExprKind::MapExprComma {
6122 expr,
6123 list: _,
6124 flatten_array_refs,
6125 stream,
6126 } => ExprKind::MapExprComma {
6127 expr,
6128 list: Box::new(lhs),
6129 flatten_array_refs,
6130 stream,
6131 },
6132 ExprKind::GrepExpr {
6133 block,
6134 list: _,
6135 keyword,
6136 } => ExprKind::GrepExpr {
6137 block,
6138 list: Box::new(lhs),
6139 keyword,
6140 },
6141 ExprKind::GrepExprComma {
6142 expr,
6143 list: _,
6144 keyword,
6145 } => ExprKind::GrepExprComma {
6146 expr,
6147 list: Box::new(lhs),
6148 keyword,
6149 },
6150 ExprKind::ForEachExpr { block, list: _ } => ExprKind::ForEachExpr {
6151 block,
6152 list: Box::new(lhs),
6153 },
6154 ExprKind::SortExpr { cmp, list: _ } => ExprKind::SortExpr {
6155 cmp,
6156 list: Box::new(lhs),
6157 },
6158 ExprKind::JoinExpr { separator, list: _ } => ExprKind::JoinExpr {
6159 separator,
6160 list: Box::new(lhs),
6161 },
6162 ExprKind::ReduceExpr { block, list: _ } => ExprKind::ReduceExpr {
6163 block,
6164 list: Box::new(lhs),
6165 },
6166 ExprKind::PMapExpr {
6167 block,
6168 list: _,
6169 progress,
6170 flat_outputs,
6171 on_cluster,
6172 stream,
6173 } => ExprKind::PMapExpr {
6174 block,
6175 list: Box::new(lhs),
6176 progress,
6177 flat_outputs,
6178 on_cluster,
6179 stream,
6180 },
6181 ExprKind::PMapChunkedExpr {
6182 chunk_size,
6183 block,
6184 list: _,
6185 progress,
6186 } => ExprKind::PMapChunkedExpr {
6187 chunk_size,
6188 block,
6189 list: Box::new(lhs),
6190 progress,
6191 },
6192 ExprKind::PGrepExpr {
6193 block,
6194 list: _,
6195 progress,
6196 stream,
6197 } => ExprKind::PGrepExpr {
6198 block,
6199 list: Box::new(lhs),
6200 progress,
6201 stream,
6202 },
6203 ExprKind::PForExpr {
6204 block,
6205 list: _,
6206 progress,
6207 } => ExprKind::PForExpr {
6208 block,
6209 list: Box::new(lhs),
6210 progress,
6211 },
6212 ExprKind::PSortExpr {
6213 cmp,
6214 list: _,
6215 progress,
6216 } => ExprKind::PSortExpr {
6217 cmp,
6218 list: Box::new(lhs),
6219 progress,
6220 },
6221 ExprKind::PReduceExpr {
6222 block,
6223 list: _,
6224 progress,
6225 } => ExprKind::PReduceExpr {
6226 block,
6227 list: Box::new(lhs),
6228 progress,
6229 },
6230 ExprKind::PcacheExpr {
6231 block,
6232 list: _,
6233 progress,
6234 } => ExprKind::PcacheExpr {
6235 block,
6236 list: Box::new(lhs),
6237 progress,
6238 },
6239 ExprKind::PReduceInitExpr {
6240 init,
6241 block,
6242 list: _,
6243 progress,
6244 } => ExprKind::PReduceInitExpr {
6245 init,
6246 block,
6247 list: Box::new(lhs),
6248 progress,
6249 },
6250 ExprKind::PMapReduceExpr {
6251 map_block,
6252 reduce_block,
6253 list: _,
6254 progress,
6255 } => ExprKind::PMapReduceExpr {
6256 map_block,
6257 reduce_block,
6258 list: Box::new(lhs),
6259 progress,
6260 },
6261
6262 ExprKind::Push { array, mut values } => {
6267 values.insert(0, lhs);
6268 ExprKind::Push { array, values }
6269 }
6270 ExprKind::Unshift { array, mut values } => {
6271 values.insert(0, lhs);
6272 ExprKind::Unshift { array, values }
6273 }
6274
6275 ExprKind::SplitExpr {
6277 pattern,
6278 string: _,
6279 limit,
6280 } => ExprKind::SplitExpr {
6281 pattern,
6282 string: Box::new(lhs),
6283 limit,
6284 },
6285
6286 ExprKind::Substitution {
6290 pattern,
6291 replacement,
6292 mut flags,
6293 expr: _,
6294 delim,
6295 } => {
6296 if !flags.contains('r') {
6297 flags.push('r');
6298 }
6299 ExprKind::Substitution {
6300 expr: Box::new(lhs),
6301 pattern,
6302 replacement,
6303 flags,
6304 delim,
6305 }
6306 }
6307 ExprKind::Transliterate {
6308 from,
6309 to,
6310 mut flags,
6311 expr: _,
6312 delim,
6313 } => {
6314 if !flags.contains('r') {
6315 flags.push('r');
6316 }
6317 ExprKind::Transliterate {
6318 expr: Box::new(lhs),
6319 from,
6320 to,
6321 flags,
6322 delim,
6323 }
6324 }
6325 ExprKind::Match {
6326 pattern,
6327 flags,
6328 scalar_g,
6329 expr: _,
6330 delim,
6331 } => ExprKind::Match {
6332 expr: Box::new(lhs),
6333 pattern,
6334 flags,
6335 scalar_g,
6336 delim,
6337 },
6338 ExprKind::Regex(pattern, flags) => ExprKind::Match {
6340 expr: Box::new(lhs),
6341 pattern,
6342 flags,
6343 scalar_g: false,
6344 delim: '/',
6345 },
6346
6347 ExprKind::Bareword(name) => match name.as_str() {
6349 "reverse" => {
6350 if !crate::compat_mode() {
6351 return Err(self
6352 .syntax_err("stryke uses `rev` instead of `reverse` (this is not Perl 5)", line));
6353 }
6354 ExprKind::ReverseExpr(Box::new(lhs))
6355 }
6356 "rv" | "reversed" | "rev" => ExprKind::Rev(Box::new(lhs)),
6357 "uq" | "uniq" | "distinct" => ExprKind::FuncCall {
6358 name: "uniq".to_string(),
6359 args: vec![lhs],
6360 },
6361 "fl" | "flatten" => ExprKind::FuncCall {
6362 name: "flatten".to_string(),
6363 args: vec![lhs],
6364 },
6365 _ => ExprKind::FuncCall {
6366 name,
6367 args: vec![lhs],
6368 },
6369 },
6370
6371 kind @ (ExprKind::ScalarVar(_)
6373 | ExprKind::ArrayElement { .. }
6374 | ExprKind::HashElement { .. }
6375 | ExprKind::Deref { .. }
6376 | ExprKind::ArrowDeref { .. }
6377 | ExprKind::CodeRef { .. }
6378 | ExprKind::SubroutineRef(_)
6379 | ExprKind::SubroutineCodeRef(_)
6380 | ExprKind::DynamicSubCodeRef(_)) => ExprKind::IndirectCall {
6381 target: Box::new(Expr { kind, line: rline }),
6382 args: vec![lhs],
6383 ampersand: false,
6384 pass_caller_arglist: false,
6385 },
6386
6387 ExprKind::Do(inner) if matches!(inner.kind, ExprKind::CodeRef { .. }) => {
6391 ExprKind::IndirectCall {
6392 target: inner,
6393 args: vec![lhs],
6394 ampersand: false,
6395 pass_caller_arglist: false,
6396 }
6397 }
6398
6399 other => {
6400 return Err(self.syntax_err(
6401 format!(
6402 "right-hand side of `|>` must be a call, builtin, or coderef \
6403 expression (got {})",
6404 Self::expr_kind_name(&other)
6405 ),
6406 line,
6407 ));
6408 }
6409 };
6410 Ok(Expr {
6411 kind: new_kind,
6412 line,
6413 })
6414 }
6415
6416 fn expr_kind_name(kind: &ExprKind) -> &'static str {
6418 match kind {
6419 ExprKind::Integer(_) | ExprKind::Float(_) => "numeric literal",
6420 ExprKind::String(_) | ExprKind::InterpolatedString(_) => "string literal",
6421 ExprKind::BinOp { .. } => "binary expression",
6422 ExprKind::UnaryOp { .. } => "unary expression",
6423 ExprKind::Ternary { .. } => "ternary expression",
6424 ExprKind::Assign { .. } | ExprKind::CompoundAssign { .. } => "assignment",
6425 ExprKind::List(_) => "list expression",
6426 ExprKind::Range { .. } => "range expression",
6427 _ => "expression",
6428 }
6429 }
6430
6431 fn parse_or_word(&mut self) -> PerlResult<Expr> {
6433 let mut left = self.parse_and_word()?;
6434 while matches!(self.peek(), Token::LogOrWord) {
6435 let line = left.line;
6436 self.advance();
6437 let right = self.parse_and_word()?;
6438 left = Expr {
6439 kind: ExprKind::BinOp {
6440 left: Box::new(left),
6441 op: BinOp::LogOrWord,
6442 right: Box::new(right),
6443 },
6444 line,
6445 };
6446 }
6447 Ok(left)
6448 }
6449
6450 fn parse_and_word(&mut self) -> PerlResult<Expr> {
6451 let mut left = self.parse_not_word()?;
6452 while matches!(self.peek(), Token::LogAndWord) {
6453 let line = left.line;
6454 self.advance();
6455 let right = self.parse_not_word()?;
6456 left = Expr {
6457 kind: ExprKind::BinOp {
6458 left: Box::new(left),
6459 op: BinOp::LogAndWord,
6460 right: Box::new(right),
6461 },
6462 line,
6463 };
6464 }
6465 Ok(left)
6466 }
6467
6468 fn parse_not_word(&mut self) -> PerlResult<Expr> {
6469 if matches!(self.peek(), Token::LogNotWord) {
6470 let line = self.peek_line();
6471 self.advance();
6472 let expr = self.parse_not_word()?;
6473 return Ok(Expr {
6474 kind: ExprKind::UnaryOp {
6475 op: UnaryOp::LogNotWord,
6476 expr: Box::new(expr),
6477 },
6478 line,
6479 });
6480 }
6481 self.parse_range()
6482 }
6483
6484 fn parse_log_or(&mut self) -> PerlResult<Expr> {
6485 let mut left = self.parse_log_and()?;
6486 loop {
6487 let op = match self.peek() {
6488 Token::LogOr => BinOp::LogOr,
6489 Token::DefinedOr => BinOp::DefinedOr,
6490 _ => break,
6491 };
6492 let line = left.line;
6493 self.advance();
6494 let right = self.parse_log_and()?;
6495 left = Expr {
6496 kind: ExprKind::BinOp {
6497 left: Box::new(left),
6498 op,
6499 right: Box::new(right),
6500 },
6501 line,
6502 };
6503 }
6504 Ok(left)
6505 }
6506
6507 fn parse_log_and(&mut self) -> PerlResult<Expr> {
6508 let mut left = self.parse_bit_or()?;
6509 while matches!(self.peek(), Token::LogAnd) {
6510 let line = left.line;
6511 self.advance();
6512 let right = self.parse_bit_or()?;
6513 left = Expr {
6514 kind: ExprKind::BinOp {
6515 left: Box::new(left),
6516 op: BinOp::LogAnd,
6517 right: Box::new(right),
6518 },
6519 line,
6520 };
6521 }
6522 Ok(left)
6523 }
6524
6525 fn parse_bit_or(&mut self) -> PerlResult<Expr> {
6526 let mut left = self.parse_bit_xor()?;
6527 while matches!(self.peek(), Token::BitOr) {
6528 let line = left.line;
6529 self.advance();
6530 let right = self.parse_bit_xor()?;
6531 left = Expr {
6532 kind: ExprKind::BinOp {
6533 left: Box::new(left),
6534 op: BinOp::BitOr,
6535 right: Box::new(right),
6536 },
6537 line,
6538 };
6539 }
6540 Ok(left)
6541 }
6542
6543 fn parse_bit_xor(&mut self) -> PerlResult<Expr> {
6544 let mut left = self.parse_bit_and()?;
6545 while matches!(self.peek(), Token::BitXor) {
6546 let line = left.line;
6547 self.advance();
6548 let right = self.parse_bit_and()?;
6549 left = Expr {
6550 kind: ExprKind::BinOp {
6551 left: Box::new(left),
6552 op: BinOp::BitXor,
6553 right: Box::new(right),
6554 },
6555 line,
6556 };
6557 }
6558 Ok(left)
6559 }
6560
6561 fn parse_bit_and(&mut self) -> PerlResult<Expr> {
6562 let mut left = self.parse_equality()?;
6563 while matches!(self.peek(), Token::BitAnd) {
6564 let line = left.line;
6565 self.advance();
6566 let right = self.parse_equality()?;
6567 left = Expr {
6568 kind: ExprKind::BinOp {
6569 left: Box::new(left),
6570 op: BinOp::BitAnd,
6571 right: Box::new(right),
6572 },
6573 line,
6574 };
6575 }
6576 Ok(left)
6577 }
6578
6579 fn parse_equality(&mut self) -> PerlResult<Expr> {
6580 let mut left = self.parse_comparison()?;
6581 loop {
6582 let op = match self.peek() {
6583 Token::NumEq => BinOp::NumEq,
6584 Token::NumNe => BinOp::NumNe,
6585 Token::StrEq => BinOp::StrEq,
6586 Token::StrNe => BinOp::StrNe,
6587 Token::Spaceship => BinOp::Spaceship,
6588 Token::StrCmp => BinOp::StrCmp,
6589 _ => break,
6590 };
6591 let line = left.line;
6592 self.advance();
6593 let right = self.parse_comparison()?;
6594 left = Expr {
6595 kind: ExprKind::BinOp {
6596 left: Box::new(left),
6597 op,
6598 right: Box::new(right),
6599 },
6600 line,
6601 };
6602 }
6603 Ok(left)
6604 }
6605
6606 fn parse_comparison(&mut self) -> PerlResult<Expr> {
6607 let left = self.parse_shift()?;
6608 let first_op = match self.peek() {
6609 Token::NumLt => BinOp::NumLt,
6610 Token::NumGt => BinOp::NumGt,
6611 Token::NumLe => BinOp::NumLe,
6612 Token::NumGe => BinOp::NumGe,
6613 Token::StrLt => BinOp::StrLt,
6614 Token::StrGt => BinOp::StrGt,
6615 Token::StrLe => BinOp::StrLe,
6616 Token::StrGe => BinOp::StrGe,
6617 _ => return Ok(left),
6618 };
6619 let line = left.line;
6620 self.advance();
6621 let middle = self.parse_shift()?;
6622
6623 let second_op = match self.peek() {
6624 Token::NumLt => Some(BinOp::NumLt),
6625 Token::NumGt => Some(BinOp::NumGt),
6626 Token::NumLe => Some(BinOp::NumLe),
6627 Token::NumGe => Some(BinOp::NumGe),
6628 Token::StrLt => Some(BinOp::StrLt),
6629 Token::StrGt => Some(BinOp::StrGt),
6630 Token::StrLe => Some(BinOp::StrLe),
6631 Token::StrGe => Some(BinOp::StrGe),
6632 _ => None,
6633 };
6634
6635 if second_op.is_none() {
6636 return Ok(Expr {
6637 kind: ExprKind::BinOp {
6638 left: Box::new(left),
6639 op: first_op,
6640 right: Box::new(middle),
6641 },
6642 line,
6643 });
6644 }
6645
6646 let mut operands = vec![left, middle];
6649 let mut ops = vec![first_op];
6650
6651 loop {
6652 let op = match self.peek() {
6653 Token::NumLt => BinOp::NumLt,
6654 Token::NumGt => BinOp::NumGt,
6655 Token::NumLe => BinOp::NumLe,
6656 Token::NumGe => BinOp::NumGe,
6657 Token::StrLt => BinOp::StrLt,
6658 Token::StrGt => BinOp::StrGt,
6659 Token::StrLe => BinOp::StrLe,
6660 Token::StrGe => BinOp::StrGe,
6661 _ => break,
6662 };
6663 self.advance();
6664 ops.push(op);
6665 operands.push(self.parse_shift()?);
6666 }
6667
6668 let mut result = Expr {
6670 kind: ExprKind::BinOp {
6671 left: Box::new(operands[0].clone()),
6672 op: ops[0],
6673 right: Box::new(operands[1].clone()),
6674 },
6675 line,
6676 };
6677
6678 for i in 1..ops.len() {
6679 let cmp = Expr {
6680 kind: ExprKind::BinOp {
6681 left: Box::new(operands[i].clone()),
6682 op: ops[i],
6683 right: Box::new(operands[i + 1].clone()),
6684 },
6685 line,
6686 };
6687 result = Expr {
6688 kind: ExprKind::BinOp {
6689 left: Box::new(result),
6690 op: BinOp::LogAnd,
6691 right: Box::new(cmp),
6692 },
6693 line,
6694 };
6695 }
6696
6697 Ok(result)
6698 }
6699
6700 fn parse_shift(&mut self) -> PerlResult<Expr> {
6701 let mut left = self.parse_addition()?;
6702 loop {
6703 let op = match self.peek() {
6704 Token::ShiftLeft => BinOp::ShiftLeft,
6705 Token::ShiftRight => BinOp::ShiftRight,
6706 _ => break,
6707 };
6708 let line = left.line;
6709 self.advance();
6710 let right = self.parse_addition()?;
6711 left = Expr {
6712 kind: ExprKind::BinOp {
6713 left: Box::new(left),
6714 op,
6715 right: Box::new(right),
6716 },
6717 line,
6718 };
6719 }
6720 Ok(left)
6721 }
6722
6723 fn parse_addition(&mut self) -> PerlResult<Expr> {
6724 let mut left = self.parse_multiplication()?;
6725 loop {
6726 let op = match self.peek() {
6729 Token::Plus if self.peek_line() == self.prev_line() => BinOp::Add,
6730 Token::Minus if self.peek_line() == self.prev_line() => BinOp::Sub,
6731 Token::Dot => BinOp::Concat,
6732 _ => break,
6733 };
6734 let line = left.line;
6735 self.advance();
6736 let right = self.parse_multiplication()?;
6737 left = Expr {
6738 kind: ExprKind::BinOp {
6739 left: Box::new(left),
6740 op,
6741 right: Box::new(right),
6742 },
6743 line,
6744 };
6745 }
6746 Ok(left)
6747 }
6748
6749 fn parse_multiplication(&mut self) -> PerlResult<Expr> {
6750 let mut left = self.parse_regex_bind()?;
6751 loop {
6752 let op = match self.peek() {
6753 Token::Star => BinOp::Mul,
6754 Token::Slash if self.suppress_slash_as_div == 0 => BinOp::Div,
6755 Token::Percent if self.peek_line() == self.prev_line() => BinOp::Mod,
6758 Token::X => {
6759 let line = left.line;
6760 self.advance();
6761 let right = self.parse_regex_bind()?;
6762 left = Expr {
6763 kind: ExprKind::Repeat {
6764 expr: Box::new(left),
6765 count: Box::new(right),
6766 },
6767 line,
6768 };
6769 continue;
6770 }
6771 _ => break,
6772 };
6773 let line = left.line;
6774 self.advance();
6775 let right = self.parse_regex_bind()?;
6776 left = Expr {
6777 kind: ExprKind::BinOp {
6778 left: Box::new(left),
6779 op,
6780 right: Box::new(right),
6781 },
6782 line,
6783 };
6784 }
6785 Ok(left)
6786 }
6787
6788 fn parse_regex_bind(&mut self) -> PerlResult<Expr> {
6789 let left = self.parse_unary()?;
6790 match self.peek() {
6791 Token::BindMatch => {
6792 let line = left.line;
6793 self.advance();
6794 match self.peek().clone() {
6795 Token::Regex(pattern, flags, delim) => {
6796 self.advance();
6797 Ok(Expr {
6798 kind: ExprKind::Match {
6799 expr: Box::new(left),
6800 pattern,
6801 flags,
6802 scalar_g: false,
6803 delim,
6804 },
6805 line,
6806 })
6807 }
6808 Token::Ident(ref s) if s.starts_with('\x00') => {
6809 let (Token::Ident(encoded), _) = self.advance() else {
6810 unreachable!()
6811 };
6812 let parts: Vec<&str> = encoded.split('\x00').collect();
6813 if parts.len() >= 4 && parts[1] == "s" {
6814 let delim = parts.get(5).and_then(|s| s.chars().next()).unwrap_or('/');
6815 Ok(Expr {
6816 kind: ExprKind::Substitution {
6817 expr: Box::new(left),
6818 pattern: parts[2].to_string(),
6819 replacement: parts[3].to_string(),
6820 flags: parts.get(4).unwrap_or(&"").to_string(),
6821 delim,
6822 },
6823 line,
6824 })
6825 } else if parts.len() >= 4 && parts[1] == "tr" {
6826 let delim = parts.get(5).and_then(|s| s.chars().next()).unwrap_or('/');
6827 Ok(Expr {
6828 kind: ExprKind::Transliterate {
6829 expr: Box::new(left),
6830 from: parts[2].to_string(),
6831 to: parts[3].to_string(),
6832 flags: parts.get(4).unwrap_or(&"").to_string(),
6833 delim,
6834 },
6835 line,
6836 })
6837 } else {
6838 Err(self.syntax_err("Invalid regex binding", line))
6839 }
6840 }
6841 _ => {
6842 let rhs = self.parse_unary()?;
6843 Ok(Expr {
6844 kind: ExprKind::BinOp {
6845 left: Box::new(left),
6846 op: BinOp::BindMatch,
6847 right: Box::new(rhs),
6848 },
6849 line,
6850 })
6851 }
6852 }
6853 }
6854 Token::BindNotMatch => {
6855 let line = left.line;
6856 self.advance();
6857 match self.peek().clone() {
6858 Token::Regex(pattern, flags, delim) => {
6859 self.advance();
6860 Ok(Expr {
6861 kind: ExprKind::UnaryOp {
6862 op: UnaryOp::LogNot,
6863 expr: Box::new(Expr {
6864 kind: ExprKind::Match {
6865 expr: Box::new(left),
6866 pattern,
6867 flags,
6868 scalar_g: false,
6869 delim,
6870 },
6871 line,
6872 }),
6873 },
6874 line,
6875 })
6876 }
6877 Token::Ident(ref s) if s.starts_with('\x00') => {
6878 let (Token::Ident(encoded), _) = self.advance() else {
6879 unreachable!()
6880 };
6881 let parts: Vec<&str> = encoded.split('\x00').collect();
6882 if parts.len() >= 4 && parts[1] == "s" {
6883 let delim = parts.get(5).and_then(|s| s.chars().next()).unwrap_or('/');
6884 Ok(Expr {
6885 kind: ExprKind::UnaryOp {
6886 op: UnaryOp::LogNot,
6887 expr: Box::new(Expr {
6888 kind: ExprKind::Substitution {
6889 expr: Box::new(left),
6890 pattern: parts[2].to_string(),
6891 replacement: parts[3].to_string(),
6892 flags: parts.get(4).unwrap_or(&"").to_string(),
6893 delim,
6894 },
6895 line,
6896 }),
6897 },
6898 line,
6899 })
6900 } else if parts.len() >= 4 && parts[1] == "tr" {
6901 let delim = parts.get(5).and_then(|s| s.chars().next()).unwrap_or('/');
6902 Ok(Expr {
6903 kind: ExprKind::UnaryOp {
6904 op: UnaryOp::LogNot,
6905 expr: Box::new(Expr {
6906 kind: ExprKind::Transliterate {
6907 expr: Box::new(left),
6908 from: parts[2].to_string(),
6909 to: parts[3].to_string(),
6910 flags: parts.get(4).unwrap_or(&"").to_string(),
6911 delim,
6912 },
6913 line,
6914 }),
6915 },
6916 line,
6917 })
6918 } else {
6919 Err(self.syntax_err("Invalid regex binding after !~", line))
6920 }
6921 }
6922 _ => {
6923 let rhs = self.parse_unary()?;
6924 Ok(Expr {
6925 kind: ExprKind::BinOp {
6926 left: Box::new(left),
6927 op: BinOp::BindNotMatch,
6928 right: Box::new(rhs),
6929 },
6930 line,
6931 })
6932 }
6933 }
6934 }
6935 _ => Ok(left),
6936 }
6937 }
6938
6939 fn parse_thread_input(&mut self) -> PerlResult<Expr> {
6942 self.suppress_slash_as_div = self.suppress_slash_as_div.saturating_add(1);
6943 let result = self.parse_range();
6944 self.suppress_slash_as_div = self.suppress_slash_as_div.saturating_sub(1);
6945 result
6946 }
6947
6948 fn parse_range(&mut self) -> PerlResult<Expr> {
6955 let left = self.parse_log_or()?;
6956 let line = left.line;
6957 let (exclusive, _colon_style) = if self.eat(&Token::RangeExclusive) {
6959 (true, false)
6960 } else if self.eat(&Token::Range) {
6961 (false, false)
6962 } else if self.suppress_colon_range == 0 && self.eat(&Token::Colon) {
6963 (false, true)
6966 } else {
6967 return Ok(left);
6968 };
6969 let right = self.parse_log_or()?;
6970 let step = if self.eat(&Token::Colon) {
6972 Some(Box::new(self.parse_unary()?))
6973 } else {
6974 None
6975 };
6976 Ok(Expr {
6977 kind: ExprKind::Range {
6978 from: Box::new(left),
6979 to: Box::new(right),
6980 exclusive,
6981 step,
6982 },
6983 line,
6984 })
6985 }
6986
6987 fn parse_package_qualified_identifier(&mut self) -> PerlResult<String> {
6989 let mut name = match self.advance() {
6990 (Token::Ident(n), _) => n,
6991 (tok, l) => {
6992 return Err(self.syntax_err(format!("Expected identifier, got {:?}", tok), l));
6993 }
6994 };
6995 while self.eat(&Token::PackageSep) {
6996 match self.advance() {
6997 (Token::Ident(part), _) => {
6998 name.push_str("::");
6999 name.push_str(&part);
7000 }
7001 (tok, l) => {
7002 return Err(self
7003 .syntax_err(format!("Expected identifier after `::`, got {:?}", tok), l));
7004 }
7005 }
7006 }
7007 Ok(name)
7008 }
7009
7010 fn parse_qualified_subroutine_name(&mut self) -> PerlResult<String> {
7012 self.parse_package_qualified_identifier()
7013 }
7014
7015 fn parse_unary(&mut self) -> PerlResult<Expr> {
7016 let line = self.peek_line();
7017 match self.peek().clone() {
7018 Token::Minus => {
7019 self.advance();
7020 let expr = self.parse_power()?;
7021 Ok(Expr {
7022 kind: ExprKind::UnaryOp {
7023 op: UnaryOp::Negate,
7024 expr: Box::new(expr),
7025 },
7026 line,
7027 })
7028 }
7029 Token::Plus => {
7032 self.advance();
7033 self.parse_unary()
7034 }
7035 Token::LogNot => {
7036 self.advance();
7037 let expr = self.parse_unary()?;
7038 Ok(Expr {
7039 kind: ExprKind::UnaryOp {
7040 op: UnaryOp::LogNot,
7041 expr: Box::new(expr),
7042 },
7043 line,
7044 })
7045 }
7046 Token::BitNot => {
7047 self.advance();
7048 let expr = self.parse_unary()?;
7049 Ok(Expr {
7050 kind: ExprKind::UnaryOp {
7051 op: UnaryOp::BitNot,
7052 expr: Box::new(expr),
7053 },
7054 line,
7055 })
7056 }
7057 Token::Increment => {
7058 self.advance();
7059 let expr = self.parse_postfix()?;
7060 Ok(Expr {
7061 kind: ExprKind::UnaryOp {
7062 op: UnaryOp::PreIncrement,
7063 expr: Box::new(expr),
7064 },
7065 line,
7066 })
7067 }
7068 Token::Decrement => {
7069 self.advance();
7070 let expr = self.parse_postfix()?;
7071 Ok(Expr {
7072 kind: ExprKind::UnaryOp {
7073 op: UnaryOp::PreDecrement,
7074 expr: Box::new(expr),
7075 },
7076 line,
7077 })
7078 }
7079 Token::BitAnd => {
7080 self.advance();
7083 if matches!(self.peek(), Token::LBrace) {
7084 self.advance();
7085 let inner = self.parse_expression()?;
7086 self.expect(&Token::RBrace)?;
7087 return Ok(Expr {
7088 kind: ExprKind::DynamicSubCodeRef(Box::new(inner)),
7089 line,
7090 });
7091 }
7092 if matches!(self.peek(), Token::Ident(_)) {
7093 let name = self.parse_qualified_subroutine_name()?;
7094 return Ok(Expr {
7095 kind: ExprKind::SubroutineRef(name),
7096 line,
7097 });
7098 }
7099 let target = self.parse_primary()?;
7100 if matches!(self.peek(), Token::LParen) {
7101 self.advance();
7102 let args = self.parse_arg_list()?;
7103 self.expect(&Token::RParen)?;
7104 return Ok(Expr {
7105 kind: ExprKind::IndirectCall {
7106 target: Box::new(target),
7107 args,
7108 ampersand: true,
7109 pass_caller_arglist: false,
7110 },
7111 line,
7112 });
7113 }
7114 Ok(Expr {
7116 kind: ExprKind::IndirectCall {
7117 target: Box::new(target),
7118 args: vec![],
7119 ampersand: true,
7120 pass_caller_arglist: true,
7121 },
7122 line,
7123 })
7124 }
7125 Token::Backslash => {
7126 self.advance();
7127 let expr = self.parse_unary()?;
7128 if let ExprKind::SubroutineRef(name) = expr.kind {
7129 return Ok(Expr {
7130 kind: ExprKind::SubroutineCodeRef(name),
7131 line,
7132 });
7133 }
7134 if matches!(expr.kind, ExprKind::DynamicSubCodeRef(_)) {
7135 return Ok(expr);
7136 }
7137 Ok(Expr {
7139 kind: ExprKind::ScalarRef(Box::new(expr)),
7140 line,
7141 })
7142 }
7143 Token::FileTest(op) => {
7144 self.advance();
7145 let expr = if Self::filetest_allows_implicit_topic(self.peek()) {
7147 Expr {
7148 kind: ExprKind::ScalarVar("_".into()),
7149 line: self.peek_line(),
7150 }
7151 } else {
7152 self.parse_unary()?
7153 };
7154 Ok(Expr {
7155 kind: ExprKind::FileTest {
7156 op,
7157 expr: Box::new(expr),
7158 },
7159 line,
7160 })
7161 }
7162 _ => self.parse_power(),
7163 }
7164 }
7165
7166 fn parse_power(&mut self) -> PerlResult<Expr> {
7167 let left = self.parse_postfix()?;
7168 if matches!(self.peek(), Token::Power) {
7169 let line = left.line;
7170 self.advance();
7171 let right = self.parse_unary()?; return Ok(Expr {
7173 kind: ExprKind::BinOp {
7174 left: Box::new(left),
7175 op: BinOp::Pow,
7176 right: Box::new(right),
7177 },
7178 line,
7179 });
7180 }
7181 Ok(left)
7182 }
7183
7184 fn parse_postfix(&mut self) -> PerlResult<Expr> {
7185 let mut expr = self.parse_primary()?;
7186 loop {
7187 match self.peek().clone() {
7188 Token::Increment => {
7189 if self.peek_line() > self.prev_line() {
7192 break;
7193 }
7194 let line = expr.line;
7195 self.advance();
7196 expr = Expr {
7197 kind: ExprKind::PostfixOp {
7198 expr: Box::new(expr),
7199 op: PostfixOp::Increment,
7200 },
7201 line,
7202 };
7203 }
7204 Token::Decrement => {
7205 if self.peek_line() > self.prev_line() {
7208 break;
7209 }
7210 let line = expr.line;
7211 self.advance();
7212 expr = Expr {
7213 kind: ExprKind::PostfixOp {
7214 expr: Box::new(expr),
7215 op: PostfixOp::Decrement,
7216 },
7217 line,
7218 };
7219 }
7220 Token::LParen => {
7221 if self.suppress_indirect_paren_call > 0 {
7222 break;
7223 }
7224 if self.peek_line() > self.prev_line() {
7228 break;
7229 }
7230 let line = expr.line;
7231 self.advance();
7232 let args = self.parse_arg_list()?;
7233 self.expect(&Token::RParen)?;
7234 expr = Expr {
7235 kind: ExprKind::IndirectCall {
7236 target: Box::new(expr),
7237 args,
7238 ampersand: false,
7239 pass_caller_arglist: false,
7240 },
7241 line,
7242 };
7243 }
7244 Token::Arrow => {
7245 let line = expr.line;
7246 self.advance();
7247 match self.peek().clone() {
7248 Token::LBracket => {
7249 self.advance();
7250 let index = self.parse_expression()?;
7251 self.expect(&Token::RBracket)?;
7252 expr = Expr {
7253 kind: ExprKind::ArrowDeref {
7254 expr: Box::new(expr),
7255 index: Box::new(index),
7256 kind: DerefKind::Array,
7257 },
7258 line,
7259 };
7260 }
7261 Token::LBrace => {
7262 self.advance();
7263 let key = self.parse_hash_subscript_key()?;
7264 self.expect(&Token::RBrace)?;
7265 expr = Expr {
7266 kind: ExprKind::ArrowDeref {
7267 expr: Box::new(expr),
7268 index: Box::new(key),
7269 kind: DerefKind::Hash,
7270 },
7271 line,
7272 };
7273 }
7274 Token::LParen => {
7275 self.advance();
7276 let args = self.parse_arg_list()?;
7277 self.expect(&Token::RParen)?;
7278 expr = Expr {
7279 kind: ExprKind::ArrowDeref {
7280 expr: Box::new(expr),
7281 index: Box::new(Expr {
7282 kind: ExprKind::List(args),
7283 line,
7284 }),
7285 kind: DerefKind::Call,
7286 },
7287 line,
7288 };
7289 }
7290 Token::Ident(method) => {
7291 self.advance();
7292 if method == "SUPER" {
7293 self.expect(&Token::PackageSep)?;
7294 let real_method = match self.advance() {
7295 (Token::Ident(n), _) => n,
7296 (tok, l) => {
7297 return Err(self.syntax_err(
7298 format!(
7299 "Expected method name after SUPER::, got {:?}",
7300 tok
7301 ),
7302 l,
7303 ));
7304 }
7305 };
7306 let args = if self.eat(&Token::LParen) {
7307 let a = self.parse_arg_list()?;
7308 self.expect(&Token::RParen)?;
7309 a
7310 } else {
7311 self.parse_method_arg_list_no_paren()?
7312 };
7313 expr = Expr {
7314 kind: ExprKind::MethodCall {
7315 object: Box::new(expr),
7316 method: real_method,
7317 args,
7318 super_call: true,
7319 },
7320 line,
7321 };
7322 } else {
7323 let mut method_name = method;
7324 while self.eat(&Token::PackageSep) {
7325 match self.advance() {
7326 (Token::Ident(part), _) => {
7327 method_name.push_str("::");
7328 method_name.push_str(&part);
7329 }
7330 (tok, l) => {
7331 return Err(self.syntax_err(
7332 format!(
7333 "Expected identifier after :: in method name, got {:?}",
7334 tok
7335 ),
7336 l,
7337 ));
7338 }
7339 }
7340 }
7341 let args = if self.eat(&Token::LParen) {
7342 let a = self.parse_arg_list()?;
7343 self.expect(&Token::RParen)?;
7344 a
7345 } else {
7346 self.parse_method_arg_list_no_paren()?
7347 };
7348 expr = Expr {
7349 kind: ExprKind::MethodCall {
7350 object: Box::new(expr),
7351 method: method_name,
7352 args,
7353 super_call: false,
7354 },
7355 line,
7356 };
7357 }
7358 }
7359 Token::ArrayAt => {
7365 self.advance(); match self.peek().clone() {
7367 Token::Star => {
7368 self.advance();
7369 expr = Expr {
7370 kind: ExprKind::Deref {
7371 expr: Box::new(expr),
7372 kind: Sigil::Array,
7373 },
7374 line,
7375 };
7376 }
7377 Token::LBracket => {
7378 self.advance();
7379 let mut indices = Vec::new();
7380 while !matches!(self.peek(), Token::RBracket | Token::Eof) {
7381 indices.push(self.parse_assign_expr()?);
7382 if !self.eat(&Token::Comma) {
7383 break;
7384 }
7385 }
7386 self.expect(&Token::RBracket)?;
7387 let source = Expr {
7388 kind: ExprKind::Deref {
7389 expr: Box::new(expr),
7390 kind: Sigil::Array,
7391 },
7392 line,
7393 };
7394 expr = Expr {
7395 kind: ExprKind::AnonymousListSlice {
7396 source: Box::new(source),
7397 indices,
7398 },
7399 line,
7400 };
7401 }
7402 Token::LBrace => {
7403 self.advance();
7404 let mut keys = Vec::new();
7405 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
7406 keys.push(self.parse_assign_expr()?);
7407 if !self.eat(&Token::Comma) {
7408 break;
7409 }
7410 }
7411 self.expect(&Token::RBrace)?;
7412 expr = Expr {
7413 kind: ExprKind::HashSliceDeref {
7414 container: Box::new(expr),
7415 keys,
7416 },
7417 line,
7418 };
7419 }
7420 tok => {
7421 return Err(self.syntax_err(
7422 format!(
7423 "Expected `*`, `[…]`, or `{{…}}` after `->@`, got {:?}",
7424 tok
7425 ),
7426 line,
7427 ));
7428 }
7429 }
7430 }
7431 Token::HashPercent => {
7432 self.advance(); match self.peek().clone() {
7434 Token::Star => {
7435 self.advance();
7436 expr = Expr {
7437 kind: ExprKind::Deref {
7438 expr: Box::new(expr),
7439 kind: Sigil::Hash,
7440 },
7441 line,
7442 };
7443 }
7444 tok => {
7445 return Err(self.syntax_err(
7446 format!("Expected `*` after `->%`, got {:?}", tok),
7447 line,
7448 ));
7449 }
7450 }
7451 }
7452 Token::X => {
7454 self.advance();
7455 let args = if self.eat(&Token::LParen) {
7456 let a = self.parse_arg_list()?;
7457 self.expect(&Token::RParen)?;
7458 a
7459 } else {
7460 self.parse_method_arg_list_no_paren()?
7461 };
7462 expr = Expr {
7463 kind: ExprKind::MethodCall {
7464 object: Box::new(expr),
7465 method: "x".to_string(),
7466 args,
7467 super_call: false,
7468 },
7469 line,
7470 };
7471 }
7472 _ => break,
7473 }
7474 }
7475 Token::LBracket => {
7476 let line = expr.line;
7478 if matches!(expr.kind, ExprKind::ScalarVar(_)) {
7479 if let ExprKind::ScalarVar(ref name) = expr.kind {
7480 let name = name.clone();
7481 self.advance();
7482 let index = self.parse_expression()?;
7483 self.expect(&Token::RBracket)?;
7484 expr = Expr {
7485 kind: ExprKind::ArrayElement {
7486 array: name,
7487 index: Box::new(index),
7488 },
7489 line,
7490 };
7491 }
7492 } else if postfix_lbracket_is_arrow_container(&expr) {
7493 self.advance();
7494 let indices = self.parse_arg_list()?;
7495 self.expect(&Token::RBracket)?;
7496 expr = Expr {
7497 kind: ExprKind::ArrowDeref {
7498 expr: Box::new(expr),
7499 index: Box::new(Expr {
7500 kind: ExprKind::List(indices),
7501 line,
7502 }),
7503 kind: DerefKind::Array,
7504 },
7505 line,
7506 };
7507 } else {
7508 self.advance();
7509 let indices = self.parse_arg_list()?;
7510 self.expect(&Token::RBracket)?;
7511 expr = Expr {
7512 kind: ExprKind::AnonymousListSlice {
7513 source: Box::new(expr),
7514 indices,
7515 },
7516 line,
7517 };
7518 }
7519 }
7520 Token::LBrace => {
7521 if self.suppress_scalar_hash_brace > 0 {
7522 break;
7523 }
7524 if self.peek_line() > self.prev_line() {
7527 break;
7528 }
7529 let line = expr.line;
7532 let is_scalar_named_hash = matches!(expr.kind, ExprKind::ScalarVar(_));
7533 let is_chainable_hash_subscript = is_scalar_named_hash
7534 || matches!(
7535 expr.kind,
7536 ExprKind::HashElement { .. }
7537 | ExprKind::ArrayElement { .. }
7538 | ExprKind::ArrowDeref { .. }
7539 | ExprKind::Deref {
7540 kind: Sigil::Scalar,
7541 ..
7542 }
7543 );
7544 if !is_chainable_hash_subscript {
7545 break;
7546 }
7547 self.advance();
7548 let key = self.parse_hash_subscript_key()?;
7549 self.expect(&Token::RBrace)?;
7550 expr = if is_scalar_named_hash {
7551 if let ExprKind::ScalarVar(ref name) = expr.kind {
7552 let name = name.clone();
7553 if name == "_" {
7555 Expr {
7556 kind: ExprKind::ArrowDeref {
7557 expr: Box::new(Expr {
7558 kind: ExprKind::ScalarVar("_".into()),
7559 line,
7560 }),
7561 index: Box::new(key),
7562 kind: DerefKind::Hash,
7563 },
7564 line,
7565 }
7566 } else {
7567 Expr {
7568 kind: ExprKind::HashElement {
7569 hash: name,
7570 key: Box::new(key),
7571 },
7572 line,
7573 }
7574 }
7575 } else {
7576 unreachable!("is_scalar_named_hash implies ScalarVar");
7577 }
7578 } else {
7579 Expr {
7580 kind: ExprKind::ArrowDeref {
7581 expr: Box::new(expr),
7582 index: Box::new(key),
7583 kind: DerefKind::Hash,
7584 },
7585 line,
7586 }
7587 };
7588 }
7589 _ => break,
7590 }
7591 }
7592 Ok(expr)
7593 }
7594
7595 fn parse_primary(&mut self) -> PerlResult<Expr> {
7596 let line = self.peek_line();
7597 if let Token::Ident(ref kw) = self.peek().clone() {
7602 if matches!(kw.as_str(), "my" | "our" | "state" | "local") {
7603 let kw_owned = kw.clone();
7604 let saved_pos = self.pos;
7609 let stmt = self.parse_my_our_local(&kw_owned, false)?;
7610 let decls = match stmt.kind {
7611 StmtKind::My(d)
7612 | StmtKind::Our(d)
7613 | StmtKind::State(d)
7614 | StmtKind::Local(d) => d,
7615 _ => {
7616 self.pos = saved_pos;
7621 return Err(self.syntax_err(
7622 "`my`/`our`/`local` in expression must declare variables",
7623 line,
7624 ));
7625 }
7626 };
7627 return Ok(Expr {
7628 kind: ExprKind::MyExpr {
7629 keyword: kw_owned,
7630 decls,
7631 },
7632 line,
7633 });
7634 }
7635 }
7636 match self.peek().clone() {
7637 Token::Integer(n) => {
7638 self.advance();
7639 Ok(Expr {
7640 kind: ExprKind::Integer(n),
7641 line,
7642 })
7643 }
7644 Token::Float(f) => {
7645 self.advance();
7646 Ok(Expr {
7647 kind: ExprKind::Float(f),
7648 line,
7649 })
7650 }
7651 Token::ArrowBrace => {
7657 self.advance();
7658 let mut stmts = Vec::new();
7659 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
7660 if self.eat(&Token::Semicolon) {
7661 continue;
7662 }
7663 stmts.push(self.parse_statement()?);
7664 }
7665 self.expect(&Token::RBrace)?;
7666 let inner_line = stmts.first().map(|s| s.line).unwrap_or(line);
7667 let inner = Expr {
7668 kind: ExprKind::CodeRef {
7669 params: vec![],
7670 body: stmts,
7671 },
7672 line: inner_line,
7673 };
7674 Ok(Expr {
7675 kind: ExprKind::Do(Box::new(inner)),
7676 line,
7677 })
7678 }
7679 Token::Star => {
7680 self.advance();
7681 if matches!(self.peek(), Token::LBrace) {
7682 self.advance();
7683 let inner = self.parse_expression()?;
7684 self.expect(&Token::RBrace)?;
7685 return Ok(Expr {
7686 kind: ExprKind::Deref {
7687 expr: Box::new(inner),
7688 kind: Sigil::Typeglob,
7689 },
7690 line,
7691 });
7692 }
7693 if matches!(
7695 self.peek(),
7696 Token::ScalarVar(_)
7697 | Token::ArrayVar(_)
7698 | Token::HashVar(_)
7699 | Token::DerefScalarVar(_)
7700 | Token::HashPercent
7701 ) {
7702 let inner = self.parse_postfix()?;
7703 return Ok(Expr {
7704 kind: ExprKind::TypeglobExpr(Box::new(inner)),
7705 line,
7706 });
7707 }
7708 let mut full_name = match self.advance() {
7710 (Token::Ident(n), _) => n,
7711 (Token::X, _) => "x".to_string(),
7712 (tok, l) => {
7713 return Err(self
7714 .syntax_err(format!("Expected identifier after *, got {:?}", tok), l));
7715 }
7716 };
7717 while self.eat(&Token::PackageSep) {
7718 match self.advance() {
7719 (Token::Ident(part), _) => {
7720 full_name = format!("{}::{}", full_name, part);
7721 }
7722 (Token::X, _) => {
7723 full_name = format!("{}::x", full_name);
7724 }
7725 (tok, l) => {
7726 return Err(self.syntax_err(
7727 format!("Expected identifier after :: in typeglob, got {:?}", tok),
7728 l,
7729 ));
7730 }
7731 }
7732 }
7733 Ok(Expr {
7734 kind: ExprKind::Typeglob(full_name),
7735 line,
7736 })
7737 }
7738 Token::SingleString(s) => {
7739 self.advance();
7740 Ok(Expr {
7741 kind: ExprKind::String(s),
7742 line,
7743 })
7744 }
7745 Token::DoubleString(s) => {
7746 self.advance();
7747 self.parse_interpolated_string(&s, line)
7748 }
7749 Token::BacktickString(s) => {
7750 self.advance();
7751 let inner = self.parse_interpolated_string(&s, line)?;
7752 Ok(Expr {
7753 kind: ExprKind::Qx(Box::new(inner)),
7754 line,
7755 })
7756 }
7757 Token::HereDoc(_, body, interpolate) => {
7758 self.advance();
7759 if interpolate {
7760 self.parse_interpolated_string(&body, line)
7761 } else {
7762 Ok(Expr {
7763 kind: ExprKind::String(body),
7764 line,
7765 })
7766 }
7767 }
7768 Token::Regex(pattern, flags, _delim) => {
7769 self.advance();
7770 Ok(Expr {
7771 kind: ExprKind::Regex(pattern, flags),
7772 line,
7773 })
7774 }
7775 Token::QW(words) => {
7776 self.advance();
7777 Ok(Expr {
7778 kind: ExprKind::QW(words),
7779 line,
7780 })
7781 }
7782 Token::DerefScalarVar(name) => {
7783 self.advance();
7784 Ok(Expr {
7785 kind: ExprKind::Deref {
7786 expr: Box::new(Expr {
7787 kind: ExprKind::ScalarVar(name),
7788 line,
7789 }),
7790 kind: Sigil::Scalar,
7791 },
7792 line,
7793 })
7794 }
7795 Token::ScalarVar(name) => {
7796 self.advance();
7797 Ok(Expr {
7798 kind: ExprKind::ScalarVar(name),
7799 line,
7800 })
7801 }
7802 Token::ArrayVar(name) => {
7803 self.advance();
7804 match self.peek() {
7806 Token::LBracket => {
7807 self.advance();
7808 let indices = self.parse_arg_list()?;
7809 self.expect(&Token::RBracket)?;
7810 Ok(Expr {
7811 kind: ExprKind::ArraySlice {
7812 array: name,
7813 indices,
7814 },
7815 line,
7816 })
7817 }
7818 Token::LBrace if self.suppress_scalar_hash_brace == 0 => {
7819 self.advance();
7820 let keys = self.parse_arg_list()?;
7821 self.expect(&Token::RBrace)?;
7822 Ok(Expr {
7823 kind: ExprKind::HashSlice { hash: name, keys },
7824 line,
7825 })
7826 }
7827 _ => Ok(Expr {
7828 kind: ExprKind::ArrayVar(name),
7829 line,
7830 }),
7831 }
7832 }
7833 Token::HashVar(name) => {
7834 self.advance();
7835 Ok(Expr {
7836 kind: ExprKind::HashVar(name),
7837 line,
7838 })
7839 }
7840 Token::HashPercent => {
7841 self.advance();
7843 if matches!(self.peek(), Token::ScalarVar(_)) {
7844 let n = match self.advance() {
7845 (Token::ScalarVar(n), _) => n,
7846 (tok, l) => {
7847 return Err(self.syntax_err(
7848 format!("Expected scalar variable after %%, got {:?}", tok),
7849 l,
7850 ));
7851 }
7852 };
7853 return Ok(Expr {
7854 kind: ExprKind::Deref {
7855 expr: Box::new(Expr {
7856 kind: ExprKind::ScalarVar(n),
7857 line,
7858 }),
7859 kind: Sigil::Hash,
7860 },
7861 line,
7862 });
7863 }
7864 if matches!(self.peek(), Token::LBracket) {
7869 self.advance();
7870 let pairs = self.parse_hashref_pairs_until(&Token::RBracket)?;
7871 self.expect(&Token::RBracket)?;
7872 let href = Expr {
7873 kind: ExprKind::HashRef(pairs),
7874 line,
7875 };
7876 return Ok(Expr {
7877 kind: ExprKind::Deref {
7878 expr: Box::new(href),
7879 kind: Sigil::Hash,
7880 },
7881 line,
7882 });
7883 }
7884 self.expect(&Token::LBrace)?;
7885 let looks_like_pair = matches!(
7891 self.peek(),
7892 Token::Ident(_) | Token::SingleString(_) | Token::DoubleString(_)
7893 ) && matches!(self.peek_at(1), Token::FatArrow);
7894 let inner = if looks_like_pair {
7895 let pairs = self.parse_hashref_pairs_until(&Token::RBrace)?;
7896 Expr {
7897 kind: ExprKind::HashRef(pairs),
7898 line,
7899 }
7900 } else {
7901 self.parse_expression()?
7902 };
7903 self.expect(&Token::RBrace)?;
7904 Ok(Expr {
7905 kind: ExprKind::Deref {
7906 expr: Box::new(inner),
7907 kind: Sigil::Hash,
7908 },
7909 line,
7910 })
7911 }
7912 Token::ArrayAt => {
7913 self.advance();
7914 if matches!(self.peek(), Token::LBrace) {
7916 self.advance();
7917 let inner = self.parse_expression()?;
7918 self.expect(&Token::RBrace)?;
7919 return Ok(Expr {
7920 kind: ExprKind::Deref {
7921 expr: Box::new(inner),
7922 kind: Sigil::Array,
7923 },
7924 line,
7925 });
7926 }
7927 if matches!(self.peek(), Token::LBracket) {
7931 self.advance();
7932 let mut elems = Vec::new();
7933 if !matches!(self.peek(), Token::RBracket) {
7934 elems.push(self.parse_assign_expr()?);
7935 while self.eat(&Token::Comma) {
7936 if matches!(self.peek(), Token::RBracket) {
7937 break;
7938 }
7939 elems.push(self.parse_assign_expr()?);
7940 }
7941 }
7942 self.expect(&Token::RBracket)?;
7943 let aref = Expr {
7944 kind: ExprKind::ArrayRef(elems),
7945 line,
7946 };
7947 return Ok(Expr {
7948 kind: ExprKind::Deref {
7949 expr: Box::new(aref),
7950 kind: Sigil::Array,
7951 },
7952 line,
7953 });
7954 }
7955 let container = match self.peek().clone() {
7957 Token::ScalarVar(n) => {
7958 self.advance();
7959 Expr {
7960 kind: ExprKind::ScalarVar(n),
7961 line,
7962 }
7963 }
7964 _ => {
7965 return Err(self.syntax_err(
7966 "Expected `$name`, `{`, or `[` after `@` (e.g. `@$aref`, `@{expr}`, `@[1,2,3]`, or `@$href{keys}`)",
7967 line,
7968 ));
7969 }
7970 };
7971 if matches!(self.peek(), Token::LBrace) {
7972 self.advance();
7973 let keys = self.parse_arg_list()?;
7974 self.expect(&Token::RBrace)?;
7975 return Ok(Expr {
7976 kind: ExprKind::HashSliceDeref {
7977 container: Box::new(container),
7978 keys,
7979 },
7980 line,
7981 });
7982 }
7983 Ok(Expr {
7984 kind: ExprKind::Deref {
7985 expr: Box::new(container),
7986 kind: Sigil::Array,
7987 },
7988 line,
7989 })
7990 }
7991 Token::LParen => {
7992 self.advance();
7993 if matches!(self.peek(), Token::RParen) {
7994 self.advance();
7995 return Ok(Expr {
7996 kind: ExprKind::List(vec![]),
7997 line,
7998 });
7999 }
8000 let saved_no_pipe = self.no_pipe_forward_depth;
8003 self.no_pipe_forward_depth = 0;
8004 let expr = self.parse_expression();
8005 self.no_pipe_forward_depth = saved_no_pipe;
8006 let expr = expr?;
8007 self.expect(&Token::RParen)?;
8008 Ok(expr)
8009 }
8010 Token::LBracket => {
8011 self.advance();
8012 let elems = self.parse_arg_list()?;
8013 self.expect(&Token::RBracket)?;
8014 Ok(Expr {
8015 kind: ExprKind::ArrayRef(elems),
8016 line,
8017 })
8018 }
8019 Token::LBrace => {
8020 self.advance();
8022 let saved = self.pos;
8024 match self.try_parse_hash_ref() {
8025 Ok(pairs) => Ok(Expr {
8026 kind: ExprKind::HashRef(pairs),
8027 line,
8028 }),
8029 Err(_) => {
8030 self.pos = saved;
8031 let mut stmts = Vec::new();
8033 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
8034 if self.eat(&Token::Semicolon) {
8035 continue;
8036 }
8037 stmts.push(self.parse_statement()?);
8038 }
8039 self.expect(&Token::RBrace)?;
8040 Ok(Expr {
8041 kind: ExprKind::CodeRef {
8042 params: vec![],
8043 body: stmts,
8044 },
8045 line,
8046 })
8047 }
8048 }
8049 }
8050 Token::Diamond => {
8051 self.advance();
8052 Ok(Expr {
8053 kind: ExprKind::ReadLine(None),
8054 line,
8055 })
8056 }
8057 Token::ReadLine(handle) => {
8058 self.advance();
8059 Ok(Expr {
8060 kind: ExprKind::ReadLine(Some(handle)),
8061 line,
8062 })
8063 }
8064
8065 Token::ThreadArrow => {
8067 self.advance();
8068 self.parse_thread_macro(line, false)
8069 }
8070 Token::ThreadArrowLast => {
8071 self.advance();
8072 self.parse_thread_macro(line, true)
8073 }
8074 Token::Ident(ref name) => {
8075 let name = name.clone();
8076 if name.starts_with('\x00') {
8078 self.advance();
8079 let parts: Vec<&str> = name.split('\x00').collect();
8080 if parts.len() >= 4 && parts[1] == "s" {
8081 let delim = parts.get(5).and_then(|s| s.chars().next()).unwrap_or('/');
8082 return Ok(Expr {
8083 kind: ExprKind::Substitution {
8084 expr: Box::new(Expr {
8085 kind: ExprKind::ScalarVar("_".into()),
8086 line,
8087 }),
8088 pattern: parts[2].to_string(),
8089 replacement: parts[3].to_string(),
8090 flags: parts.get(4).unwrap_or(&"").to_string(),
8091 delim,
8092 },
8093 line,
8094 });
8095 }
8096 if parts.len() >= 4 && parts[1] == "tr" {
8097 let delim = parts.get(5).and_then(|s| s.chars().next()).unwrap_or('/');
8098 return Ok(Expr {
8099 kind: ExprKind::Transliterate {
8100 expr: Box::new(Expr {
8101 kind: ExprKind::ScalarVar("_".into()),
8102 line,
8103 }),
8104 from: parts[2].to_string(),
8105 to: parts[3].to_string(),
8106 flags: parts.get(4).unwrap_or(&"").to_string(),
8107 delim,
8108 },
8109 line,
8110 });
8111 }
8112 return Err(self.syntax_err("Unexpected encoded token", line));
8113 }
8114 self.parse_named_expr(name)
8115 }
8116
8117 Token::Percent => {
8120 self.advance();
8121 match self.peek().clone() {
8122 Token::Ident(name) => {
8123 self.advance();
8124 Ok(Expr {
8125 kind: ExprKind::HashVar(name),
8126 line,
8127 })
8128 }
8129 Token::ScalarVar(n) => {
8130 self.advance();
8131 Ok(Expr {
8132 kind: ExprKind::Deref {
8133 expr: Box::new(Expr {
8134 kind: ExprKind::ScalarVar(n),
8135 line,
8136 }),
8137 kind: Sigil::Hash,
8138 },
8139 line,
8140 })
8141 }
8142 Token::LBrace => {
8143 self.advance();
8144 let looks_like_pair = matches!(
8145 self.peek(),
8146 Token::Ident(_) | Token::SingleString(_) | Token::DoubleString(_)
8147 ) && matches!(self.peek_at(1), Token::FatArrow);
8148 let inner = if looks_like_pair {
8149 let pairs = self.parse_hashref_pairs_until(&Token::RBrace)?;
8150 Expr {
8151 kind: ExprKind::HashRef(pairs),
8152 line,
8153 }
8154 } else {
8155 self.parse_expression()?
8156 };
8157 self.expect(&Token::RBrace)?;
8158 Ok(Expr {
8159 kind: ExprKind::Deref {
8160 expr: Box::new(inner),
8161 kind: Sigil::Hash,
8162 },
8163 line,
8164 })
8165 }
8166 Token::LBracket => {
8167 self.advance();
8168 let pairs = self.parse_hashref_pairs_until(&Token::RBracket)?;
8169 self.expect(&Token::RBracket)?;
8170 let href = Expr {
8171 kind: ExprKind::HashRef(pairs),
8172 line,
8173 };
8174 Ok(Expr {
8175 kind: ExprKind::Deref {
8176 expr: Box::new(href),
8177 kind: Sigil::Hash,
8178 },
8179 line,
8180 })
8181 }
8182 tok => Err(self.syntax_err(
8183 format!(
8184 "Expected identifier, `$`, `{{`, or `[` after `%`, got {:?}",
8185 tok
8186 ),
8187 line,
8188 )),
8189 }
8190 }
8191
8192 tok => Err(self.syntax_err(format!("Unexpected token {:?}", tok), line)),
8193 }
8194 }
8195
8196 fn parse_named_expr(&mut self, mut name: String) -> PerlResult<Expr> {
8197 let line = self.peek_line();
8198 self.advance(); while self.eat(&Token::PackageSep) {
8200 match self.advance() {
8201 (Token::Ident(part), _) => {
8202 name = format!("{}::{}", name, part);
8203 }
8204 (tok, err_line) => {
8205 return Err(self.syntax_err(
8206 format!("Expected identifier after `::`, got {:?}", tok),
8207 err_line,
8208 ));
8209 }
8210 }
8211 }
8212
8213 if matches!(self.peek(), Token::FatArrow) {
8217 return Ok(Expr {
8218 kind: ExprKind::String(name),
8219 line,
8220 });
8221 }
8222
8223 if crate::compat_mode() {
8224 if let Some(ext) = Self::stryke_extension_name(&name) {
8225 if !self.declared_subs.contains(&name) {
8226 return Err(self.syntax_err(
8227 format!("`{ext}` is a stryke extension (disabled by --compat)"),
8228 line,
8229 ));
8230 }
8231 }
8232 }
8233
8234 match name.as_str() {
8235 "__FILE__" => Ok(Expr {
8236 kind: ExprKind::MagicConst(MagicConstKind::File),
8237 line,
8238 }),
8239 "__LINE__" => Ok(Expr {
8240 kind: ExprKind::MagicConst(MagicConstKind::Line),
8241 line,
8242 }),
8243 "__SUB__" => Ok(Expr {
8244 kind: ExprKind::MagicConst(MagicConstKind::Sub),
8245 line,
8246 }),
8247 "stdin" => Ok(Expr {
8248 kind: ExprKind::FuncCall {
8249 name: "stdin".into(),
8250 args: vec![],
8251 },
8252 line,
8253 }),
8254 "range" => {
8255 let args = self.parse_builtin_args()?;
8256 Ok(Expr {
8257 kind: ExprKind::FuncCall {
8258 name: "range".into(),
8259 args,
8260 },
8261 line,
8262 })
8263 }
8264 "print" | "pr" => self.parse_print_like(|h, a| ExprKind::Print { handle: h, args: a }),
8265 "say" => {
8266 if !crate::compat_mode() {
8267 return Err(self.syntax_err("stryke uses `p` instead of `say` (this is not Perl 5)", line));
8268 }
8269 self.parse_print_like(|h, a| ExprKind::Say { handle: h, args: a })
8270 }
8271 "p" => self.parse_print_like(|h, a| ExprKind::Say { handle: h, args: a }),
8272 "printf" => self.parse_print_like(|h, a| ExprKind::Printf { handle: h, args: a }),
8273 "die" => {
8274 let args = self.parse_list_until_terminator()?;
8275 Ok(Expr {
8276 kind: ExprKind::Die(args),
8277 line,
8278 })
8279 }
8280 "warn" => {
8281 let args = self.parse_list_until_terminator()?;
8282 Ok(Expr {
8283 kind: ExprKind::Warn(args),
8284 line,
8285 })
8286 }
8287 "croak" | "confess" => {
8292 let args = self.parse_list_until_terminator()?;
8293 Ok(Expr {
8294 kind: ExprKind::Die(args),
8295 line,
8296 })
8297 }
8298 "carp" | "cluck" => {
8300 let args = self.parse_list_until_terminator()?;
8301 Ok(Expr {
8302 kind: ExprKind::Warn(args),
8303 line,
8304 })
8305 }
8306 "chomp" => {
8307 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8308 return Ok(e);
8309 }
8310 let a = self.parse_one_arg_or_default()?;
8311 Ok(Expr {
8312 kind: ExprKind::Chomp(Box::new(a)),
8313 line,
8314 })
8315 }
8316 "chop" => {
8317 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8318 return Ok(e);
8319 }
8320 let a = self.parse_one_arg_or_default()?;
8321 Ok(Expr {
8322 kind: ExprKind::Chop(Box::new(a)),
8323 line,
8324 })
8325 }
8326 "length" => {
8327 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8328 return Ok(e);
8329 }
8330 let a = self.parse_one_arg_or_default()?;
8331 Ok(Expr {
8332 kind: ExprKind::Length(Box::new(a)),
8333 line,
8334 })
8335 }
8336 "defined" => {
8337 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8338 return Ok(e);
8339 }
8340 let a = self.parse_one_arg_or_default()?;
8341 Ok(Expr {
8342 kind: ExprKind::Defined(Box::new(a)),
8343 line,
8344 })
8345 }
8346 "ref" => {
8347 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8348 return Ok(e);
8349 }
8350 let a = self.parse_one_arg_or_default()?;
8351 Ok(Expr {
8352 kind: ExprKind::Ref(Box::new(a)),
8353 line,
8354 })
8355 }
8356 "undef" => {
8357 if self.peek_line() == self.prev_line()
8360 && matches!(
8361 self.peek(),
8362 Token::ScalarVar(_) | Token::ArrayVar(_) | Token::HashVar(_)
8363 )
8364 {
8365 let target = self.parse_primary()?;
8366 return Ok(Expr {
8367 kind: ExprKind::Assign {
8368 target: Box::new(target),
8369 value: Box::new(Expr {
8370 kind: ExprKind::Undef,
8371 line,
8372 }),
8373 },
8374 line,
8375 });
8376 }
8377 Ok(Expr {
8378 kind: ExprKind::Undef,
8379 line,
8380 })
8381 }
8382 "scalar" => {
8383 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8384 return Ok(e);
8385 }
8386 let a = self.parse_one_arg_or_default()?;
8387 Ok(Expr {
8388 kind: ExprKind::ScalarContext(Box::new(a)),
8389 line,
8390 })
8391 }
8392 "abs" => {
8393 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8394 return Ok(e);
8395 }
8396 let a = self.parse_one_arg_or_default()?;
8397 Ok(Expr {
8398 kind: ExprKind::Abs(Box::new(a)),
8399 line,
8400 })
8401 }
8402 "inc" | "dec" => {
8407 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8408 return Ok(e);
8409 }
8410 let a = self.parse_one_arg_or_default()?;
8411 Ok(Expr {
8412 kind: ExprKind::FuncCall {
8413 name,
8414 args: vec![a],
8415 },
8416 line,
8417 })
8418 }
8419 "int" => {
8420 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8421 return Ok(e);
8422 }
8423 let a = self.parse_one_arg_or_default()?;
8424 Ok(Expr {
8425 kind: ExprKind::Int(Box::new(a)),
8426 line,
8427 })
8428 }
8429 "sqrt" => {
8430 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8431 return Ok(e);
8432 }
8433 let a = self.parse_one_arg_or_default()?;
8434 Ok(Expr {
8435 kind: ExprKind::Sqrt(Box::new(a)),
8436 line,
8437 })
8438 }
8439 "sin" => {
8440 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8441 return Ok(e);
8442 }
8443 let a = self.parse_one_arg_or_default()?;
8444 Ok(Expr {
8445 kind: ExprKind::Sin(Box::new(a)),
8446 line,
8447 })
8448 }
8449 "cos" => {
8450 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8451 return Ok(e);
8452 }
8453 let a = self.parse_one_arg_or_default()?;
8454 Ok(Expr {
8455 kind: ExprKind::Cos(Box::new(a)),
8456 line,
8457 })
8458 }
8459 "atan2" => {
8460 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8461 return Ok(e);
8462 }
8463 let args = self.parse_builtin_args()?;
8464 if args.len() != 2 {
8465 return Err(self.syntax_err("atan2 requires two arguments", line));
8466 }
8467 Ok(Expr {
8468 kind: ExprKind::Atan2 {
8469 y: Box::new(args[0].clone()),
8470 x: Box::new(args[1].clone()),
8471 },
8472 line,
8473 })
8474 }
8475 "exp" => {
8476 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8477 return Ok(e);
8478 }
8479 let a = self.parse_one_arg_or_default()?;
8480 Ok(Expr {
8481 kind: ExprKind::Exp(Box::new(a)),
8482 line,
8483 })
8484 }
8485 "log" => {
8486 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8487 return Ok(e);
8488 }
8489 let a = self.parse_one_arg_or_default()?;
8490 Ok(Expr {
8491 kind: ExprKind::Log(Box::new(a)),
8492 line,
8493 })
8494 }
8495 "input" => {
8496 let args = if matches!(
8497 self.peek(),
8498 Token::Semicolon
8499 | Token::RBrace
8500 | Token::RParen
8501 | Token::Eof
8502 | Token::Comma
8503 | Token::PipeForward
8504 ) {
8505 vec![]
8506 } else if matches!(self.peek(), Token::LParen) {
8507 self.advance();
8508 if matches!(self.peek(), Token::RParen) {
8509 self.advance();
8510 vec![]
8511 } else {
8512 let a = self.parse_expression()?;
8513 self.expect(&Token::RParen)?;
8514 vec![a]
8515 }
8516 } else {
8517 let a = self.parse_one_arg()?;
8518 vec![a]
8519 };
8520 Ok(Expr {
8521 kind: ExprKind::FuncCall {
8522 name: "input".to_string(),
8523 args,
8524 },
8525 line,
8526 })
8527 }
8528 "rand" => {
8529 if matches!(
8530 self.peek(),
8531 Token::Semicolon
8532 | Token::RBrace
8533 | Token::RParen
8534 | Token::Eof
8535 | Token::Comma
8536 | Token::PipeForward
8537 ) {
8538 Ok(Expr {
8539 kind: ExprKind::Rand(None),
8540 line,
8541 })
8542 } else if matches!(self.peek(), Token::LParen) {
8543 self.advance();
8544 if matches!(self.peek(), Token::RParen) {
8545 self.advance();
8546 Ok(Expr {
8547 kind: ExprKind::Rand(None),
8548 line,
8549 })
8550 } else {
8551 let a = self.parse_expression()?;
8552 self.expect(&Token::RParen)?;
8553 Ok(Expr {
8554 kind: ExprKind::Rand(Some(Box::new(a))),
8555 line,
8556 })
8557 }
8558 } else {
8559 let a = self.parse_one_arg()?;
8560 Ok(Expr {
8561 kind: ExprKind::Rand(Some(Box::new(a))),
8562 line,
8563 })
8564 }
8565 }
8566 "srand" => {
8567 if matches!(
8568 self.peek(),
8569 Token::Semicolon
8570 | Token::RBrace
8571 | Token::RParen
8572 | Token::Eof
8573 | Token::Comma
8574 | Token::PipeForward
8575 ) {
8576 Ok(Expr {
8577 kind: ExprKind::Srand(None),
8578 line,
8579 })
8580 } else if matches!(self.peek(), Token::LParen) {
8581 self.advance();
8582 if matches!(self.peek(), Token::RParen) {
8583 self.advance();
8584 Ok(Expr {
8585 kind: ExprKind::Srand(None),
8586 line,
8587 })
8588 } else {
8589 let a = self.parse_expression()?;
8590 self.expect(&Token::RParen)?;
8591 Ok(Expr {
8592 kind: ExprKind::Srand(Some(Box::new(a))),
8593 line,
8594 })
8595 }
8596 } else {
8597 let a = self.parse_one_arg()?;
8598 Ok(Expr {
8599 kind: ExprKind::Srand(Some(Box::new(a))),
8600 line,
8601 })
8602 }
8603 }
8604 "hex" => {
8605 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8606 return Ok(e);
8607 }
8608 let a = self.parse_one_arg_or_default()?;
8609 Ok(Expr {
8610 kind: ExprKind::Hex(Box::new(a)),
8611 line,
8612 })
8613 }
8614 "oct" => {
8615 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8616 return Ok(e);
8617 }
8618 let a = self.parse_one_arg_or_default()?;
8619 Ok(Expr {
8620 kind: ExprKind::Oct(Box::new(a)),
8621 line,
8622 })
8623 }
8624 "chr" => {
8625 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8626 return Ok(e);
8627 }
8628 let a = self.parse_one_arg_or_default()?;
8629 Ok(Expr {
8630 kind: ExprKind::Chr(Box::new(a)),
8631 line,
8632 })
8633 }
8634 "ord" => {
8635 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8636 return Ok(e);
8637 }
8638 let a = self.parse_one_arg_or_default()?;
8639 Ok(Expr {
8640 kind: ExprKind::Ord(Box::new(a)),
8641 line,
8642 })
8643 }
8644 "lc" => {
8645 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8646 return Ok(e);
8647 }
8648 let a = self.parse_one_arg_or_default()?;
8649 Ok(Expr {
8650 kind: ExprKind::Lc(Box::new(a)),
8651 line,
8652 })
8653 }
8654 "uc" => {
8655 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8656 return Ok(e);
8657 }
8658 let a = self.parse_one_arg_or_default()?;
8659 Ok(Expr {
8660 kind: ExprKind::Uc(Box::new(a)),
8661 line,
8662 })
8663 }
8664 "lcfirst" => {
8665 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8666 return Ok(e);
8667 }
8668 let a = self.parse_one_arg_or_default()?;
8669 Ok(Expr {
8670 kind: ExprKind::Lcfirst(Box::new(a)),
8671 line,
8672 })
8673 }
8674 "ucfirst" => {
8675 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8676 return Ok(e);
8677 }
8678 let a = self.parse_one_arg_or_default()?;
8679 Ok(Expr {
8680 kind: ExprKind::Ucfirst(Box::new(a)),
8681 line,
8682 })
8683 }
8684 "fc" => {
8685 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8686 return Ok(e);
8687 }
8688 let a = self.parse_one_arg_or_default()?;
8689 Ok(Expr {
8690 kind: ExprKind::Fc(Box::new(a)),
8691 line,
8692 })
8693 }
8694 "crypt" => {
8695 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8696 return Ok(e);
8697 }
8698 let args = self.parse_builtin_args()?;
8699 if args.len() != 2 {
8700 return Err(self.syntax_err("crypt requires two arguments", line));
8701 }
8702 Ok(Expr {
8703 kind: ExprKind::Crypt {
8704 plaintext: Box::new(args[0].clone()),
8705 salt: Box::new(args[1].clone()),
8706 },
8707 line,
8708 })
8709 }
8710 "pos" => {
8711 if matches!(
8712 self.peek(),
8713 Token::Semicolon
8714 | Token::RBrace
8715 | Token::RParen
8716 | Token::Eof
8717 | Token::Comma
8718 | Token::PipeForward
8719 ) {
8720 Ok(Expr {
8721 kind: ExprKind::Pos(None),
8722 line,
8723 })
8724 } else if matches!(self.peek(), Token::Assign) {
8725 self.advance();
8727 let rhs = self.parse_assign_expr()?;
8728 Ok(Expr {
8729 kind: ExprKind::Assign {
8730 target: Box::new(Expr {
8731 kind: ExprKind::Pos(Some(Box::new(Expr {
8732 kind: ExprKind::ScalarVar("_".into()),
8733 line,
8734 }))),
8735 line,
8736 }),
8737 value: Box::new(rhs),
8738 },
8739 line,
8740 })
8741 } else if matches!(self.peek(), Token::LParen) {
8742 self.advance();
8743 if matches!(self.peek(), Token::RParen) {
8744 self.advance();
8745 Ok(Expr {
8746 kind: ExprKind::Pos(None),
8747 line,
8748 })
8749 } else {
8750 let a = self.parse_expression()?;
8751 self.expect(&Token::RParen)?;
8752 Ok(Expr {
8753 kind: ExprKind::Pos(Some(Box::new(a))),
8754 line,
8755 })
8756 }
8757 } else {
8758 let saved = self.pos;
8759 let subj = self.parse_unary()?;
8760 if matches!(self.peek(), Token::Assign) {
8761 self.advance();
8762 let rhs = self.parse_assign_expr()?;
8763 Ok(Expr {
8764 kind: ExprKind::Assign {
8765 target: Box::new(Expr {
8766 kind: ExprKind::Pos(Some(Box::new(subj))),
8767 line,
8768 }),
8769 value: Box::new(rhs),
8770 },
8771 line,
8772 })
8773 } else {
8774 self.pos = saved;
8775 let a = self.parse_one_arg()?;
8776 Ok(Expr {
8777 kind: ExprKind::Pos(Some(Box::new(a))),
8778 line,
8779 })
8780 }
8781 }
8782 }
8783 "study" => {
8784 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8785 return Ok(e);
8786 }
8787 let a = self.parse_one_arg_or_default()?;
8788 Ok(Expr {
8789 kind: ExprKind::Study(Box::new(a)),
8790 line,
8791 })
8792 }
8793 "push" => {
8794 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8795 return Ok(e);
8796 }
8797 let args = self.parse_builtin_args()?;
8798 let (first, rest) = args
8799 .split_first()
8800 .ok_or_else(|| self.syntax_err("push requires arguments", line))?;
8801 Ok(Expr {
8802 kind: ExprKind::Push {
8803 array: Box::new(first.clone()),
8804 values: rest.to_vec(),
8805 },
8806 line,
8807 })
8808 }
8809 "pop" => {
8810 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8811 return Ok(e);
8812 }
8813 let a = self.parse_one_arg_or_argv()?;
8814 Ok(Expr {
8815 kind: ExprKind::Pop(Box::new(a)),
8816 line,
8817 })
8818 }
8819 "shift" => {
8820 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8821 return Ok(e);
8822 }
8823 let a = self.parse_one_arg_or_argv()?;
8824 Ok(Expr {
8825 kind: ExprKind::Shift(Box::new(a)),
8826 line,
8827 })
8828 }
8829 "unshift" => {
8830 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8831 return Ok(e);
8832 }
8833 let args = self.parse_builtin_args()?;
8834 let (first, rest) = args
8835 .split_first()
8836 .ok_or_else(|| self.syntax_err("unshift requires arguments", line))?;
8837 Ok(Expr {
8838 kind: ExprKind::Unshift {
8839 array: Box::new(first.clone()),
8840 values: rest.to_vec(),
8841 },
8842 line,
8843 })
8844 }
8845 "splice" => {
8846 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8847 return Ok(e);
8848 }
8849 let args = self.parse_builtin_args()?;
8850 let mut iter = args.into_iter();
8851 let array = Box::new(
8852 iter.next()
8853 .ok_or_else(|| self.syntax_err("splice requires arguments", line))?,
8854 );
8855 let offset = iter.next().map(Box::new);
8856 let length = iter.next().map(Box::new);
8857 let replacement: Vec<Expr> = iter.collect();
8858 Ok(Expr {
8859 kind: ExprKind::Splice {
8860 array,
8861 offset,
8862 length,
8863 replacement,
8864 },
8865 line,
8866 })
8867 }
8868 "delete" => {
8869 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8870 return Ok(e);
8871 }
8872 let a = self.parse_postfix()?;
8873 Ok(Expr {
8874 kind: ExprKind::Delete(Box::new(a)),
8875 line,
8876 })
8877 }
8878 "exists" => {
8879 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8880 return Ok(e);
8881 }
8882 let a = self.parse_postfix()?;
8883 Ok(Expr {
8884 kind: ExprKind::Exists(Box::new(a)),
8885 line,
8886 })
8887 }
8888 "keys" => {
8889 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8890 return Ok(e);
8891 }
8892 let a = self.parse_one_arg_or_default()?;
8893 Ok(Expr {
8894 kind: ExprKind::Keys(Box::new(a)),
8895 line,
8896 })
8897 }
8898 "values" => {
8899 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8900 return Ok(e);
8901 }
8902 let a = self.parse_one_arg_or_default()?;
8903 Ok(Expr {
8904 kind: ExprKind::Values(Box::new(a)),
8905 line,
8906 })
8907 }
8908 "each" => {
8909 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8910 return Ok(e);
8911 }
8912 let a = self.parse_one_arg_or_default()?;
8913 Ok(Expr {
8914 kind: ExprKind::Each(Box::new(a)),
8915 line,
8916 })
8917 }
8918 "fore" | "e" | "ep" => {
8919 if matches!(self.peek(), Token::LBrace) {
8921 let (block, list) = self.parse_block_list()?;
8922 Ok(Expr {
8923 kind: ExprKind::ForEachExpr {
8924 block,
8925 list: Box::new(list),
8926 },
8927 line,
8928 })
8929 } else if self.in_pipe_rhs() {
8930 let is_terminal = matches!(
8933 self.peek(),
8934 Token::Semicolon
8935 | Token::RParen
8936 | Token::Eof
8937 | Token::PipeForward
8938 | Token::RBrace
8939 );
8940 let block = if name == "ep" && is_terminal {
8941 vec![Statement {
8942 label: None,
8943 kind: StmtKind::Expression(Expr {
8944 kind: ExprKind::Say {
8945 handle: None,
8946 args: vec![Expr {
8947 kind: ExprKind::ScalarVar("_".into()),
8948 line,
8949 }],
8950 },
8951 line,
8952 }),
8953 line,
8954 }]
8955 } else {
8956 let expr = self.parse_assign_expr_stop_at_pipe()?;
8957 let expr = Self::lift_bareword_to_topic_call(expr);
8958 vec![Statement {
8959 label: None,
8960 kind: StmtKind::Expression(expr),
8961 line,
8962 }]
8963 };
8964 let list = self.pipe_placeholder_list(line);
8965 Ok(Expr {
8966 kind: ExprKind::ForEachExpr {
8967 block,
8968 list: Box::new(list),
8969 },
8970 line,
8971 })
8972 } else {
8973 let expr = self.parse_assign_expr()?;
8975 let expr = Self::lift_bareword_to_topic_call(expr);
8976 self.expect(&Token::Comma)?;
8977 let list_parts = self.parse_list_until_terminator()?;
8978 let list_expr = if list_parts.len() == 1 {
8979 list_parts.into_iter().next().unwrap()
8980 } else {
8981 Expr {
8982 kind: ExprKind::List(list_parts),
8983 line,
8984 }
8985 };
8986 let block = vec![Statement {
8987 label: None,
8988 kind: StmtKind::Expression(expr),
8989 line,
8990 }];
8991 Ok(Expr {
8992 kind: ExprKind::ForEachExpr {
8993 block,
8994 list: Box::new(list_expr),
8995 },
8996 line,
8997 })
8998 }
8999 }
9000 "rev" => {
9001 let a = if self.in_pipe_rhs()
9006 && matches!(
9007 self.peek(),
9008 Token::Semicolon | Token::RParen | Token::Eof | Token::PipeForward
9009 ) {
9010 self.pipe_placeholder_list(line)
9011 } else {
9012 self.parse_one_arg_or_default()?
9013 };
9014 Ok(Expr {
9015 kind: ExprKind::Rev(Box::new(a)),
9016 line,
9017 })
9018 }
9019 "reverse" => {
9020 if !crate::compat_mode() {
9021 return Err(
9022 self.syntax_err("stryke uses `rev` instead of `reverse` (this is not Perl 5)", line)
9023 );
9024 }
9025 let a = if self.in_pipe_rhs()
9027 && matches!(
9028 self.peek(),
9029 Token::Semicolon
9030 | Token::RBrace
9031 | Token::RParen
9032 | Token::Eof
9033 | Token::PipeForward
9034 ) {
9035 self.pipe_placeholder_list(line)
9036 } else {
9037 self.parse_one_arg()?
9038 };
9039 Ok(Expr {
9040 kind: ExprKind::ReverseExpr(Box::new(a)),
9041 line,
9042 })
9043 }
9044 "reversed" | "rv" => {
9045 let a = if self.in_pipe_rhs()
9047 && matches!(
9048 self.peek(),
9049 Token::Semicolon
9050 | Token::RBrace
9051 | Token::RParen
9052 | Token::Eof
9053 | Token::PipeForward
9054 ) {
9055 self.pipe_placeholder_list(line)
9056 } else {
9057 self.parse_one_arg()?
9058 };
9059 Ok(Expr {
9060 kind: ExprKind::Rev(Box::new(a)),
9061 line,
9062 })
9063 }
9064 "join" => {
9065 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9066 return Ok(e);
9067 }
9068 let args = self.parse_builtin_args()?;
9069 if args.is_empty() {
9070 return Err(self.syntax_err("join requires separator and list", line));
9071 }
9072 if args.len() < 2 && !self.in_pipe_rhs() {
9074 return Err(self.syntax_err("join requires separator and list", line));
9075 }
9076 Ok(Expr {
9077 kind: ExprKind::JoinExpr {
9078 separator: Box::new(args[0].clone()),
9079 list: Box::new(Expr {
9080 kind: ExprKind::List(args[1..].to_vec()),
9081 line,
9082 }),
9083 },
9084 line,
9085 })
9086 }
9087 "split" => {
9088 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9089 return Ok(e);
9090 }
9091 let args = self.parse_builtin_args()?;
9092 let pattern = args.first().cloned().unwrap_or(Expr {
9093 kind: ExprKind::String(" ".into()),
9094 line,
9095 });
9096 let string = args.get(1).cloned().unwrap_or(Expr {
9097 kind: ExprKind::ScalarVar("_".into()),
9098 line,
9099 });
9100 let limit = args.get(2).cloned().map(Box::new);
9101 Ok(Expr {
9102 kind: ExprKind::SplitExpr {
9103 pattern: Box::new(pattern),
9104 string: Box::new(string),
9105 limit,
9106 },
9107 line,
9108 })
9109 }
9110 "substr" => {
9111 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9112 return Ok(e);
9113 }
9114 let args = self.parse_builtin_args()?;
9115 Ok(Expr {
9116 kind: ExprKind::Substr {
9117 string: Box::new(args[0].clone()),
9118 offset: Box::new(args[1].clone()),
9119 length: args.get(2).cloned().map(Box::new),
9120 replacement: args.get(3).cloned().map(Box::new),
9121 },
9122 line,
9123 })
9124 }
9125 "index" => {
9126 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9127 return Ok(e);
9128 }
9129 let args = self.parse_builtin_args()?;
9130 Ok(Expr {
9131 kind: ExprKind::Index {
9132 string: Box::new(args[0].clone()),
9133 substr: Box::new(args[1].clone()),
9134 position: args.get(2).cloned().map(Box::new),
9135 },
9136 line,
9137 })
9138 }
9139 "rindex" => {
9140 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9141 return Ok(e);
9142 }
9143 let args = self.parse_builtin_args()?;
9144 Ok(Expr {
9145 kind: ExprKind::Rindex {
9146 string: Box::new(args[0].clone()),
9147 substr: Box::new(args[1].clone()),
9148 position: args.get(2).cloned().map(Box::new),
9149 },
9150 line,
9151 })
9152 }
9153 "sprintf" => {
9154 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9155 return Ok(e);
9156 }
9157 let args = self.parse_builtin_args()?;
9158 let (first, rest) = args
9159 .split_first()
9160 .ok_or_else(|| self.syntax_err("sprintf requires format", line))?;
9161 Ok(Expr {
9162 kind: ExprKind::Sprintf {
9163 format: Box::new(first.clone()),
9164 args: rest.to_vec(),
9165 },
9166 line,
9167 })
9168 }
9169 "map" | "flat_map" | "maps" | "flat_maps" => {
9170 let flatten_array_refs = matches!(name.as_str(), "flat_map" | "flat_maps");
9171 let stream = matches!(name.as_str(), "maps" | "flat_maps");
9172 if matches!(self.peek(), Token::LBrace) {
9173 let (block, list) = self.parse_block_list()?;
9174 Ok(Expr {
9175 kind: ExprKind::MapExpr {
9176 block,
9177 list: Box::new(list),
9178 flatten_array_refs,
9179 stream,
9180 },
9181 line,
9182 })
9183 } else {
9184 let expr = self.parse_assign_expr_stop_at_pipe()?;
9185 let expr = Self::lift_bareword_to_topic_call(expr);
9188 let list_expr = if self.in_pipe_rhs()
9189 && matches!(
9190 self.peek(),
9191 Token::Semicolon
9192 | Token::RBrace
9193 | Token::RParen
9194 | Token::Eof
9195 | Token::PipeForward
9196 ) {
9197 self.pipe_placeholder_list(line)
9198 } else {
9199 self.expect(&Token::Comma)?;
9200 let list_parts = self.parse_list_until_terminator()?;
9201 if list_parts.len() == 1 {
9202 list_parts.into_iter().next().unwrap()
9203 } else {
9204 Expr {
9205 kind: ExprKind::List(list_parts),
9206 line,
9207 }
9208 }
9209 };
9210 Ok(Expr {
9211 kind: ExprKind::MapExprComma {
9212 expr: Box::new(expr),
9213 list: Box::new(list_expr),
9214 flatten_array_refs,
9215 stream,
9216 },
9217 line,
9218 })
9219 }
9220 }
9221 "cond" => {
9222 if crate::compat_mode() {
9223 return Err(self.syntax_err(
9224 "`cond` is a stryke extension (disabled by --compat)",
9225 line,
9226 ));
9227 }
9228 self.parse_cond_expr(line)
9229 }
9230 "match" => {
9231 if crate::compat_mode() {
9232 return Err(self.syntax_err(
9233 "algebraic `match` is a stryke extension (disabled by --compat)",
9234 line,
9235 ));
9236 }
9237 self.parse_algebraic_match_expr(line)
9238 }
9239 "grep" | "greps" | "filter" | "fi" | "find_all" => {
9240 let keyword = match name.as_str() {
9241 "grep" => crate::ast::GrepBuiltinKeyword::Grep,
9242 "greps" => crate::ast::GrepBuiltinKeyword::Greps,
9243 "filter" | "fi" => crate::ast::GrepBuiltinKeyword::Filter,
9244 "find_all" => crate::ast::GrepBuiltinKeyword::FindAll,
9245 _ => unreachable!(),
9246 };
9247 if matches!(self.peek(), Token::LBrace) {
9248 let (block, list) = self.parse_block_list()?;
9249 Ok(Expr {
9250 kind: ExprKind::GrepExpr {
9251 block,
9252 list: Box::new(list),
9253 keyword,
9254 },
9255 line,
9256 })
9257 } else {
9258 let expr = self.parse_assign_expr_stop_at_pipe()?;
9259 if self.in_pipe_rhs()
9260 && matches!(
9261 self.peek(),
9262 Token::Semicolon
9263 | Token::RBrace
9264 | Token::RParen
9265 | Token::Eof
9266 | Token::PipeForward
9267 )
9268 {
9269 let list = self.pipe_placeholder_list(line);
9274 let topic = Expr {
9275 kind: ExprKind::ScalarVar("_".into()),
9276 line,
9277 };
9278 let test = match &expr.kind {
9279 ExprKind::Integer(_) | ExprKind::Float(_) => Expr {
9280 kind: ExprKind::BinOp {
9281 op: BinOp::NumEq,
9282 left: Box::new(topic),
9283 right: Box::new(expr),
9284 },
9285 line,
9286 },
9287 ExprKind::String(_) | ExprKind::InterpolatedString(_) => Expr {
9288 kind: ExprKind::BinOp {
9289 op: BinOp::StrEq,
9290 left: Box::new(topic),
9291 right: Box::new(expr),
9292 },
9293 line,
9294 },
9295 ExprKind::Regex { .. } => Expr {
9296 kind: ExprKind::BinOp {
9297 op: BinOp::BindMatch,
9298 left: Box::new(topic),
9299 right: Box::new(expr),
9300 },
9301 line,
9302 },
9303 _ => {
9304 Self::lift_bareword_to_topic_call(expr)
9306 }
9307 };
9308 let block = vec![Statement {
9309 label: None,
9310 kind: StmtKind::Expression(test),
9311 line,
9312 }];
9313 Ok(Expr {
9314 kind: ExprKind::GrepExpr {
9315 block,
9316 list: Box::new(list),
9317 keyword,
9318 },
9319 line,
9320 })
9321 } else {
9322 let expr = Self::lift_bareword_to_topic_call(expr);
9323 self.expect(&Token::Comma)?;
9324 let list_parts = self.parse_list_until_terminator()?;
9325 let list_expr = if list_parts.len() == 1 {
9326 list_parts.into_iter().next().unwrap()
9327 } else {
9328 Expr {
9329 kind: ExprKind::List(list_parts),
9330 line,
9331 }
9332 };
9333 Ok(Expr {
9334 kind: ExprKind::GrepExprComma {
9335 expr: Box::new(expr),
9336 list: Box::new(list_expr),
9337 keyword,
9338 },
9339 line,
9340 })
9341 }
9342 }
9343 }
9344 "sort" => {
9345 use crate::ast::SortComparator;
9346 if matches!(self.peek(), Token::LBrace) {
9347 let block = self.parse_block()?;
9348 let _ = self.eat(&Token::Comma);
9349 let list = if self.in_pipe_rhs()
9350 && matches!(
9351 self.peek(),
9352 Token::Semicolon
9353 | Token::RBrace
9354 | Token::RParen
9355 | Token::Eof
9356 | Token::PipeForward
9357 ) {
9358 self.pipe_placeholder_list(line)
9359 } else {
9360 self.parse_expression()?
9361 };
9362 Ok(Expr {
9363 kind: ExprKind::SortExpr {
9364 cmp: Some(SortComparator::Block(block)),
9365 list: Box::new(list),
9366 },
9367 line,
9368 })
9369 } else if matches!(self.peek(), Token::ScalarVar(ref v) if v == "a" || v == "b") {
9370 let block = self.parse_block_or_bareword_cmp_block()?;
9372 let _ = self.eat(&Token::Comma);
9373 let list = if self.in_pipe_rhs()
9374 && matches!(
9375 self.peek(),
9376 Token::Semicolon
9377 | Token::RBrace
9378 | Token::RParen
9379 | Token::Eof
9380 | Token::PipeForward
9381 ) {
9382 self.pipe_placeholder_list(line)
9383 } else {
9384 self.parse_expression()?
9385 };
9386 Ok(Expr {
9387 kind: ExprKind::SortExpr {
9388 cmp: Some(SortComparator::Block(block)),
9389 list: Box::new(list),
9390 },
9391 line,
9392 })
9393 } else if matches!(self.peek(), Token::ScalarVar(_)) {
9394 self.suppress_indirect_paren_call =
9396 self.suppress_indirect_paren_call.saturating_add(1);
9397 let code = self.parse_assign_expr()?;
9398 self.suppress_indirect_paren_call =
9399 self.suppress_indirect_paren_call.saturating_sub(1);
9400 let list = if matches!(self.peek(), Token::LParen) {
9401 self.advance();
9402 let e = self.parse_expression()?;
9403 self.expect(&Token::RParen)?;
9404 e
9405 } else {
9406 self.parse_expression()?
9407 };
9408 Ok(Expr {
9409 kind: ExprKind::SortExpr {
9410 cmp: Some(SortComparator::Code(Box::new(code))),
9411 list: Box::new(list),
9412 },
9413 line,
9414 })
9415 } else if matches!(self.peek(), Token::Ident(ref name) if !Self::is_known_bareword(name))
9416 {
9417 let block = self.parse_block_or_bareword_cmp_block()?;
9419 let _ = self.eat(&Token::Comma);
9420 let list = if self.in_pipe_rhs()
9421 && matches!(
9422 self.peek(),
9423 Token::Semicolon
9424 | Token::RBrace
9425 | Token::RParen
9426 | Token::Eof
9427 | Token::PipeForward
9428 ) {
9429 self.pipe_placeholder_list(line)
9430 } else {
9431 self.parse_expression()?
9432 };
9433 Ok(Expr {
9434 kind: ExprKind::SortExpr {
9435 cmp: Some(SortComparator::Block(block)),
9436 list: Box::new(list),
9437 },
9438 line,
9439 })
9440 } else {
9441 let list = if self.in_pipe_rhs()
9444 && matches!(
9445 self.peek(),
9446 Token::Semicolon
9447 | Token::RBrace
9448 | Token::RParen
9449 | Token::Eof
9450 | Token::PipeForward
9451 ) {
9452 self.pipe_placeholder_list(line)
9453 } else {
9454 self.parse_expression()?
9455 };
9456 Ok(Expr {
9457 kind: ExprKind::SortExpr {
9458 cmp: None,
9459 list: Box::new(list),
9460 },
9461 line,
9462 })
9463 }
9464 }
9465 "reduce" | "fold" | "inject" => {
9466 let (block, list) = self.parse_block_list()?;
9467 Ok(Expr {
9468 kind: ExprKind::ReduceExpr {
9469 block,
9470 list: Box::new(list),
9471 },
9472 line,
9473 })
9474 }
9475 "pmap" => {
9477 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
9478 Ok(Expr {
9479 kind: ExprKind::PMapExpr {
9480 block,
9481 list: Box::new(list),
9482 progress: progress.map(Box::new),
9483 flat_outputs: false,
9484 on_cluster: None,
9485 stream: false,
9486 },
9487 line,
9488 })
9489 }
9490 "pmap_on" => {
9491 let (cluster, block, list, progress) =
9492 self.parse_cluster_block_then_list_optional_progress()?;
9493 Ok(Expr {
9494 kind: ExprKind::PMapExpr {
9495 block,
9496 list: Box::new(list),
9497 progress: progress.map(Box::new),
9498 flat_outputs: false,
9499 on_cluster: Some(Box::new(cluster)),
9500 stream: false,
9501 },
9502 line,
9503 })
9504 }
9505 "pflat_map" => {
9506 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
9507 Ok(Expr {
9508 kind: ExprKind::PMapExpr {
9509 block,
9510 list: Box::new(list),
9511 progress: progress.map(Box::new),
9512 flat_outputs: true,
9513 on_cluster: None,
9514 stream: false,
9515 },
9516 line,
9517 })
9518 }
9519 "pflat_map_on" => {
9520 let (cluster, block, list, progress) =
9521 self.parse_cluster_block_then_list_optional_progress()?;
9522 Ok(Expr {
9523 kind: ExprKind::PMapExpr {
9524 block,
9525 list: Box::new(list),
9526 progress: progress.map(Box::new),
9527 flat_outputs: true,
9528 on_cluster: Some(Box::new(cluster)),
9529 stream: false,
9530 },
9531 line,
9532 })
9533 }
9534 "pmaps" => {
9535 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
9536 Ok(Expr {
9537 kind: ExprKind::PMapExpr {
9538 block,
9539 list: Box::new(list),
9540 progress: progress.map(Box::new),
9541 flat_outputs: false,
9542 on_cluster: None,
9543 stream: true,
9544 },
9545 line,
9546 })
9547 }
9548 "pflat_maps" => {
9549 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
9550 Ok(Expr {
9551 kind: ExprKind::PMapExpr {
9552 block,
9553 list: Box::new(list),
9554 progress: progress.map(Box::new),
9555 flat_outputs: true,
9556 on_cluster: None,
9557 stream: true,
9558 },
9559 line,
9560 })
9561 }
9562 "pgreps" => {
9563 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
9564 Ok(Expr {
9565 kind: ExprKind::PGrepExpr {
9566 block,
9567 list: Box::new(list),
9568 progress: progress.map(Box::new),
9569 stream: true,
9570 },
9571 line,
9572 })
9573 }
9574 "pmap_chunked" => {
9575 let chunk_size = self.parse_assign_expr()?;
9576 let block = self.parse_block_or_bareword_block()?;
9577 self.eat(&Token::Comma);
9578 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
9579 Ok(Expr {
9580 kind: ExprKind::PMapChunkedExpr {
9581 chunk_size: Box::new(chunk_size),
9582 block,
9583 list: Box::new(list),
9584 progress: progress.map(Box::new),
9585 },
9586 line,
9587 })
9588 }
9589 "pgrep" => {
9590 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
9591 Ok(Expr {
9592 kind: ExprKind::PGrepExpr {
9593 block,
9594 list: Box::new(list),
9595 progress: progress.map(Box::new),
9596 stream: false,
9597 },
9598 line,
9599 })
9600 }
9601 "pfor" => {
9602 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
9603 Ok(Expr {
9604 kind: ExprKind::PForExpr {
9605 block,
9606 list: Box::new(list),
9607 progress: progress.map(Box::new),
9608 },
9609 line,
9610 })
9611 }
9612 "par_lines" | "par_walk" => {
9613 let args = self.parse_builtin_args()?;
9614 if args.len() < 2 {
9615 return Err(
9616 self.syntax_err(format!("{} requires at least two arguments", name), line)
9617 );
9618 }
9619
9620 if name == "par_lines" {
9621 Ok(Expr {
9622 kind: ExprKind::ParLinesExpr {
9623 path: Box::new(args[0].clone()),
9624 callback: Box::new(args[1].clone()),
9625 progress: None,
9626 },
9627 line,
9628 })
9629 } else {
9630 Ok(Expr {
9631 kind: ExprKind::ParWalkExpr {
9632 path: Box::new(args[0].clone()),
9633 callback: Box::new(args[1].clone()),
9634 progress: None,
9635 },
9636 line,
9637 })
9638 }
9639 }
9640 "pwatch" | "watch" => {
9641 let args = self.parse_builtin_args()?;
9642 if args.len() < 2 {
9643 return Err(
9644 self.syntax_err(format!("{} requires at least two arguments", name), line)
9645 );
9646 }
9647 Ok(Expr {
9648 kind: ExprKind::PwatchExpr {
9649 path: Box::new(args[0].clone()),
9650 callback: Box::new(args[1].clone()),
9651 },
9652 line,
9653 })
9654 }
9655 "fan" => {
9656 let (count, block) = self.parse_fan_count_and_block(line)?;
9662 let progress = self.parse_fan_optional_progress("fan")?;
9663 Ok(Expr {
9664 kind: ExprKind::FanExpr {
9665 count,
9666 block,
9667 progress,
9668 capture: false,
9669 },
9670 line,
9671 })
9672 }
9673 "fan_cap" => {
9674 let (count, block) = self.parse_fan_count_and_block(line)?;
9675 let progress = self.parse_fan_optional_progress("fan_cap")?;
9676 Ok(Expr {
9677 kind: ExprKind::FanExpr {
9678 count,
9679 block,
9680 progress,
9681 capture: true,
9682 },
9683 line,
9684 })
9685 }
9686 "async" => {
9687 if !matches!(self.peek(), Token::LBrace) {
9688 return Err(self.syntax_err("async must be followed by { BLOCK }", line));
9689 }
9690 let block = self.parse_block()?;
9691 Ok(Expr {
9692 kind: ExprKind::AsyncBlock { body: block },
9693 line,
9694 })
9695 }
9696 "spawn" => {
9697 if !matches!(self.peek(), Token::LBrace) {
9698 return Err(self.syntax_err("spawn must be followed by { BLOCK }", line));
9699 }
9700 let block = self.parse_block()?;
9701 Ok(Expr {
9702 kind: ExprKind::SpawnBlock { body: block },
9703 line,
9704 })
9705 }
9706 "trace" => {
9707 if !matches!(self.peek(), Token::LBrace) {
9708 return Err(self.syntax_err("trace must be followed by { BLOCK }", line));
9709 }
9710 let block = self.parse_block()?;
9711 Ok(Expr {
9712 kind: ExprKind::Trace { body: block },
9713 line,
9714 })
9715 }
9716 "timer" => {
9717 let block = self.parse_block_or_bareword_block_no_args()?;
9718 Ok(Expr {
9719 kind: ExprKind::Timer { body: block },
9720 line,
9721 })
9722 }
9723 "bench" => {
9724 let block = self.parse_block_or_bareword_block_no_args()?;
9725 let times = Box::new(self.parse_expression()?);
9726 Ok(Expr {
9727 kind: ExprKind::Bench { body: block, times },
9728 line,
9729 })
9730 }
9731 "spinner" => {
9732 let (message, body) = if matches!(self.peek(), Token::LBrace) {
9734 let body = self.parse_block()?;
9735 (
9736 Box::new(Expr {
9737 kind: ExprKind::String("working".to_string()),
9738 line,
9739 }),
9740 body,
9741 )
9742 } else {
9743 let msg = self.parse_assign_expr()?;
9744 let body = self.parse_block()?;
9745 (Box::new(msg), body)
9746 };
9747 Ok(Expr {
9748 kind: ExprKind::Spinner { message, body },
9749 line,
9750 })
9751 }
9752 "thread" | "t" => {
9753 self.parse_thread_macro(line, false)
9763 }
9764 "retry" => {
9765 let body = if matches!(self.peek(), Token::LBrace) {
9768 self.parse_block()?
9769 } else {
9770 let bw_line = self.peek_line();
9771 let Token::Ident(ref name) = self.peek().clone() else {
9772 return Err(self
9773 .syntax_err("retry: expected block or bareword function name", line));
9774 };
9775 let name = name.clone();
9776 self.advance();
9777 vec![Statement::new(
9778 StmtKind::Expression(Expr {
9779 kind: ExprKind::FuncCall { name, args: vec![] },
9780 line: bw_line,
9781 }),
9782 bw_line,
9783 )]
9784 };
9785 self.eat(&Token::Comma);
9786 match self.peek() {
9787 Token::Ident(ref s) if s == "times" => {
9788 self.advance();
9789 }
9790 _ => {
9791 return Err(self.syntax_err("retry: expected `times =>` after block", line));
9792 }
9793 }
9794 self.expect(&Token::FatArrow)?;
9795 let times = Box::new(self.parse_assign_expr()?);
9796 let mut backoff = RetryBackoff::None;
9797 if self.eat(&Token::Comma) {
9798 match self.peek() {
9799 Token::Ident(ref s) if s == "backoff" => {
9800 self.advance();
9801 }
9802 _ => {
9803 return Err(
9804 self.syntax_err("retry: expected `backoff =>` after comma", line)
9805 );
9806 }
9807 }
9808 self.expect(&Token::FatArrow)?;
9809 let Token::Ident(mode) = self.peek().clone() else {
9810 return Err(self.syntax_err(
9811 "retry: expected backoff mode (none, linear, exponential)",
9812 line,
9813 ));
9814 };
9815 backoff = match mode.as_str() {
9816 "none" => RetryBackoff::None,
9817 "linear" => RetryBackoff::Linear,
9818 "exponential" => RetryBackoff::Exponential,
9819 _ => {
9820 return Err(
9821 self.syntax_err(format!("retry: invalid backoff `{mode}`"), line)
9822 );
9823 }
9824 };
9825 self.advance();
9826 }
9827 Ok(Expr {
9828 kind: ExprKind::RetryBlock {
9829 body,
9830 times,
9831 backoff,
9832 },
9833 line,
9834 })
9835 }
9836 "rate_limit" => {
9837 self.expect(&Token::LParen)?;
9838 let max = Box::new(self.parse_assign_expr()?);
9839 self.expect(&Token::Comma)?;
9840 let window = Box::new(self.parse_assign_expr()?);
9841 self.expect(&Token::RParen)?;
9842 let body = self.parse_block_or_bareword_block_no_args()?;
9843 let slot = self.alloc_rate_limit_slot();
9844 Ok(Expr {
9845 kind: ExprKind::RateLimitBlock {
9846 slot,
9847 max,
9848 window,
9849 body,
9850 },
9851 line,
9852 })
9853 }
9854 "every" => {
9855 let has_paren = self.eat(&Token::LParen);
9858 let interval = Box::new(self.parse_assign_expr()?);
9859 if has_paren {
9860 self.expect(&Token::RParen)?;
9861 }
9862 let body = if matches!(self.peek(), Token::LBrace) {
9863 self.parse_block()?
9864 } else {
9865 let bline = self.peek_line();
9866 let expr = self.parse_assign_expr()?;
9867 vec![Statement::new(StmtKind::Expression(expr), bline)]
9868 };
9869 Ok(Expr {
9870 kind: ExprKind::EveryBlock { interval, body },
9871 line,
9872 })
9873 }
9874 "gen" => {
9875 if !matches!(self.peek(), Token::LBrace) {
9876 return Err(self.syntax_err("gen must be followed by { BLOCK }", line));
9877 }
9878 let body = self.parse_block()?;
9879 Ok(Expr {
9880 kind: ExprKind::GenBlock { body },
9881 line,
9882 })
9883 }
9884 "yield" => {
9885 let e = self.parse_assign_expr()?;
9886 Ok(Expr {
9887 kind: ExprKind::Yield(Box::new(e)),
9888 line,
9889 })
9890 }
9891 "await" => {
9892 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9893 return Ok(e);
9894 }
9895 let a = self.parse_one_arg_or_default()?;
9898 Ok(Expr {
9899 kind: ExprKind::Await(Box::new(a)),
9900 line,
9901 })
9902 }
9903 "slurp" | "cat" | "c" => {
9904 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9905 return Ok(e);
9906 }
9907 let a = self.parse_one_arg_or_default()?;
9908 Ok(Expr {
9909 kind: ExprKind::Slurp(Box::new(a)),
9910 line,
9911 })
9912 }
9913 "capture" => {
9914 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9915 return Ok(e);
9916 }
9917 let a = self.parse_one_arg()?;
9918 Ok(Expr {
9919 kind: ExprKind::Capture(Box::new(a)),
9920 line,
9921 })
9922 }
9923 "fetch_url" => {
9924 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9925 return Ok(e);
9926 }
9927 let a = self.parse_one_arg()?;
9928 Ok(Expr {
9929 kind: ExprKind::FetchUrl(Box::new(a)),
9930 line,
9931 })
9932 }
9933 "pchannel" => {
9934 let capacity = if self.eat(&Token::LParen) {
9935 if matches!(self.peek(), Token::RParen) {
9936 self.advance();
9937 None
9938 } else {
9939 let e = self.parse_expression()?;
9940 self.expect(&Token::RParen)?;
9941 Some(Box::new(e))
9942 }
9943 } else {
9944 None
9945 };
9946 Ok(Expr {
9947 kind: ExprKind::Pchannel { capacity },
9948 line,
9949 })
9950 }
9951 "psort" => {
9952 if matches!(self.peek(), Token::LBrace)
9953 || matches!(self.peek(), Token::ScalarVar(ref v) if v == "a" || v == "b")
9954 || matches!(self.peek(), Token::Ident(ref name) if !Self::is_known_bareword(name))
9955 {
9956 let block = self.parse_block_or_bareword_cmp_block()?;
9957 self.eat(&Token::Comma);
9958 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
9959 Ok(Expr {
9960 kind: ExprKind::PSortExpr {
9961 cmp: Some(block),
9962 list: Box::new(list),
9963 progress: progress.map(Box::new),
9964 },
9965 line,
9966 })
9967 } else {
9968 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
9969 Ok(Expr {
9970 kind: ExprKind::PSortExpr {
9971 cmp: None,
9972 list: Box::new(list),
9973 progress: progress.map(Box::new),
9974 },
9975 line,
9976 })
9977 }
9978 }
9979 "preduce" => {
9980 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
9981 Ok(Expr {
9982 kind: ExprKind::PReduceExpr {
9983 block,
9984 list: Box::new(list),
9985 progress: progress.map(Box::new),
9986 },
9987 line,
9988 })
9989 }
9990 "preduce_init" => {
9991 let (init, block, list, progress) =
9992 self.parse_init_block_then_list_optional_progress()?;
9993 Ok(Expr {
9994 kind: ExprKind::PReduceInitExpr {
9995 init: Box::new(init),
9996 block,
9997 list: Box::new(list),
9998 progress: progress.map(Box::new),
9999 },
10000 line,
10001 })
10002 }
10003 "pmap_reduce" => {
10004 let map_block = self.parse_block_or_bareword_block()?;
10005 let reduce_block = if matches!(self.peek(), Token::LBrace) {
10008 self.parse_block()?
10009 } else {
10010 self.expect(&Token::Comma)?;
10012 self.parse_block_or_bareword_cmp_block()?
10013 };
10014 self.eat(&Token::Comma);
10015 let line = self.peek_line();
10016 if let Token::Ident(ref kw) = self.peek().clone() {
10017 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
10018 self.advance();
10019 self.expect(&Token::FatArrow)?;
10020 let prog = self.parse_assign_expr()?;
10021 return Ok(Expr {
10022 kind: ExprKind::PMapReduceExpr {
10023 map_block,
10024 reduce_block,
10025 list: Box::new(Expr {
10026 kind: ExprKind::List(vec![]),
10027 line,
10028 }),
10029 progress: Some(Box::new(prog)),
10030 },
10031 line,
10032 });
10033 }
10034 }
10035 if matches!(
10036 self.peek(),
10037 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof
10038 ) {
10039 return Ok(Expr {
10040 kind: ExprKind::PMapReduceExpr {
10041 map_block,
10042 reduce_block,
10043 list: Box::new(Expr {
10044 kind: ExprKind::List(vec![]),
10045 line,
10046 }),
10047 progress: None,
10048 },
10049 line,
10050 });
10051 }
10052 let mut parts = vec![self.parse_assign_expr()?];
10053 loop {
10054 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
10055 break;
10056 }
10057 if matches!(
10058 self.peek(),
10059 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof
10060 ) {
10061 break;
10062 }
10063 if let Token::Ident(ref kw) = self.peek().clone() {
10064 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
10065 self.advance();
10066 self.expect(&Token::FatArrow)?;
10067 let prog = self.parse_assign_expr()?;
10068 return Ok(Expr {
10069 kind: ExprKind::PMapReduceExpr {
10070 map_block,
10071 reduce_block,
10072 list: Box::new(merge_expr_list(parts)),
10073 progress: Some(Box::new(prog)),
10074 },
10075 line,
10076 });
10077 }
10078 }
10079 parts.push(self.parse_assign_expr()?);
10080 }
10081 Ok(Expr {
10082 kind: ExprKind::PMapReduceExpr {
10083 map_block,
10084 reduce_block,
10085 list: Box::new(merge_expr_list(parts)),
10086 progress: None,
10087 },
10088 line,
10089 })
10090 }
10091 "puniq" => {
10092 if self.pipe_supplies_slurped_list_operand() {
10093 return Ok(Expr {
10094 kind: ExprKind::FuncCall {
10095 name: "puniq".to_string(),
10096 args: vec![],
10097 },
10098 line,
10099 });
10100 }
10101 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
10102 let mut args = vec![list];
10103 if let Some(p) = progress {
10104 args.push(p);
10105 }
10106 Ok(Expr {
10107 kind: ExprKind::FuncCall {
10108 name: "puniq".to_string(),
10109 args,
10110 },
10111 line,
10112 })
10113 }
10114 "pfirst" => {
10115 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
10116 let cr = Expr {
10117 kind: ExprKind::CodeRef {
10118 params: vec![],
10119 body: block,
10120 },
10121 line,
10122 };
10123 let mut args = vec![cr, list];
10124 if let Some(p) = progress {
10125 args.push(p);
10126 }
10127 Ok(Expr {
10128 kind: ExprKind::FuncCall {
10129 name: "pfirst".to_string(),
10130 args,
10131 },
10132 line,
10133 })
10134 }
10135 "pany" => {
10136 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
10137 let cr = Expr {
10138 kind: ExprKind::CodeRef {
10139 params: vec![],
10140 body: block,
10141 },
10142 line,
10143 };
10144 let mut args = vec![cr, list];
10145 if let Some(p) = progress {
10146 args.push(p);
10147 }
10148 Ok(Expr {
10149 kind: ExprKind::FuncCall {
10150 name: "pany".to_string(),
10151 args,
10152 },
10153 line,
10154 })
10155 }
10156 "uniq" | "distinct" => {
10157 if self.pipe_supplies_slurped_list_operand() {
10158 return Ok(Expr {
10159 kind: ExprKind::FuncCall {
10160 name: name.clone(),
10161 args: vec![],
10162 },
10163 line,
10164 });
10165 }
10166 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
10167 if progress.is_some() {
10168 return Err(self.syntax_err(
10169 "`progress =>` is not supported for uniq (use puniq for parallel + progress)",
10170 line,
10171 ));
10172 }
10173 Ok(Expr {
10174 kind: ExprKind::FuncCall {
10175 name: name.clone(),
10176 args: vec![list],
10177 },
10178 line,
10179 })
10180 }
10181 "flatten" => {
10182 if self.pipe_supplies_slurped_list_operand() {
10183 return Ok(Expr {
10184 kind: ExprKind::FuncCall {
10185 name: "flatten".to_string(),
10186 args: vec![],
10187 },
10188 line,
10189 });
10190 }
10191 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
10192 if progress.is_some() {
10193 return Err(self.syntax_err("`progress =>` is not supported for flatten", line));
10194 }
10195 Ok(Expr {
10196 kind: ExprKind::FuncCall {
10197 name: "flatten".to_string(),
10198 args: vec![list],
10199 },
10200 line,
10201 })
10202 }
10203 "set" => {
10204 if self.pipe_supplies_slurped_list_operand() {
10205 return Ok(Expr {
10206 kind: ExprKind::FuncCall {
10207 name: "set".to_string(),
10208 args: vec![],
10209 },
10210 line,
10211 });
10212 }
10213 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
10214 if progress.is_some() {
10215 return Err(self.syntax_err("`progress =>` is not supported for set", line));
10216 }
10217 Ok(Expr {
10218 kind: ExprKind::FuncCall {
10219 name: "set".to_string(),
10220 args: vec![list],
10221 },
10222 line,
10223 })
10224 }
10225 "size" => {
10229 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10230 return Ok(e);
10231 }
10232 if self.pipe_supplies_slurped_list_operand() {
10233 return Ok(Expr {
10234 kind: ExprKind::FuncCall {
10235 name: "size".to_string(),
10236 args: vec![],
10237 },
10238 line,
10239 });
10240 }
10241 let a = self.parse_one_arg_or_default()?;
10242 Ok(Expr {
10243 kind: ExprKind::FuncCall {
10244 name: "size".to_string(),
10245 args: vec![a],
10246 },
10247 line,
10248 })
10249 }
10250 "list_count" | "list_size" | "count" | "len" | "cnt" => {
10251 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10252 return Ok(e);
10253 }
10254 if self.pipe_supplies_slurped_list_operand() {
10255 return Ok(Expr {
10256 kind: ExprKind::FuncCall {
10257 name: name.clone(),
10258 args: vec![],
10259 },
10260 line,
10261 });
10262 }
10263 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
10264 if progress.is_some() {
10265 return Err(self.syntax_err(
10266 "`progress =>` is not supported for list_count / list_size / count / cnt",
10267 line,
10268 ));
10269 }
10270 Ok(Expr {
10271 kind: ExprKind::FuncCall {
10272 name: name.clone(),
10273 args: vec![list],
10274 },
10275 line,
10276 })
10277 }
10278 "shuffle" | "shuffled" => {
10279 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10280 return Ok(e);
10281 }
10282 if self.pipe_supplies_slurped_list_operand() {
10283 return Ok(Expr {
10284 kind: ExprKind::FuncCall {
10285 name: "shuffle".to_string(),
10286 args: vec![],
10287 },
10288 line,
10289 });
10290 }
10291 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
10292 if progress.is_some() {
10293 return Err(self.syntax_err("`progress =>` is not supported for shuffle", line));
10294 }
10295 Ok(Expr {
10296 kind: ExprKind::FuncCall {
10297 name: "shuffle".to_string(),
10298 args: vec![list],
10299 },
10300 line,
10301 })
10302 }
10303 "chunked" => {
10304 let mut parts = Vec::new();
10305 if self.eat(&Token::LParen) {
10306 if !matches!(self.peek(), Token::RParen) {
10307 parts.push(self.parse_assign_expr()?);
10308 while self.eat(&Token::Comma) {
10309 if matches!(self.peek(), Token::RParen) {
10310 break;
10311 }
10312 parts.push(self.parse_assign_expr()?);
10313 }
10314 }
10315 self.expect(&Token::RParen)?;
10316 } else {
10317 parts.push(self.parse_assign_expr_stop_at_pipe()?);
10321 loop {
10322 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
10323 break;
10324 }
10325 if matches!(
10326 self.peek(),
10327 Token::Semicolon
10328 | Token::RBrace
10329 | Token::RParen
10330 | Token::Eof
10331 | Token::PipeForward
10332 ) {
10333 break;
10334 }
10335 if self.peek_is_postfix_stmt_modifier_keyword() {
10336 break;
10337 }
10338 parts.push(self.parse_assign_expr_stop_at_pipe()?);
10339 }
10340 }
10341 if parts.len() == 1 {
10342 let n = parts.pop().unwrap();
10343 return Ok(Expr {
10344 kind: ExprKind::FuncCall {
10345 name: "chunked".to_string(),
10346 args: vec![n],
10347 },
10348 line,
10349 });
10350 }
10351 if parts.is_empty() {
10352 return Ok(Expr {
10353 kind: ExprKind::FuncCall {
10354 name: "chunked".to_string(),
10355 args: parts,
10356 },
10357 line,
10358 });
10359 }
10360 if parts.len() == 2 {
10361 let n = parts.pop().unwrap();
10362 let list = parts.pop().unwrap();
10363 return Ok(Expr {
10364 kind: ExprKind::FuncCall {
10365 name: "chunked".to_string(),
10366 args: vec![list, n],
10367 },
10368 line,
10369 });
10370 }
10371 Err(self.syntax_err(
10372 "chunked: use LIST |> chunked(N) or chunked((1,2,3), 2)",
10373 line,
10374 ))
10375 }
10376 "windowed" => {
10377 let mut parts = Vec::new();
10378 if self.eat(&Token::LParen) {
10379 if !matches!(self.peek(), Token::RParen) {
10380 parts.push(self.parse_assign_expr()?);
10381 while self.eat(&Token::Comma) {
10382 if matches!(self.peek(), Token::RParen) {
10383 break;
10384 }
10385 parts.push(self.parse_assign_expr()?);
10386 }
10387 }
10388 self.expect(&Token::RParen)?;
10389 } else {
10390 parts.push(self.parse_assign_expr_stop_at_pipe()?);
10393 loop {
10394 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
10395 break;
10396 }
10397 if matches!(
10398 self.peek(),
10399 Token::Semicolon
10400 | Token::RBrace
10401 | Token::RParen
10402 | Token::Eof
10403 | Token::PipeForward
10404 ) {
10405 break;
10406 }
10407 if self.peek_is_postfix_stmt_modifier_keyword() {
10408 break;
10409 }
10410 parts.push(self.parse_assign_expr_stop_at_pipe()?);
10411 }
10412 }
10413 if parts.len() == 1 {
10414 let n = parts.pop().unwrap();
10415 return Ok(Expr {
10416 kind: ExprKind::FuncCall {
10417 name: "windowed".to_string(),
10418 args: vec![n],
10419 },
10420 line,
10421 });
10422 }
10423 if parts.is_empty() {
10424 return Ok(Expr {
10425 kind: ExprKind::FuncCall {
10426 name: "windowed".to_string(),
10427 args: parts,
10428 },
10429 line,
10430 });
10431 }
10432 if parts.len() == 2 {
10433 let n = parts.pop().unwrap();
10434 let list = parts.pop().unwrap();
10435 return Ok(Expr {
10436 kind: ExprKind::FuncCall {
10437 name: "windowed".to_string(),
10438 args: vec![list, n],
10439 },
10440 line,
10441 });
10442 }
10443 Err(self.syntax_err(
10444 "windowed: use LIST |> windowed(N) or windowed((1,2,3), 2)",
10445 line,
10446 ))
10447 }
10448 "any" | "all" | "none" => {
10449 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
10450 if progress.is_some() {
10451 return Err(self.syntax_err(
10452 "`progress =>` is not supported for any/all/none (use pany for parallel + progress)",
10453 line,
10454 ));
10455 }
10456 let cr = Expr {
10457 kind: ExprKind::CodeRef {
10458 params: vec![],
10459 body: block,
10460 },
10461 line,
10462 };
10463 Ok(Expr {
10464 kind: ExprKind::FuncCall {
10465 name: name.clone(),
10466 args: vec![cr, list],
10467 },
10468 line,
10469 })
10470 }
10471 "first" | "detect" | "find" => {
10473 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
10474 if progress.is_some() {
10475 return Err(self.syntax_err(
10476 "`progress =>` is not supported for first/detect/find (use pfirst for parallel + progress)",
10477 line,
10478 ));
10479 }
10480 let cr = Expr {
10481 kind: ExprKind::CodeRef {
10482 params: vec![],
10483 body: block,
10484 },
10485 line,
10486 };
10487 Ok(Expr {
10488 kind: ExprKind::FuncCall {
10489 name: "first".to_string(),
10490 args: vec![cr, list],
10491 },
10492 line,
10493 })
10494 }
10495 "take_while" | "drop_while" | "skip_while" | "reject" | "tap" | "peek"
10496 | "partition" | "min_by" | "max_by" | "zip_with" | "count_by" => {
10497 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
10498 if progress.is_some() {
10499 return Err(
10500 self.syntax_err(format!("`progress =>` is not supported for {name}"), line)
10501 );
10502 }
10503 let cr = Expr {
10504 kind: ExprKind::CodeRef {
10505 params: vec![],
10506 body: block,
10507 },
10508 line,
10509 };
10510 Ok(Expr {
10511 kind: ExprKind::FuncCall {
10512 name: name.to_string(),
10513 args: vec![cr, list],
10514 },
10515 line,
10516 })
10517 }
10518 "group_by" | "chunk_by" => {
10519 if matches!(self.peek(), Token::LBrace) {
10520 let (block, list) = self.parse_block_list()?;
10521 let cr = Expr {
10522 kind: ExprKind::CodeRef {
10523 params: vec![],
10524 body: block,
10525 },
10526 line,
10527 };
10528 Ok(Expr {
10529 kind: ExprKind::FuncCall {
10530 name: name.to_string(),
10531 args: vec![cr, list],
10532 },
10533 line,
10534 })
10535 } else {
10536 let key_expr = self.parse_assign_expr()?;
10537 self.expect(&Token::Comma)?;
10538 let list_parts = self.parse_list_until_terminator()?;
10539 let list_expr = if list_parts.len() == 1 {
10540 list_parts.into_iter().next().unwrap()
10541 } else {
10542 Expr {
10543 kind: ExprKind::List(list_parts),
10544 line,
10545 }
10546 };
10547 Ok(Expr {
10548 kind: ExprKind::FuncCall {
10549 name: name.to_string(),
10550 args: vec![key_expr, list_expr],
10551 },
10552 line,
10553 })
10554 }
10555 }
10556 "with_index" => {
10557 if self.pipe_supplies_slurped_list_operand() {
10558 return Ok(Expr {
10559 kind: ExprKind::FuncCall {
10560 name: "with_index".to_string(),
10561 args: vec![],
10562 },
10563 line,
10564 });
10565 }
10566 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
10567 if progress.is_some() {
10568 return Err(
10569 self.syntax_err("`progress =>` is not supported for with_index", line)
10570 );
10571 }
10572 Ok(Expr {
10573 kind: ExprKind::FuncCall {
10574 name: "with_index".to_string(),
10575 args: vec![list],
10576 },
10577 line,
10578 })
10579 }
10580 "pcache" => {
10581 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
10582 Ok(Expr {
10583 kind: ExprKind::PcacheExpr {
10584 block,
10585 list: Box::new(list),
10586 progress: progress.map(Box::new),
10587 },
10588 line,
10589 })
10590 }
10591 "pselect" => {
10592 let paren = self.eat(&Token::LParen);
10593 let (receivers, timeout) = self.parse_comma_expr_list_with_timeout_tail(paren)?;
10594 if paren {
10595 self.expect(&Token::RParen)?;
10596 }
10597 if receivers.is_empty() {
10598 return Err(self.syntax_err("pselect needs at least one receiver", line));
10599 }
10600 Ok(Expr {
10601 kind: ExprKind::PselectExpr {
10602 receivers,
10603 timeout: timeout.map(Box::new),
10604 },
10605 line,
10606 })
10607 }
10608 "open" => {
10609 let paren = matches!(self.peek(), Token::LParen);
10610 if paren {
10611 self.advance();
10612 }
10613 if matches!(self.peek(), Token::Ident(ref s) if s == "my") {
10614 self.advance();
10615 let name = self.parse_scalar_var_name()?;
10616 self.expect(&Token::Comma)?;
10617 let mode = self.parse_assign_expr()?;
10618 let file = if self.eat(&Token::Comma) {
10619 Some(self.parse_assign_expr()?)
10620 } else {
10621 None
10622 };
10623 if paren {
10624 self.expect(&Token::RParen)?;
10625 }
10626 Ok(Expr {
10627 kind: ExprKind::Open {
10628 handle: Box::new(Expr {
10629 kind: ExprKind::OpenMyHandle { name },
10630 line,
10631 }),
10632 mode: Box::new(mode),
10633 file: file.map(Box::new),
10634 },
10635 line,
10636 })
10637 } else {
10638 let args = if paren {
10639 self.parse_arg_list()?
10640 } else {
10641 self.parse_list_until_terminator()?
10642 };
10643 if paren {
10644 self.expect(&Token::RParen)?;
10645 }
10646 if args.len() < 2 {
10647 return Err(self.syntax_err("open requires at least 2 arguments", line));
10648 }
10649 Ok(Expr {
10650 kind: ExprKind::Open {
10651 handle: Box::new(args[0].clone()),
10652 mode: Box::new(args[1].clone()),
10653 file: args.get(2).cloned().map(Box::new),
10654 },
10655 line,
10656 })
10657 }
10658 }
10659 "close" => {
10660 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10661 return Ok(e);
10662 }
10663 let a = self.parse_one_arg_or_default()?;
10664 Ok(Expr {
10665 kind: ExprKind::Close(Box::new(a)),
10666 line,
10667 })
10668 }
10669 "opendir" => {
10670 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10671 return Ok(e);
10672 }
10673 let args = self.parse_builtin_args()?;
10674 if args.len() != 2 {
10675 return Err(self.syntax_err("opendir requires two arguments", line));
10676 }
10677 Ok(Expr {
10678 kind: ExprKind::Opendir {
10679 handle: Box::new(args[0].clone()),
10680 path: Box::new(args[1].clone()),
10681 },
10682 line,
10683 })
10684 }
10685 "readdir" => {
10686 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10687 return Ok(e);
10688 }
10689 let a = self.parse_one_arg()?;
10690 Ok(Expr {
10691 kind: ExprKind::Readdir(Box::new(a)),
10692 line,
10693 })
10694 }
10695 "closedir" => {
10696 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10697 return Ok(e);
10698 }
10699 let a = self.parse_one_arg()?;
10700 Ok(Expr {
10701 kind: ExprKind::Closedir(Box::new(a)),
10702 line,
10703 })
10704 }
10705 "rewinddir" => {
10706 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10707 return Ok(e);
10708 }
10709 let a = self.parse_one_arg()?;
10710 Ok(Expr {
10711 kind: ExprKind::Rewinddir(Box::new(a)),
10712 line,
10713 })
10714 }
10715 "telldir" => {
10716 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10717 return Ok(e);
10718 }
10719 let a = self.parse_one_arg()?;
10720 Ok(Expr {
10721 kind: ExprKind::Telldir(Box::new(a)),
10722 line,
10723 })
10724 }
10725 "seekdir" => {
10726 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10727 return Ok(e);
10728 }
10729 let args = self.parse_builtin_args()?;
10730 if args.len() != 2 {
10731 return Err(self.syntax_err("seekdir requires two arguments", line));
10732 }
10733 Ok(Expr {
10734 kind: ExprKind::Seekdir {
10735 handle: Box::new(args[0].clone()),
10736 position: Box::new(args[1].clone()),
10737 },
10738 line,
10739 })
10740 }
10741 "eof" => {
10742 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10743 return Ok(e);
10744 }
10745 if matches!(self.peek(), Token::LParen) {
10746 self.advance();
10747 if matches!(self.peek(), Token::RParen) {
10748 self.advance();
10749 Ok(Expr {
10750 kind: ExprKind::Eof(None),
10751 line,
10752 })
10753 } else {
10754 let a = self.parse_expression()?;
10755 self.expect(&Token::RParen)?;
10756 Ok(Expr {
10757 kind: ExprKind::Eof(Some(Box::new(a))),
10758 line,
10759 })
10760 }
10761 } else {
10762 Ok(Expr {
10763 kind: ExprKind::Eof(None),
10764 line,
10765 })
10766 }
10767 }
10768 "system" => {
10769 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10770 return Ok(e);
10771 }
10772 let args = self.parse_builtin_args()?;
10773 Ok(Expr {
10774 kind: ExprKind::System(args),
10775 line,
10776 })
10777 }
10778 "exec" => {
10779 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10780 return Ok(e);
10781 }
10782 let args = self.parse_builtin_args()?;
10783 Ok(Expr {
10784 kind: ExprKind::Exec(args),
10785 line,
10786 })
10787 }
10788 "eval" => {
10789 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10790 return Ok(e);
10791 }
10792 let a = if matches!(self.peek(), Token::LBrace) {
10793 let block = self.parse_block()?;
10794 Expr {
10795 kind: ExprKind::CodeRef {
10796 params: vec![],
10797 body: block,
10798 },
10799 line,
10800 }
10801 } else {
10802 self.parse_one_arg_or_default()?
10803 };
10804 Ok(Expr {
10805 kind: ExprKind::Eval(Box::new(a)),
10806 line,
10807 })
10808 }
10809 "do" => {
10810 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10811 return Ok(e);
10812 }
10813 let a = self.parse_one_arg()?;
10814 Ok(Expr {
10815 kind: ExprKind::Do(Box::new(a)),
10816 line,
10817 })
10818 }
10819 "require" => {
10820 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10821 return Ok(e);
10822 }
10823 let a = self.parse_one_arg()?;
10824 Ok(Expr {
10825 kind: ExprKind::Require(Box::new(a)),
10826 line,
10827 })
10828 }
10829 "exit" => {
10830 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10831 return Ok(e);
10832 }
10833 if matches!(
10834 self.peek(),
10835 Token::Semicolon | Token::RBrace | Token::Eof | Token::PipeForward
10836 ) {
10837 Ok(Expr {
10838 kind: ExprKind::Exit(None),
10839 line,
10840 })
10841 } else {
10842 let a = self.parse_one_arg()?;
10843 Ok(Expr {
10844 kind: ExprKind::Exit(Some(Box::new(a))),
10845 line,
10846 })
10847 }
10848 }
10849 "chdir" => {
10850 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10851 return Ok(e);
10852 }
10853 let a = self.parse_one_arg_or_default()?;
10854 Ok(Expr {
10855 kind: ExprKind::Chdir(Box::new(a)),
10856 line,
10857 })
10858 }
10859 "mkdir" => {
10860 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10861 return Ok(e);
10862 }
10863 let args = self.parse_builtin_args()?;
10864 Ok(Expr {
10865 kind: ExprKind::Mkdir {
10866 path: Box::new(args[0].clone()),
10867 mode: args.get(1).cloned().map(Box::new),
10868 },
10869 line,
10870 })
10871 }
10872 "unlink" | "rm" => {
10873 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10874 return Ok(e);
10875 }
10876 let args = self.parse_builtin_args()?;
10877 Ok(Expr {
10878 kind: ExprKind::Unlink(args),
10879 line,
10880 })
10881 }
10882 "rename" => {
10883 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10884 return Ok(e);
10885 }
10886 let args = self.parse_builtin_args()?;
10887 if args.len() != 2 {
10888 return Err(self.syntax_err("rename requires two arguments", line));
10889 }
10890 Ok(Expr {
10891 kind: ExprKind::Rename {
10892 old: Box::new(args[0].clone()),
10893 new: Box::new(args[1].clone()),
10894 },
10895 line,
10896 })
10897 }
10898 "chmod" => {
10899 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10900 return Ok(e);
10901 }
10902 let args = self.parse_builtin_args()?;
10903 if args.len() < 2 {
10904 return Err(self.syntax_err("chmod requires mode and at least one file", line));
10905 }
10906 Ok(Expr {
10907 kind: ExprKind::Chmod(args),
10908 line,
10909 })
10910 }
10911 "chown" => {
10912 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10913 return Ok(e);
10914 }
10915 let args = self.parse_builtin_args()?;
10916 if args.len() < 3 {
10917 return Err(
10918 self.syntax_err("chown requires uid, gid, and at least one file", line)
10919 );
10920 }
10921 Ok(Expr {
10922 kind: ExprKind::Chown(args),
10923 line,
10924 })
10925 }
10926 "stat" => {
10927 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10928 return Ok(e);
10929 }
10930 let args = self.parse_builtin_args()?;
10931 let arg = if args.len() == 1 {
10932 args[0].clone()
10933 } else if args.is_empty() {
10934 Expr {
10935 kind: ExprKind::ScalarVar("_".into()),
10936 line,
10937 }
10938 } else {
10939 return Err(self.syntax_err("stat requires zero or one argument", line));
10940 };
10941 Ok(Expr {
10942 kind: ExprKind::Stat(Box::new(arg)),
10943 line,
10944 })
10945 }
10946 "lstat" => {
10947 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10948 return Ok(e);
10949 }
10950 let args = self.parse_builtin_args()?;
10951 let arg = if args.len() == 1 {
10952 args[0].clone()
10953 } else if args.is_empty() {
10954 Expr {
10955 kind: ExprKind::ScalarVar("_".into()),
10956 line,
10957 }
10958 } else {
10959 return Err(self.syntax_err("lstat requires zero or one argument", line));
10960 };
10961 Ok(Expr {
10962 kind: ExprKind::Lstat(Box::new(arg)),
10963 line,
10964 })
10965 }
10966 "link" => {
10967 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10968 return Ok(e);
10969 }
10970 let args = self.parse_builtin_args()?;
10971 if args.len() != 2 {
10972 return Err(self.syntax_err("link requires two arguments", line));
10973 }
10974 Ok(Expr {
10975 kind: ExprKind::Link {
10976 old: Box::new(args[0].clone()),
10977 new: Box::new(args[1].clone()),
10978 },
10979 line,
10980 })
10981 }
10982 "symlink" => {
10983 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10984 return Ok(e);
10985 }
10986 let args = self.parse_builtin_args()?;
10987 if args.len() != 2 {
10988 return Err(self.syntax_err("symlink requires two arguments", line));
10989 }
10990 Ok(Expr {
10991 kind: ExprKind::Symlink {
10992 old: Box::new(args[0].clone()),
10993 new: Box::new(args[1].clone()),
10994 },
10995 line,
10996 })
10997 }
10998 "readlink" => {
10999 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11000 return Ok(e);
11001 }
11002 let args = self.parse_builtin_args()?;
11003 let arg = if args.len() == 1 {
11004 args[0].clone()
11005 } else if args.is_empty() {
11006 Expr {
11007 kind: ExprKind::ScalarVar("_".into()),
11008 line,
11009 }
11010 } else {
11011 return Err(self.syntax_err("readlink requires zero or one argument", line));
11012 };
11013 Ok(Expr {
11014 kind: ExprKind::Readlink(Box::new(arg)),
11015 line,
11016 })
11017 }
11018 "files" => {
11019 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11020 return Ok(e);
11021 }
11022 let args = self.parse_builtin_args()?;
11023 Ok(Expr {
11024 kind: ExprKind::Files(args),
11025 line,
11026 })
11027 }
11028 "filesf" | "f" => {
11029 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11030 return Ok(e);
11031 }
11032 let args = self.parse_builtin_args()?;
11033 Ok(Expr {
11034 kind: ExprKind::Filesf(args),
11035 line,
11036 })
11037 }
11038 "fr" => {
11039 let args = self.parse_builtin_args()?;
11040 Ok(Expr {
11041 kind: ExprKind::FilesfRecursive(args),
11042 line,
11043 })
11044 }
11045 "dirs" | "d" => {
11046 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11047 return Ok(e);
11048 }
11049 let args = self.parse_builtin_args()?;
11050 Ok(Expr {
11051 kind: ExprKind::Dirs(args),
11052 line,
11053 })
11054 }
11055 "dr" => {
11056 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11057 return Ok(e);
11058 }
11059 let args = self.parse_builtin_args()?;
11060 Ok(Expr {
11061 kind: ExprKind::DirsRecursive(args),
11062 line,
11063 })
11064 }
11065 "sym_links" => {
11066 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11067 return Ok(e);
11068 }
11069 let args = self.parse_builtin_args()?;
11070 Ok(Expr {
11071 kind: ExprKind::SymLinks(args),
11072 line,
11073 })
11074 }
11075 "sockets" => {
11076 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11077 return Ok(e);
11078 }
11079 let args = self.parse_builtin_args()?;
11080 Ok(Expr {
11081 kind: ExprKind::Sockets(args),
11082 line,
11083 })
11084 }
11085 "pipes" => {
11086 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11087 return Ok(e);
11088 }
11089 let args = self.parse_builtin_args()?;
11090 Ok(Expr {
11091 kind: ExprKind::Pipes(args),
11092 line,
11093 })
11094 }
11095 "block_devices" => {
11096 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11097 return Ok(e);
11098 }
11099 let args = self.parse_builtin_args()?;
11100 Ok(Expr {
11101 kind: ExprKind::BlockDevices(args),
11102 line,
11103 })
11104 }
11105 "char_devices" => {
11106 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11107 return Ok(e);
11108 }
11109 let args = self.parse_builtin_args()?;
11110 Ok(Expr {
11111 kind: ExprKind::CharDevices(args),
11112 line,
11113 })
11114 }
11115 "exe" | "executables" => {
11116 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11117 return Ok(e);
11118 }
11119 let args = self.parse_builtin_args()?;
11120 Ok(Expr {
11121 kind: ExprKind::Executables(args),
11122 line,
11123 })
11124 }
11125 "glob" => {
11126 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11127 return Ok(e);
11128 }
11129 let args = self.parse_builtin_args()?;
11130 Ok(Expr {
11131 kind: ExprKind::Glob(args),
11132 line,
11133 })
11134 }
11135 "glob_par" => {
11136 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11137 return Ok(e);
11138 }
11139 let (args, progress) = self.parse_glob_par_or_par_sed_args()?;
11140 Ok(Expr {
11141 kind: ExprKind::GlobPar { args, progress },
11142 line,
11143 })
11144 }
11145 "par_sed" => {
11146 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11147 return Ok(e);
11148 }
11149 let (args, progress) = self.parse_glob_par_or_par_sed_args()?;
11150 Ok(Expr {
11151 kind: ExprKind::ParSed { args, progress },
11152 line,
11153 })
11154 }
11155 "bless" => {
11156 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11157 return Ok(e);
11158 }
11159 let args = self.parse_builtin_args()?;
11160 Ok(Expr {
11161 kind: ExprKind::Bless {
11162 ref_expr: Box::new(args[0].clone()),
11163 class: args.get(1).cloned().map(Box::new),
11164 },
11165 line,
11166 })
11167 }
11168 "caller" => {
11169 if matches!(self.peek(), Token::LParen) {
11170 self.advance();
11171 if matches!(self.peek(), Token::RParen) {
11172 self.advance();
11173 Ok(Expr {
11174 kind: ExprKind::Caller(None),
11175 line,
11176 })
11177 } else {
11178 let a = self.parse_expression()?;
11179 self.expect(&Token::RParen)?;
11180 Ok(Expr {
11181 kind: ExprKind::Caller(Some(Box::new(a))),
11182 line,
11183 })
11184 }
11185 } else {
11186 Ok(Expr {
11187 kind: ExprKind::Caller(None),
11188 line,
11189 })
11190 }
11191 }
11192 "wantarray" => {
11193 if matches!(self.peek(), Token::LParen) {
11194 self.advance();
11195 self.expect(&Token::RParen)?;
11196 }
11197 Ok(Expr {
11198 kind: ExprKind::Wantarray,
11199 line,
11200 })
11201 }
11202 "sub" => {
11203 if !crate::compat_mode() {
11205 return Err(self.syntax_err(
11206 "stryke uses `fn {}` instead of `fn {}` (this is not Perl 5)",
11207 line,
11208 ));
11209 }
11210 let (params, _prototype) = self.parse_sub_sig_or_prototype_opt()?;
11212 let body = self.parse_block()?;
11213 Ok(Expr {
11214 kind: ExprKind::CodeRef { params, body },
11215 line,
11216 })
11217 }
11218 "fn" => {
11219 let (params, _prototype) = self.parse_sub_sig_or_prototype_opt()?;
11221 let body = self.parse_block()?;
11222 Ok(Expr {
11223 kind: ExprKind::CodeRef { params, body },
11224 line,
11225 })
11226 }
11227 _ => {
11228 if matches!(self.peek(), Token::FatArrow) {
11231 return Ok(Expr {
11232 kind: ExprKind::String(name),
11233 line,
11234 });
11235 }
11236 if name == "_" {
11239 return Ok(Expr {
11240 kind: ExprKind::ScalarVar("_".to_string()),
11241 line,
11242 });
11243 }
11244 if matches!(self.peek(), Token::LParen) {
11246 self.advance();
11247 let args = self.parse_arg_list()?;
11248 self.expect(&Token::RParen)?;
11249 Ok(Expr {
11250 kind: ExprKind::FuncCall { name, args },
11251 line,
11252 })
11253 } else if self.peek().is_term_start()
11254 && !(matches!(self.peek(), Token::Ident(ref kw) if kw == "sub")
11255 && matches!(self.peek_at(1), Token::Ident(_)))
11256 && !(self.suppress_parenless_call > 0 && matches!(self.peek(), Token::Ident(_)))
11257 && !(matches!(self.peek(), Token::LBrace)
11258 && self.peek_line() > self.prev_line())
11259 {
11260 let args = self.parse_list_until_terminator()?;
11270 Ok(Expr {
11271 kind: ExprKind::FuncCall { name, args },
11272 line,
11273 })
11274 } else {
11275 Ok(Expr {
11281 kind: ExprKind::Bareword(name),
11282 line,
11283 })
11284 }
11285 }
11286 }
11287 }
11288
11289 fn parse_print_like(
11290 &mut self,
11291 make: impl FnOnce(Option<String>, Vec<Expr>) -> ExprKind,
11292 ) -> PerlResult<Expr> {
11293 let line = self.peek_line();
11294 let handle = if let Token::Ident(ref h) = self.peek().clone() {
11296 if h.chars().all(|c| c.is_uppercase() || c == '_')
11297 && !matches!(self.peek(), Token::LParen)
11298 {
11299 let h = h.clone();
11300 let saved = self.pos;
11301 self.advance();
11302 if self.peek().is_term_start()
11304 || matches!(
11305 self.peek(),
11306 Token::DoubleString(_) | Token::BacktickString(_) | Token::SingleString(_)
11307 )
11308 {
11309 Some(h)
11310 } else {
11311 self.pos = saved;
11312 None
11313 }
11314 } else {
11315 None
11316 }
11317 } else if let Token::ScalarVar(ref v) = self.peek().clone() {
11318 let v = v.clone();
11328 if v == "_" {
11329 None
11330 } else {
11331 let saved = self.pos;
11332 self.advance();
11333 let next = self.peek().clone();
11334 let is_stmt_modifier = matches!(&next, Token::Ident(kw)
11335 if matches!(kw.as_str(), "if" | "unless" | "while" | "until" | "for" | "foreach"));
11336 if !is_stmt_modifier
11337 && !matches!(next, Token::LBracket | Token::LBrace)
11338 && (next.is_term_start()
11339 || matches!(
11340 next,
11341 Token::DoubleString(_)
11342 | Token::BacktickString(_)
11343 | Token::SingleString(_)
11344 ))
11345 {
11346 Some(format!("${v}"))
11348 } else {
11349 self.pos = saved;
11350 None
11351 }
11352 }
11353 } else {
11354 None
11355 };
11356 let args =
11361 if matches!(self.peek(), Token::LParen) && matches!(self.peek_at(1), Token::RParen) {
11362 let line_topic = self.peek_line();
11363 self.advance(); self.advance(); vec![Expr {
11366 kind: ExprKind::ScalarVar("_".into()),
11367 line: line_topic,
11368 }]
11369 } else {
11370 self.parse_list_until_terminator()?
11371 };
11372 Ok(Expr {
11373 kind: make(handle, args),
11374 line,
11375 })
11376 }
11377
11378 fn parse_block_list(&mut self) -> PerlResult<(Block, Expr)> {
11379 let block = self.parse_block()?;
11380 let block_end_line = self.prev_line();
11381 self.eat(&Token::Comma);
11382 if self.in_pipe_rhs()
11386 && (matches!(
11387 self.peek(),
11388 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof | Token::PipeForward
11389 ) || self.peek_line() > block_end_line)
11390 {
11391 let line = self.peek_line();
11392 return Ok((block, self.pipe_placeholder_list(line)));
11393 }
11394 let list = self.parse_expression()?;
11395 Ok((block, list))
11396 }
11397
11398 fn parse_comma_expr_list_with_timeout_tail(
11401 &mut self,
11402 paren: bool,
11403 ) -> PerlResult<(Vec<Expr>, Option<Expr>)> {
11404 let mut parts = vec![self.parse_assign_expr()?];
11405 loop {
11406 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
11407 break;
11408 }
11409 if paren && matches!(self.peek(), Token::RParen) {
11410 break;
11411 }
11412 if matches!(
11413 self.peek(),
11414 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof
11415 ) {
11416 break;
11417 }
11418 if self.peek_is_postfix_stmt_modifier_keyword() {
11419 break;
11420 }
11421 if let Token::Ident(ref kw) = self.peek().clone() {
11422 if kw == "timeout" && matches!(self.peek_at(1), Token::FatArrow) {
11423 self.advance();
11424 self.expect(&Token::FatArrow)?;
11425 let t = self.parse_assign_expr()?;
11426 return Ok((parts, Some(t)));
11427 }
11428 }
11429 parts.push(self.parse_assign_expr()?);
11430 }
11431 Ok((parts, None))
11432 }
11433
11434 fn parse_init_block_then_list_optional_progress(
11436 &mut self,
11437 ) -> PerlResult<(Expr, Block, Expr, Option<Expr>)> {
11438 let init = self.parse_assign_expr()?;
11439 self.expect(&Token::Comma)?;
11440 let block = self.parse_block_or_bareword_block()?;
11441 self.eat(&Token::Comma);
11442 let line = self.peek_line();
11443 if let Token::Ident(ref kw) = self.peek().clone() {
11444 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
11445 self.advance();
11446 self.expect(&Token::FatArrow)?;
11447 let prog = self.parse_assign_expr()?;
11448 return Ok((
11449 init,
11450 block,
11451 Expr {
11452 kind: ExprKind::List(vec![]),
11453 line,
11454 },
11455 Some(prog),
11456 ));
11457 }
11458 }
11459 if matches!(
11460 self.peek(),
11461 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof
11462 ) {
11463 return Ok((
11464 init,
11465 block,
11466 Expr {
11467 kind: ExprKind::List(vec![]),
11468 line,
11469 },
11470 None,
11471 ));
11472 }
11473 let mut parts = vec![self.parse_assign_expr()?];
11474 loop {
11475 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
11476 break;
11477 }
11478 if matches!(
11479 self.peek(),
11480 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof
11481 ) {
11482 break;
11483 }
11484 if self.peek_is_postfix_stmt_modifier_keyword() {
11485 break;
11486 }
11487 if let Token::Ident(ref kw) = self.peek().clone() {
11488 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
11489 self.advance();
11490 self.expect(&Token::FatArrow)?;
11491 let prog = self.parse_assign_expr()?;
11492 return Ok((init, block, merge_expr_list(parts), Some(prog)));
11493 }
11494 }
11495 parts.push(self.parse_assign_expr()?);
11496 }
11497 Ok((init, block, merge_expr_list(parts), None))
11498 }
11499
11500 fn parse_cluster_block_then_list_optional_progress(
11502 &mut self,
11503 ) -> PerlResult<(Expr, Block, Expr, Option<Expr>)> {
11504 let cluster = self.parse_assign_expr()?;
11505 let block = self.parse_block_or_bareword_block()?;
11506 self.eat(&Token::Comma);
11507 let line = self.peek_line();
11508 if let Token::Ident(ref kw) = self.peek().clone() {
11509 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
11510 self.advance();
11511 self.expect(&Token::FatArrow)?;
11512 let prog = self.parse_assign_expr_stop_at_pipe()?;
11513 return Ok((
11514 cluster,
11515 block,
11516 Expr {
11517 kind: ExprKind::List(vec![]),
11518 line,
11519 },
11520 Some(prog),
11521 ));
11522 }
11523 }
11524 let empty_list_ok = matches!(
11525 self.peek(),
11526 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof | Token::PipeForward
11527 ) || (self.in_pipe_rhs() && matches!(self.peek(), Token::Comma));
11528 if empty_list_ok {
11529 return Ok((
11530 cluster,
11531 block,
11532 Expr {
11533 kind: ExprKind::List(vec![]),
11534 line,
11535 },
11536 None,
11537 ));
11538 }
11539 let mut parts = vec![self.parse_assign_expr_stop_at_pipe()?];
11540 loop {
11541 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
11542 break;
11543 }
11544 if matches!(
11545 self.peek(),
11546 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof | Token::PipeForward
11547 ) {
11548 break;
11549 }
11550 if self.peek_is_postfix_stmt_modifier_keyword() {
11551 break;
11552 }
11553 if let Token::Ident(ref kw) = self.peek().clone() {
11554 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
11555 self.advance();
11556 self.expect(&Token::FatArrow)?;
11557 let prog = self.parse_assign_expr_stop_at_pipe()?;
11558 return Ok((cluster, block, merge_expr_list(parts), Some(prog)));
11559 }
11560 }
11561 parts.push(self.parse_assign_expr_stop_at_pipe()?);
11562 }
11563 Ok((cluster, block, merge_expr_list(parts), None))
11564 }
11565
11566 fn parse_block_then_list_optional_progress(
11575 &mut self,
11576 ) -> PerlResult<(Block, Expr, Option<Expr>)> {
11577 let block = self.parse_block_or_bareword_block()?;
11578 self.eat(&Token::Comma);
11579 let line = self.peek_line();
11580 if let Token::Ident(ref kw) = self.peek().clone() {
11581 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
11582 self.advance();
11583 self.expect(&Token::FatArrow)?;
11584 let prog = self.parse_assign_expr_stop_at_pipe()?;
11585 return Ok((
11586 block,
11587 Expr {
11588 kind: ExprKind::List(vec![]),
11589 line,
11590 },
11591 Some(prog),
11592 ));
11593 }
11594 }
11595 let empty_list_ok = matches!(
11601 self.peek(),
11602 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof | Token::PipeForward
11603 ) || (self.in_pipe_rhs() && matches!(self.peek(), Token::Comma));
11604 if empty_list_ok {
11605 return Ok((
11606 block,
11607 Expr {
11608 kind: ExprKind::List(vec![]),
11609 line,
11610 },
11611 None,
11612 ));
11613 }
11614 let mut parts = vec![self.parse_assign_expr_stop_at_pipe()?];
11615 loop {
11616 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
11617 break;
11618 }
11619 if matches!(
11620 self.peek(),
11621 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof | Token::PipeForward
11622 ) {
11623 break;
11624 }
11625 if self.peek_is_postfix_stmt_modifier_keyword() {
11626 break;
11627 }
11628 if let Token::Ident(ref kw) = self.peek().clone() {
11629 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
11630 self.advance();
11631 self.expect(&Token::FatArrow)?;
11632 let prog = self.parse_assign_expr_stop_at_pipe()?;
11633 return Ok((block, merge_expr_list(parts), Some(prog)));
11634 }
11635 }
11636 parts.push(self.parse_assign_expr_stop_at_pipe()?);
11637 }
11638 Ok((block, merge_expr_list(parts), None))
11639 }
11640
11641 fn parse_fan_count_and_block(&mut self, line: usize) -> PerlResult<(Option<Box<Expr>>, Block)> {
11643 if matches!(self.peek(), Token::LBrace) {
11645 let block = self.parse_block()?;
11646 return Ok((None, block));
11647 }
11648 let saved = self.pos;
11649 let first = self.parse_postfix()?;
11651 if matches!(self.peek(), Token::LBrace) {
11652 let block = self.parse_block()?;
11654 Ok((Some(Box::new(first)), block))
11655 } else if matches!(self.peek(), Token::Semicolon | Token::RBrace | Token::Eof)
11656 || (matches!(self.peek(), Token::Comma)
11657 && matches!(self.peek_at(1), Token::Ident(ref kw) if kw == "progress"))
11658 {
11659 let block = self.bareword_to_no_arg_block(first);
11661 Ok((None, block))
11662 } else if matches!(first.kind, ExprKind::Integer(_)) {
11663 self.eat(&Token::Comma);
11665 let body = self.parse_fan_blockless_body(line)?;
11666 Ok((Some(Box::new(first)), body))
11667 } else {
11668 self.pos = saved;
11671 let body = self.parse_fan_blockless_body(line)?;
11672 Ok((None, body))
11673 }
11674 }
11675
11676 fn parse_fan_blockless_body(&mut self, line: usize) -> PerlResult<Block> {
11678 if matches!(self.peek(), Token::LBrace) {
11679 return self.parse_block();
11680 }
11681 if let Token::Ident(ref name) = self.peek().clone() {
11683 if matches!(
11684 self.peek_at(1),
11685 Token::Comma | Token::Semicolon | Token::RBrace | Token::Eof | Token::PipeForward
11686 ) {
11687 let name = name.clone();
11688 self.advance();
11689 let body = Expr {
11690 kind: ExprKind::FuncCall { name, args: vec![] },
11691 line,
11692 };
11693 return Ok(vec![Statement::new(StmtKind::Expression(body), line)]);
11694 }
11695 }
11696 let expr = self.parse_assign_expr_stop_at_pipe()?;
11698 Ok(vec![Statement::new(StmtKind::Expression(expr), line)])
11699 }
11700
11701 fn bareword_to_no_arg_block(&self, expr: Expr) -> Block {
11704 let line = expr.line;
11705 let body = match &expr.kind {
11706 ExprKind::Bareword(name) => Expr {
11707 kind: ExprKind::FuncCall {
11708 name: name.clone(),
11709 args: vec![],
11710 },
11711 line,
11712 },
11713 _ => expr,
11714 };
11715 vec![Statement::new(StmtKind::Expression(body), line)]
11716 }
11717
11718 fn parse_block_or_bareword_block(&mut self) -> PerlResult<Block> {
11727 if matches!(self.peek(), Token::LBrace) {
11728 return self.parse_block();
11729 }
11730 let line = self.peek_line();
11731 if let Token::Ident(ref name) = self.peek().clone() {
11734 if matches!(
11735 self.peek_at(1),
11736 Token::Comma | Token::Semicolon | Token::RBrace | Token::Eof | Token::PipeForward
11737 ) {
11738 let name = name.clone();
11739 self.advance();
11740 let body = Expr {
11741 kind: ExprKind::FuncCall {
11742 name,
11743 args: vec![Expr {
11744 kind: ExprKind::ScalarVar("_".to_string()),
11745 line,
11746 }],
11747 },
11748 line,
11749 };
11750 return Ok(vec![Statement::new(StmtKind::Expression(body), line)]);
11751 }
11752 }
11753 let expr = self.parse_assign_expr_stop_at_pipe()?;
11755 Ok(vec![Statement::new(StmtKind::Expression(expr), line)])
11756 }
11757
11758 fn parse_block_or_bareword_block_no_args(&mut self) -> PerlResult<Block> {
11763 if matches!(self.peek(), Token::LBrace) {
11764 return self.parse_block();
11765 }
11766 let line = self.peek_line();
11767 if let Token::Ident(ref name) = self.peek().clone() {
11768 if matches!(
11769 self.peek_at(1),
11770 Token::Comma
11771 | Token::Semicolon
11772 | Token::RBrace
11773 | Token::Eof
11774 | Token::PipeForward
11775 | Token::Integer(_)
11776 ) {
11777 let name = name.clone();
11778 self.advance();
11779 let body = Expr {
11780 kind: ExprKind::FuncCall { name, args: vec![] },
11781 line,
11782 };
11783 return Ok(vec![Statement::new(StmtKind::Expression(body), line)]);
11784 }
11785 }
11786 let expr = self.parse_postfix()?;
11787 Ok(vec![Statement::new(StmtKind::Expression(expr), line)])
11788 }
11789
11790 fn is_known_bareword(name: &str) -> bool {
11798 Self::is_perl5_core(name) || Self::stryke_extension_name(name).is_some()
11799 }
11800
11801 fn is_try_builtin_name(name: &str) -> bool {
11807 crate::builtins::BUILTIN_ARMS
11808 .iter()
11809 .any(|arm| arm.contains(&name))
11810 }
11811
11812 fn is_perl5_core(name: &str) -> bool {
11817 matches!(
11818 name,
11819 "map" | "grep" | "sort" | "reverse" | "join" | "split"
11821 | "push" | "pop" | "shift" | "unshift" | "splice"
11822 | "pack" | "unpack"
11823 | "keys" | "values" | "each"
11825 | "chomp" | "chop" | "chr" | "ord" | "hex" | "oct"
11827 | "lc" | "uc" | "lcfirst" | "ucfirst"
11828 | "length" | "substr" | "index" | "rindex"
11829 | "sprintf" | "printf" | "print" | "say"
11830 | "pos" | "quotemeta" | "study"
11831 | "abs" | "int" | "sqrt" | "sin" | "cos" | "atan2"
11833 | "exp" | "log" | "rand" | "srand"
11834 | "time" | "localtime" | "gmtime"
11836 | "defined" | "undef" | "ref" | "scalar" | "wantarray"
11838 | "caller" | "delete" | "exists" | "bless" | "prototype"
11839 | "tie" | "untie" | "tied"
11840 | "open" | "close" | "read" | "readline" | "write" | "seek" | "tell"
11842 | "eof" | "binmode" | "getc" | "fileno" | "truncate"
11843 | "format" | "formline" | "select" | "vec"
11844 | "sysopen" | "sysread" | "sysseek" | "syswrite"
11845 | "stat" | "lstat" | "rename" | "unlink" | "utime"
11847 | "mkdir" | "rmdir" | "chdir" | "chmod" | "chown"
11848 | "glob" | "opendir" | "readdir" | "closedir"
11849 | "link" | "readlink" | "symlink"
11850 | "fcntl" | "flock" | "ioctl" | "pipe" | "dbmopen" | "dbmclose"
11852 | "msgctl" | "msgget" | "msgrcv" | "msgsnd"
11854 | "semctl" | "semget" | "semop"
11855 | "shmctl" | "shmget" | "shmread" | "shmwrite"
11856 | "system" | "exec" | "exit" | "die" | "warn" | "dump"
11858 | "fork" | "wait" | "waitpid" | "kill" | "alarm" | "sleep"
11859 | "chroot" | "times" | "umask" | "reset"
11860 | "getpgrp" | "setpgrp" | "getppid"
11861 | "getpriority" | "setpriority"
11862 | "socket" | "socketpair" | "connect" | "listen" | "accept" | "shutdown"
11864 | "send" | "recv" | "bind" | "setsockopt" | "getsockopt"
11865 | "getpeername" | "getsockname"
11866 | "getpwnam" | "getpwuid" | "getpwent" | "setpwent"
11868 | "getgrnam" | "getgrgid" | "getgrent" | "setgrent"
11869 | "getlogin"
11870 | "gethostbyname" | "gethostbyaddr" | "gethostent"
11871 | "getnetbyname" | "getnetent"
11872 | "getprotobyname" | "getprotoent"
11873 | "getservbyname" | "getservent"
11874 | "sethostent" | "setnetent" | "setprotoent" | "setservent"
11875 | "endpwent" | "endgrent"
11876 | "endhostent" | "endnetent" | "endprotoent" | "endservent"
11877 | "return" | "do" | "eval" | "require"
11879 | "my" | "our" | "local" | "use" | "no"
11880 | "sub" | "if" | "unless" | "while" | "until"
11881 | "for" | "foreach" | "last" | "next" | "redo" | "goto"
11882 | "not" | "and" | "or"
11883 | "qw" | "qq" | "q"
11885 | "BEGIN" | "END"
11887 )
11888 }
11889
11890 fn stryke_extension_name(name: &str) -> Option<&str> {
11893 match name {
11894 | "pmap" | "pmap_on" | "pflat_map" | "pflat_map_on" | "pmap_chunked"
11896 | "pgrep" | "pfor" | "psort" | "preduce" | "preduce_init" | "pmap_reduce"
11897 | "pcache" | "pchannel" | "pselect" | "puniq" | "pfirst" | "pany"
11898 | "fan" | "fan_cap" | "par_lines" | "par_walk" | "par_sed"
11899 | "par_find_files" | "par_line_count" | "pwatch" | "par_pipeline_stream"
11900 | "glob_par" | "ppool" | "barrier" | "pipeline" | "cluster"
11901 | "pmaps" | "pflat_maps" | "pgreps"
11902 | "fore" | "e" | "ep" | "flat_map" | "flat_maps" | "maps" | "filter" | "fi" | "find_all" | "reduce" | "fold"
11904 | "inject" | "collect" | "uniq" | "distinct" | "any" | "all" | "none"
11905 | "first" | "detect" | "find" | "compact" | "concat" | "chain" | "reject" | "flatten" | "set"
11906 | "min_by" | "max_by" | "sort_by" | "tally" | "find_index"
11907 | "each_with_index" | "count" | "cnt" |"len" | "group_by" | "chunk_by"
11908 | "zip" | "chunk" | "chunked" | "sliding_window" | "windowed"
11909 | "enumerate" | "with_index" | "shuffle" | "shuffled"| "heap"
11910 | "take_while" | "drop_while" | "skip_while" | "tap" | "peek" | "partition"
11911 | "zip_with" | "count_by" | "skip" | "first_or"
11912 | "input" | "lines" | "words" | "chars" | "digits" | "letters" | "letters_uc" | "letters_lc"
11914 | "punctuation" | "punct"
11915 | "sentences" | "sents"
11916 | "paragraphs" | "paras" | "sections" | "sects"
11917 | "numbers" | "nums" | "graphemes" | "grs" | "columns" | "cols"
11918 | "trim" | "avg" | "stddev"
11919 | "squared" | "sq" | "square" | "cubed" | "cb" | "cube" | "expt" | "pow" | "pw"
11920 | "normalize" | "snake_case" | "camel_case" | "kebab_case"
11921 | "frequencies" | "freq" | "interleave" | "ddump" | "stringify" | "str" | "top"
11922 | "to_json" | "to_csv" | "to_toml" | "to_yaml" | "to_xml"
11923 | "to_html" | "to_markdown" | "to_table" | "xopen"
11924 | "from_json" | "from_csv" | "from_toml" | "from_yaml" | "from_xml"
11925 | "clip" | "clipboard" | "paste" | "pbcopy" | "pbpaste" | "preview"
11926 | "sparkline" | "spark" | "bar_chart" | "bars" | "flame" | "flamechart"
11927 | "histo" | "gauge" | "spinner" | "spinner_start" | "spinner_stop"
11928 | "to_hash" | "to_set"
11929 | "to_file" | "read_lines" | "append_file" | "write_json" | "read_json"
11930 | "tempfile" | "tempdir" | "list_count" | "list_size" | "size"
11931 | "clamp" | "grep_v" | "select_keys" | "pluck" | "glob_match" | "which_all"
11932 | "dedup" | "nth" | "tail" | "take" | "drop" | "tee" | "range"
11933 | "inc" | "dec" | "elapsed"
11934 | "files" | "filesf" | "f" | "fr" | "dirs" | "d" | "dr" | "sym_links"
11936 | "sockets" | "pipes" | "block_devices" | "char_devices" | "exe" | "executables"
11937 | "basename" | "dirname" | "fileparse" | "realpath" | "canonpath"
11938 | "copy" | "move" | "spurt" | "read_bytes" | "which"
11939 | "getcwd" | "touch" | "gethostname" | "uname"
11940 | "csv_read" | "csv_write" | "dataframe" | "sqlite"
11942 | "fetch" | "fetch_json" | "fetch_async" | "fetch_async_json"
11943 | "par_fetch" | "par_csv_read" | "par_pipeline"
11944 | "json_encode" | "json_decode" | "json_jq"
11945 | "http_request" | "serve" | "ssh"
11946 | "html_parse" | "css_select" | "xml_parse" | "xpath"
11947 | "smtp_send"
11948 | "net_interfaces" | "net_ipv4" | "net_ipv6" | "net_mac"
11949 | "net_public_ip" | "net_dns" | "net_reverse_dns"
11950 | "net_ping" | "net_port_open" | "net_ports_scan"
11951 | "net_latency" | "net_download" | "net_headers"
11952 | "net_dns_servers" | "net_gateway" | "net_whois" | "net_hostname"
11953 | "git_log" | "git_status" | "git_diff" | "git_branches"
11955 | "git_tags" | "git_blame" | "git_authors" | "git_files"
11956 | "git_show" | "git_root"
11957 | "audio_convert" | "audio_info" | "id3_read" | "id3_write"
11959 | "to_pdf" | "pdf_text" | "pdf_pages"
11961 | "toml_encode" | "toml_decode"
11963 | "yaml_encode" | "yaml_decode"
11964 | "xml_encode" | "xml_decode"
11965 | "md5" | "sha1" | "sha224" | "sha256" | "sha384" | "sha512"
11967 | "sha3_256" | "s3_256" | "sha3_512" | "s3_512"
11968 | "shake128" | "shake256"
11969 | "hmac_sha256" | "hmac_sha1" | "hmac_sha384" | "hmac_sha512" | "hmac_md5"
11970 | "uuid" | "crc32"
11971 | "blake2b" | "b2b" | "blake2s" | "b2s" | "blake3" | "b3"
11972 | "ripemd160" | "rmd160" | "md4"
11973 | "xxh32" | "xxhash32" | "xxh64" | "xxhash64" | "xxh3" | "xxhash3" | "xxh3_128" | "xxhash3_128"
11974 | "murmur3" | "murmur3_32" | "murmur3_128"
11975 | "siphash" | "siphash_keyed"
11976 | "hkdf_sha256" | "hkdf" | "hkdf_sha512"
11977 | "poly1305" | "poly1305_mac"
11978 | "base32_encode" | "b32e" | "base32_decode" | "b32d"
11979 | "base58_encode" | "b58e" | "base58_decode" | "b58d"
11980 | "totp" | "totp_generate" | "totp_verify" | "hotp" | "hotp_generate"
11981 | "aes_cbc_encrypt" | "aes_cbc_enc" | "aes_cbc_decrypt" | "aes_cbc_dec"
11982 | "blowfish_encrypt" | "bf_enc" | "blowfish_decrypt" | "bf_dec"
11983 | "des3_encrypt" | "3des_enc" | "tdes_enc" | "des3_decrypt" | "3des_dec" | "tdes_dec"
11984 | "twofish_encrypt" | "tf_enc" | "twofish_decrypt" | "tf_dec"
11985 | "camellia_encrypt" | "cam_enc" | "camellia_decrypt" | "cam_dec"
11986 | "cast5_encrypt" | "cast5_enc" | "cast5_decrypt" | "cast5_dec"
11987 | "salsa20" | "salsa20_encrypt" | "salsa20_decrypt"
11988 | "xsalsa20" | "xsalsa20_encrypt" | "xsalsa20_decrypt"
11989 | "secretbox" | "secretbox_seal" | "secretbox_open"
11990 | "nacl_box_keygen" | "box_keygen" | "nacl_box" | "nacl_box_seal" | "box_seal"
11991 | "nacl_box_open" | "box_open"
11992 | "qr_ascii" | "qr" | "qr_png" | "qr_svg"
11993 | "barcode_code128" | "code128" | "barcode_code39" | "code39"
11994 | "barcode_ean13" | "ean13" | "barcode_svg"
11995 | "argon2_hash" | "argon2" | "argon2_verify"
11996 | "bcrypt_hash" | "bcrypt" | "bcrypt_verify"
11997 | "scrypt_hash" | "scrypt" | "scrypt_verify"
11998 | "pbkdf2" | "pbkdf2_derive"
11999 | "random_bytes" | "randbytes" | "random_bytes_hex" | "randhex"
12000 | "aes_encrypt" | "aes_enc" | "aes_decrypt" | "aes_dec"
12001 | "chacha_encrypt" | "chacha_enc" | "chacha_decrypt" | "chacha_dec"
12002 | "rsa_keygen" | "rsa_encrypt" | "rsa_enc" | "rsa_decrypt" | "rsa_dec"
12003 | "rsa_encrypt_pkcs1" | "rsa_decrypt_pkcs1" | "rsa_sign" | "rsa_verify"
12004 | "ecdsa_p256_keygen" | "p256_keygen" | "ecdsa_p256_sign" | "p256_sign"
12005 | "ecdsa_p256_verify" | "p256_verify"
12006 | "ecdsa_p384_keygen" | "p384_keygen" | "ecdsa_p384_sign" | "p384_sign"
12007 | "ecdsa_p384_verify" | "p384_verify"
12008 | "ecdsa_secp256k1_keygen" | "secp256k1_keygen"
12009 | "ecdsa_secp256k1_sign" | "secp256k1_sign"
12010 | "ecdsa_secp256k1_verify" | "secp256k1_verify"
12011 | "ecdh_p256" | "p256_dh" | "ecdh_p384" | "p384_dh"
12012 | "ed25519_keygen" | "ed_keygen" | "ed25519_sign" | "ed_sign"
12013 | "ed25519_verify" | "ed_verify"
12014 | "x25519_keygen" | "x_keygen" | "x25519_dh" | "x_dh"
12015 | "base64_encode" | "base64_decode"
12016 | "hex_encode" | "hex_decode"
12017 | "url_encode" | "url_decode"
12018 | "gzip" | "gunzip" | "gz" | "ugz" | "zstd" | "zstd_decode" | "zst" | "uzst"
12019 | "brotli" | "br" | "brotli_decode" | "ubr"
12020 | "xz" | "lzma" | "xz_decode" | "unxz" | "unlzma"
12021 | "bzip2" | "bz2" | "bzip2_decode" | "bunzip2" | "ubz2"
12022 | "lz4" | "lz4_decode" | "unlz4"
12023 | "snappy" | "snp" | "snappy_decode" | "unsnappy"
12024 | "lzw" | "lzw_decode" | "unlzw"
12025 | "tar_create" | "tar" | "tar_extract" | "untar" | "tar_list"
12026 | "tar_gz_create" | "tgz" | "tar_gz_extract" | "untgz"
12027 | "zip_create" | "zip_archive" | "zip_extract" | "unzip_archive" | "zip_list"
12028 | "erf" | "erfc" | "gamma" | "tgamma" | "lgamma" | "ln_gamma"
12030 | "digamma" | "psi" | "beta_fn" | "lbeta" | "ln_beta"
12031 | "betainc" | "beta_reg" | "gammainc" | "gamma_li"
12032 | "gammaincc" | "gamma_ui" | "gammainc_reg" | "gamma_lr"
12033 | "gammaincc_reg" | "gamma_ur"
12034 | "datetime_utc" | "datetime_now_tz"
12036 | "datetime_format_tz" | "datetime_add_seconds"
12037 | "datetime_from_epoch"
12038 | "datetime_parse_rfc3339" | "datetime_parse_local"
12039 | "datetime_strftime"
12040 | "dateseq" | "dategrep" | "dateround" | "datesort"
12041 | "jwt_encode" | "jwt_decode" | "jwt_decode_unsafe"
12043 | "log_info" | "log_warn" | "log_error"
12045 | "log_debug" | "log_trace" | "log_json" | "log_level"
12046 | "async" | "spawn" | "trace" | "timer" | "bench"
12048 | "eval_timeout" | "retry" | "rate_limit" | "every"
12049 | "gen" | "watch"
12050 | "assert_eq" | "assert_ne" | "assert_ok" | "assert_err"
12052 | "assert_true" | "assert_false"
12053 | "assert_gt" | "assert_lt" | "assert_ge" | "assert_le"
12054 | "assert_match" | "assert_contains" | "assert_near" | "assert_dies"
12055 | "test_run"
12056 | "mounts" | "du" | "du_tree" | "process_list"
12058 | "thread_count" | "pool_info" | "par_bench"
12059 | "slurp" | "cat" | "c" | "capture" | "pager" | "pg" | "less"
12061 | "stdin"
12062 | "__stryke_rust_compile"
12064 | "p" | "rev"
12066 | "even" | "odd" | "zero" | "nonzero"
12068 | "positive" | "pos_n" | "negative" | "neg_n"
12069 | "sign" | "negate" | "double" | "triple" | "half"
12070 | "identity" | "id"
12071 | "round" | "floor" | "ceil" | "ceiling" | "trunc" | "truncn"
12072 | "gcd" | "lcm" | "min2" | "max2"
12073 | "log2" | "log10" | "hypot"
12074 | "rad_to_deg" | "r2d" | "deg_to_rad" | "d2r"
12075 | "pow2" | "abs_diff"
12076 | "factorial" | "fact" | "fibonacci" | "fib"
12077 | "is_prime" | "is_square" | "is_power_of_two" | "is_pow2"
12078 | "cbrt" | "exp2" | "percent" | "pct" | "inverse"
12079 | "median" | "mode_val" | "variance"
12080 | "is_empty" | "is_blank" | "is_numeric"
12082 | "is_upper" | "is_lower" | "is_alpha" | "is_digit" | "is_alnum"
12083 | "is_space" | "is_whitespace"
12084 | "starts_with" | "sw" | "ends_with" | "ew" | "contains"
12085 | "capitalize" | "cap" | "swap_case" | "repeat"
12086 | "title_case" | "title" | "squish"
12087 | "pad_left" | "lpad" | "pad_right" | "rpad" | "center"
12088 | "truncate_at" | "shorten" | "reverse_str" | "rev_str"
12089 | "char_count" | "word_count" | "wc" | "line_count" | "lc_lines"
12090 | "is_array" | "is_arrayref" | "is_hash" | "is_hashref"
12092 | "is_code" | "is_coderef" | "is_ref"
12093 | "is_undef" | "is_defined" | "is_def"
12094 | "is_string" | "is_str" | "is_int" | "is_integer" | "is_float"
12095 | "invert" | "merge_hash"
12097 | "has_key" | "hk" | "has_any_key" | "has_all_keys"
12098 | "both" | "either" | "neither" | "xor_bool" | "bool_to_int" | "b2i"
12100 | "riffle" | "intersperse" | "every_nth"
12102 | "drop_n" | "take_n" | "rotate" | "swap_pairs"
12103 | "to_bin" | "bin_of" | "to_hex" | "hex_of" | "to_oct" | "oct_of"
12105 | "from_bin" | "from_hex" | "from_oct" | "to_base" | "from_base"
12106 | "bits_count" | "popcount" | "leading_zeros" | "lz"
12107 | "trailing_zeros" | "tz" | "bit_length" | "bitlen"
12108 | "bit_and" | "bit_or" | "bit_xor" | "bit_not"
12110 | "shift_left" | "shl" | "shift_right" | "shr"
12111 | "bit_set" | "bit_clear" | "bit_toggle" | "bit_test"
12112 | "c_to_f" | "f_to_c" | "c_to_k" | "k_to_c" | "f_to_k" | "k_to_f"
12114 | "miles_to_km" | "km_to_miles" | "miles_to_m" | "m_to_miles"
12116 | "feet_to_m" | "m_to_feet" | "inches_to_cm" | "cm_to_inches"
12117 | "yards_to_m" | "m_to_yards"
12118 | "kg_to_lbs" | "lbs_to_kg" | "g_to_oz" | "oz_to_g"
12120 | "stone_to_kg" | "kg_to_stone"
12121 | "bytes_to_kb" | "b_to_kb" | "kb_to_bytes" | "kb_to_b"
12123 | "bytes_to_mb" | "mb_to_bytes" | "bytes_to_gb" | "gb_to_bytes"
12124 | "kb_to_mb" | "mb_to_gb"
12125 | "bits_to_bytes" | "bytes_to_bits"
12126 | "seconds_to_minutes" | "s_to_m" | "minutes_to_seconds" | "m_to_s"
12128 | "seconds_to_hours" | "hours_to_seconds"
12129 | "seconds_to_days" | "days_to_seconds"
12130 | "minutes_to_hours" | "hours_to_minutes"
12131 | "hours_to_days" | "days_to_hours"
12132 | "is_leap_year" | "is_leap" | "days_in_month"
12134 | "month_name" | "month_short"
12135 | "weekday_name" | "weekday_short" | "quarter_of"
12136 | "now_ms" | "now_us" | "now_ns"
12138 | "unix_epoch" | "epoch" | "unix_epoch_ms" | "epoch_ms"
12139 | "rgb_to_hex" | "hex_to_rgb"
12141 | "ansi_red" | "ansi_green" | "ansi_yellow" | "ansi_blue"
12142 | "ansi_magenta" | "ansi_cyan" | "ansi_white" | "ansi_black"
12143 | "ansi_bold" | "ansi_dim" | "ansi_underline" | "ansi_reverse"
12144 | "strip_ansi"
12145 | "red" | "green" | "yellow" | "blue" | "magenta" | "purple" | "cyan"
12146 | "white" | "black" | "bold" | "dim" | "italic" | "underline"
12147 | "strikethrough" | "ansi_off" | "off" | "gray" | "grey"
12148 | "bright_red" | "bright_green" | "bright_yellow" | "bright_blue"
12149 | "bright_magenta" | "bright_cyan" | "bright_white"
12150 | "bg_red" | "bg_green" | "bg_yellow" | "bg_blue"
12151 | "bg_magenta" | "bg_cyan" | "bg_white" | "bg_black"
12152 | "red_bold" | "bold_red" | "green_bold" | "bold_green"
12153 | "yellow_bold" | "bold_yellow" | "blue_bold" | "bold_blue"
12154 | "magenta_bold" | "bold_magenta" | "cyan_bold" | "bold_cyan"
12155 | "white_bold" | "bold_white"
12156 | "blink" | "rapid_blink" | "hidden" | "overline"
12157 | "bg_bright_red" | "bg_bright_green" | "bg_bright_yellow" | "bg_bright_blue"
12158 | "bg_bright_magenta" | "bg_bright_cyan" | "bg_bright_white"
12159 | "rgb" | "bg_rgb" | "color256" | "c256" | "bg_color256" | "bg_c256"
12160 | "ipv4_to_int" | "int_to_ipv4"
12162 | "is_valid_ipv4" | "is_valid_ipv6" | "is_valid_email" | "is_valid_url"
12163 | "path_ext" | "path_stem" | "path_parent" | "path_join" | "path_split"
12165 | "strip_prefix" | "strip_suffix" | "ensure_prefix" | "ensure_suffix"
12166 | "const_fn" | "always_true" | "always_false"
12168 | "flip_args" | "first_arg" | "second_arg" | "last_arg"
12169 | "count_eq" | "count_ne" | "all_eq"
12171 | "all_distinct" | "all_unique" | "has_duplicates"
12172 | "sum_of" | "product_of" | "max_of" | "min_of" | "range_of"
12173 | "quote" | "single_quote" | "unquote"
12175 | "extract_between" | "ellipsis"
12176 | "coin_flip" | "dice_roll"
12178 | "random_int" | "random_float" | "random_bool"
12179 | "random_choice" | "random_between"
12180 | "random_string" | "random_alpha" | "random_digit"
12181 | "os_name" | "os_arch" | "num_cpus"
12183 | "pid" | "ppid" | "uid" | "gid"
12184 | "username" | "home_dir" | "temp_dir"
12185 | "mem_total" | "mem_free" | "mem_used"
12186 | "swap_total" | "swap_free" | "swap_used"
12187 | "disk_total" | "disk_free" | "disk_avail" | "disk_used"
12188 | "load_avg" | "sys_uptime" | "page_size"
12189 | "os_version" | "os_family" | "endianness" | "pointer_width"
12190 | "proc_mem" | "rss"
12191 | "transpose" | "unzip"
12193 | "run_length_encode" | "rle" | "run_length_decode" | "rld"
12194 | "sliding_pairs" | "consecutive_eq" | "flatten_deep"
12195 | "tan" | "asin" | "acos" | "atan"
12197 | "sinh" | "cosh" | "tanh" | "asinh" | "acosh" | "atanh"
12198 | "sqr" | "cube_fn"
12199 | "mod_op" | "ceil_div" | "floor_div"
12200 | "is_finite" | "is_infinite" | "is_inf" | "is_nan"
12201 | "degrees" | "radians"
12202 | "min_abs" | "max_abs"
12203 | "saturate" | "sat01" | "wrap_around"
12204 | "rot13" | "rot47" | "caesar_shift" | "reverse_words"
12206 | "count_vowels" | "count_consonants" | "is_vowel" | "is_consonant"
12207 | "first_word" | "last_word"
12208 | "left_str" | "head_str" | "right_str" | "tail_str" | "mid_str"
12209 | "lowercase" | "uppercase"
12210 | "pascal_case" | "pc_case"
12211 | "constant_case" | "upper_snake" | "dot_case" | "path_case"
12212 | "is_palindrome" | "hamming_distance"
12213 | "longest_common_prefix" | "lcp"
12214 | "ascii_ord" | "ascii_chr" | "count_char" | "indexes_of"
12215 | "replace_first" | "replace_all_str"
12216 | "contains_any" | "contains_all"
12217 | "starts_with_any" | "ends_with_any"
12218 | "is_pair" | "is_triple"
12220 | "is_sorted" | "is_asc" | "is_sorted_desc" | "is_desc"
12221 | "is_empty_arr" | "is_empty_hash"
12222 | "is_subset" | "is_superset" | "is_permutation"
12223 | "first_eq" | "last_eq"
12225 | "index_of" | "last_index_of" | "positions_of"
12226 | "batch" | "binary_search" | "bsearch" | "linear_search" | "lsearch"
12227 | "distinct_count" | "longest" | "shortest"
12228 | "array_union" | "list_union"
12229 | "array_intersection" | "list_intersection"
12230 | "array_difference" | "list_difference"
12231 | "symmetric_diff" | "group_of_n" | "chunk_n"
12232 | "repeat_list" | "cycle_n" | "random_sample" | "sample_n"
12233 | "pick_keys" | "pick" | "omit_keys" | "omit"
12235 | "map_keys_fn" | "map_values_fn"
12236 | "hash_size" | "hash_from_pairs" | "pairs_from_hash"
12237 | "hash_eq" | "keys_sorted" | "values_sorted" | "remove_keys"
12238 | "today" | "yesterday" | "tomorrow" | "is_weekend" | "is_weekday"
12240 | "json_pretty" | "json_minify" | "escape_json" | "json_escape"
12242 | "cmd_exists" | "env_get" | "env_has" | "env_keys"
12244 | "argc" | "script_name"
12245 | "has_stdin_tty" | "has_stdout_tty" | "has_stderr_tty"
12246 | "uuid_v4" | "nanoid" | "short_id" | "is_uuid" | "token"
12248 | "email_domain" | "email_local"
12250 | "url_host" | "url_path" | "url_query" | "url_scheme"
12251 | "file_size" | "fsize" | "file_mtime" | "mtime"
12253 | "file_atime" | "atime" | "file_ctime" | "ctime"
12254 | "is_symlink" | "is_readable" | "is_writable" | "is_executable"
12255 | "path_is_abs" | "path_is_rel"
12256 | "min_max" | "percentile" | "harmonic_mean" | "geometric_mean" | "zscore"
12258 | "sorted" | "sorted_desc" | "sorted_nums" | "sorted_by_length"
12259 | "reverse_list" | "list_reverse"
12260 | "without" | "without_nth" | "take_last" | "drop_last"
12261 | "pairwise" | "zipmap"
12262 | "format_bytes" | "human_bytes"
12263 | "format_duration" | "human_duration"
12264 | "format_number" | "group_number"
12265 | "format_percent" | "pad_number"
12266 | "spaceship" | "cmp_num" | "cmp_str"
12267 | "compare_versions" | "version_cmp"
12268 | "hash_insert" | "hash_update" | "hash_delete"
12269 | "matches_regex" | "re_match"
12270 | "count_regex_matches" | "regex_extract"
12271 | "regex_split_str" | "regex_replace_str"
12272 | "shuffle_chars" | "random_char" | "nth_word"
12273 | "head_lines" | "tail_lines" | "count_substring"
12274 | "is_valid_hex" | "hex_upper" | "hex_lower"
12275 | "ms_to_s" | "s_to_ms" | "ms_to_ns" | "ns_to_ms"
12276 | "us_to_ns" | "ns_to_us"
12277 | "liters_to_gallons" | "gallons_to_liters"
12278 | "liters_to_ml" | "ml_to_liters"
12279 | "cups_to_ml" | "ml_to_cups"
12280 | "newtons_to_lbf" | "lbf_to_newtons"
12281 | "joules_to_cal" | "cal_to_joules"
12282 | "watts_to_hp" | "hp_to_watts"
12283 | "pascals_to_psi" | "psi_to_pascals"
12284 | "bar_to_pascals" | "pascals_to_bar"
12285 | "match"
12287 | "fst" | "rest" | "rst" | "second" | "snd"
12289 | "last_clj" | "lastc" | "butlast" | "bl"
12290 | "ffirst" | "ffs" | "fnext" | "fne" | "nfirst" | "nfs" | "nnext" | "nne"
12291 | "cons" | "conj"
12292 | "peek_clj" | "pkc" | "pop_clj" | "popc"
12293 | "some" | "not_any" | "not_every"
12294 | "comp" | "compose" | "partial" | "constantly" | "complement" | "compl"
12295 | "fnil" | "juxt"
12296 | "memoize" | "memo" | "curry" | "once"
12297 | "deep_clone" | "dclone" | "deep_merge" | "dmerge" | "deep_equal" | "deq"
12298 | "iterate" | "iter" | "repeatedly" | "rptd" | "cycle" | "cyc"
12299 | "mapcat" | "mcat" | "keep" | "kp" | "remove_clj" | "remc"
12300 | "reductions" | "rdcs"
12301 | "partition_by" | "pby" | "partition_all" | "pall"
12302 | "split_at" | "spat" | "split_with" | "spw"
12303 | "assoc" | "dissoc" | "get_in" | "gin" | "assoc_in" | "ain" | "update_in" | "uin"
12304 | "into" | "empty_clj" | "empc" | "seq" | "vec_clj" | "vecc"
12305 | "apply" | "appl"
12306 | "divmod" | "dm" | "accumulate" | "accum" | "starmap" | "smap"
12308 | "zip_longest" | "zipl" | "combinations" | "comb" | "permutations" | "perm"
12309 | "cartesian_product" | "cprod" | "compress" | "cmpr" | "filterfalse" | "falf"
12310 | "islice" | "isl" | "chain_from" | "chfr" | "pairwise_iter" | "pwi"
12311 | "tee_iter" | "teei" | "groupby_iter" | "gbi"
12312 | "each_slice" | "eslice" | "each_cons" | "econs"
12313 | "one" | "none_match" | "nonem"
12314 | "find_index_fn" | "fidx" | "rindex_fn" | "ridx"
12315 | "minmax" | "mmx" | "minmax_by" | "mmxb"
12316 | "dig" | "values_at" | "vat" | "fetch_val" | "fv" | "slice_arr" | "sla"
12317 | "transform_keys" | "tkeys" | "transform_values" | "tvals"
12318 | "sum_by" | "sumb" | "uniq_by" | "uqb"
12319 | "flat_map_fn" | "fmf" | "then_fn" | "thfn" | "times_fn" | "timf"
12320 | "step" | "upto" | "downto"
12321 | "find_last" | "fndl" | "find_last_index" | "fndli"
12323 | "at_index" | "ati" | "replace_at" | "repa"
12324 | "to_sorted" | "tsrt" | "to_reversed" | "trev" | "to_spliced" | "tspl"
12325 | "flat_depth" | "fltd" | "fill_arr" | "filla" | "includes_val" | "incv"
12326 | "object_keys" | "okeys" | "object_values" | "ovals"
12327 | "object_entries" | "oents" | "object_from_entries" | "ofents"
12328 | "span_fn" | "spanf" | "break_fn" | "brkf" | "group_runs" | "gruns"
12330 | "nub" | "sort_on" | "srton"
12331 | "intersperse_val" | "isp" | "intercalate" | "ical"
12332 | "replicate_val" | "repv" | "elem_of" | "elof" | "not_elem" | "ntelm"
12333 | "lookup_assoc" | "lkpa" | "scanl" | "scanr" | "unfoldr" | "unfr"
12334 | "find_map" | "fndm" | "filter_map" | "fltm" | "fold_right" | "fldr"
12336 | "partition_either" | "peith" | "try_fold" | "tfld"
12337 | "map_while" | "mapw" | "inspect" | "insp"
12338 | "tally_by" | "talb" | "sole" | "chunk_while" | "chkw" | "count_while" | "cntw"
12340 | "insert_at" | "insa" | "delete_at" | "dela" | "update_at" | "upda"
12342 | "split_on" | "spon" | "words_from" | "wfrm" | "unwords" | "unwds"
12343 | "lines_from" | "lfrm" | "unlines" | "unlns"
12344 | "window_n" | "winn" | "adjacent_pairs" | "adjp"
12345 | "zip_all" | "zall" | "unzip_pairs" | "uzp"
12346 | "interpose" | "ipos" | "partition_n" | "partn"
12347 | "map_indexed" | "mapi" | "reduce_indexed" | "redi" | "filter_indexed" | "flti"
12348 | "group_by_fn" | "gbf" | "index_by" | "idxb" | "associate" | "assoc_fn"
12349 | "combinations_rep" | "combrep" | "inits" | "tails" | "subsequences" | "subseqs"
12351 | "nub_by" | "nubb" | "slice_when" | "slcw" | "slice_before" | "slcb" | "slice_after" | "slca"
12352 | "each_with_object" | "ewo" | "reduce_right" | "redr"
12353 | "is_sorted_by" | "issrtb" | "intersperse_with" | "ispw"
12354 | "running_reduce" | "runred" | "windowed_circular" | "wincirc"
12355 | "distinct_by" | "distb" | "average" | "mean" | "copy_within" | "cpyw"
12356 | "and_list" | "andl" | "or_list" | "orl" | "concat_map" | "cmap"
12357 | "elem_index" | "elidx" | "elem_indices" | "elidxs" | "find_indices" | "fndidxs"
12358 | "delete_first" | "delfst" | "delete_by" | "delby" | "insert_sorted" | "inssrt"
12359 | "union_list" | "unionl" | "intersect_list" | "intl"
12360 | "maximum_by" | "maxby" | "minimum_by" | "minby" | "batched" | "btch"
12361 | "match_all" | "mall" | "capture_groups" | "capg" | "is_match" | "ism"
12363 | "split_regex" | "splre" | "replace_regex" | "replre"
12364 | "is_ascii" | "isasc" | "to_ascii" | "toasc"
12365 | "char_at" | "chat" | "code_point_at" | "cpat" | "from_code_point" | "fcp"
12366 | "normalize_spaces" | "nrmsp" | "remove_whitespace" | "rmws"
12367 | "pluralize" | "plur" | "ordinalize" | "ordn"
12368 | "parse_int" | "pint" | "parse_float" | "pflt" | "parse_bool" | "pbool"
12369 | "levenshtein" | "lev" | "soundex" | "sdx" | "similarity" | "sim"
12370 | "common_prefix" | "cpfx" | "common_suffix" | "csfx"
12371 | "wrap_text" | "wrpt" | "dedent" | "ddt" | "indent" | "idt"
12372 | "lerp" | "inv_lerp" | "ilerp" | "smoothstep" | "smst" | "remap"
12374 | "dot_product" | "dotp" | "cross_product" | "crossp"
12375 | "matrix_mul" | "matmul" | "mm"
12376 | "magnitude" | "mag" | "normalize_vec" | "nrmv"
12377 | "distance" | "dist" | "manhattan_distance" | "mdist"
12378 | "covariance" | "cov" | "correlation" | "corr"
12379 | "iqr" | "quantile" | "qntl" | "clamp_int" | "clpi"
12380 | "in_range" | "inrng" | "wrap_range" | "wrprng"
12381 | "sum_squares" | "sumsq" | "rms" | "cumsum" | "csum" | "cumprod" | "cprod_acc" | "diff"
12382 | "add_days" | "addd" | "add_hours" | "addh" | "add_minutes" | "addm"
12384 | "diff_days" | "diffd" | "diff_hours" | "diffh"
12385 | "start_of_day" | "sod" | "end_of_day" | "eod"
12386 | "start_of_hour" | "soh" | "start_of_minute" | "som"
12387 | "urle" | "urld"
12389 | "html_encode" | "htmle" | "html_decode" | "htmld"
12390 | "adler32" | "adl32" | "fnv1a" | "djb2"
12391 | "is_credit_card" | "iscc" | "is_isbn10" | "isbn10" | "is_isbn13" | "isbn13"
12393 | "is_iban" | "isiban" | "is_hex_str" | "ishex" | "is_binary_str" | "isbin"
12394 | "is_octal_str" | "isoct" | "is_json" | "isjson" | "is_base64" | "isb64"
12395 | "is_semver" | "issv" | "is_slug" | "isslug" | "slugify" | "slug"
12396 | "mode_stat" | "mstat" | "sampn" | "weighted_sample" | "wsamp"
12398 | "shuffle_arr" | "shuf" | "argmax" | "amax" | "argmin" | "amin"
12399 | "argsort" | "asrt" | "rank" | "rnk" | "dense_rank" | "drnk"
12400 | "partition_point" | "ppt" | "lower_bound" | "lbound"
12401 | "upper_bound" | "ubound" | "equal_range" | "eqrng"
12402 | "matrix_add" | "madd" | "matrix_sub" | "msub" | "matrix_mult" | "mmult"
12404 | "matrix_scalar" | "mscal" | "matrix_identity" | "mident"
12405 | "matrix_zeros" | "mzeros" | "matrix_ones" | "mones"
12406 | "matrix_diag" | "mdiag" | "matrix_trace" | "mtrace"
12407 | "matrix_row" | "mrow" | "matrix_col" | "mcol"
12408 | "matrix_shape" | "mshape" | "matrix_det" | "mdet"
12409 | "matrix_scale" | "mat_scale" | "diagonal" | "diag"
12410 | "topological_sort" | "toposort" | "bfs_traverse" | "bfs"
12412 | "dfs_traverse" | "dfs" | "shortest_path_bfs" | "spbfs"
12413 | "connected_components_graph" | "ccgraph"
12414 | "has_cycle_graph" | "hascyc" | "is_bipartite_graph" | "isbip"
12415 | "is_ipv4_addr" | "isip4" | "is_ipv6_addr" | "isip6"
12417 | "is_mac_addr" | "ismac" | "is_port_num" | "isport"
12418 | "is_hostname_valid" | "ishost"
12419 | "is_iso_date" | "isisodt" | "is_iso_time" | "isisotm"
12420 | "is_iso_datetime" | "isisodtm"
12421 | "is_phone_num" | "isphone" | "is_us_zip" | "iszip"
12422 | "word_wrap_text" | "wwrap" | "center_text" | "ctxt"
12424 | "ljust_text" | "ljt" | "rjust_text" | "rjt" | "zfill_num" | "zfill"
12425 | "remove_all_str" | "rmall" | "replace_n_times" | "repln"
12426 | "find_all_indices" | "fndalli"
12427 | "text_between" | "txbtwn" | "text_before" | "txbef" | "text_after" | "txaft"
12428 | "text_before_last" | "txbefl" | "text_after_last" | "txaftl"
12429 | "is_even_num" | "iseven" | "is_odd_num" | "isodd"
12431 | "is_positive_num" | "ispos" | "is_negative_num" | "isneg"
12432 | "is_zero_num" | "iszero" | "is_whole_num" | "iswhole"
12433 | "log_with_base" | "logb" | "nth_root_of" | "nroot"
12434 | "frac_part" | "fracp" | "reciprocal_of" | "recip"
12435 | "copy_sign" | "cpsgn" | "fused_mul_add" | "fmadd"
12436 | "floor_mod" | "fmod" | "floor_div_op" | "fdivop"
12437 | "signum_of" | "sgnum" | "midpoint_of" | "midpt"
12438 | "longest_run" | "lrun" | "longest_increasing" | "linc"
12440 | "longest_decreasing" | "ldec" | "max_sum_subarray" | "maxsub"
12441 | "majority_element" | "majority" | "kth_largest" | "kthl"
12442 | "kth_smallest" | "kths" | "count_inversions" | "cinv"
12443 | "is_monotonic" | "ismono" | "equilibrium_index" | "eqidx"
12444 | "jaccard_index" | "jaccard" | "dice_coefficient" | "dicecoef"
12446 | "overlap_coefficient" | "overlapcoef"
12447 | "power_set" | "powerset" | "cartesian_power" | "cartpow"
12448 | "is_isogram" | "isiso" | "is_heterogram" | "ishet"
12450 | "hamdist" | "jaro_similarity" | "jarosim"
12451 | "longest_common_substring" | "lcsub"
12452 | "longest_common_subsequence" | "lcseq"
12453 | "count_words" | "wcount" | "count_lines" | "lcount"
12454 | "count_chars" | "ccount" | "count_bytes" | "bcount"
12455 | "binomial" | "binom" | "catalan" | "catn" | "pascal_row" | "pascrow"
12457 | "is_coprime" | "iscopr" | "euler_totient" | "etot"
12458 | "mobius" | "mob" | "is_squarefree" | "issqfr"
12459 | "digital_root" | "digroot" | "is_narcissistic" | "isnarc"
12460 | "is_harshad" | "isharsh" | "is_kaprekar" | "iskap"
12461 | "day_of_year" | "doy" | "week_of_year" | "woy"
12463 | "days_in_month_fn" | "daysinmo" | "is_valid_date" | "isvdate"
12464 | "age_in_years" | "ageyrs"
12465 | "when_true" | "when_false" | "if_else" | "clamp_fn"
12468 | "attempt" | "try_fn" | "safe_div" | "safe_mod" | "safe_sqrt" | "safe_log"
12469 | "juxt2" | "juxt3" | "tap_val" | "debug_val" | "converge"
12470 | "iterate_n" | "unfold" | "arity_of" | "is_callable"
12471 | "coalesce" | "default_to" | "fallback"
12472 | "apply_list" | "zip_apply" | "scan"
12473 | "keep_if" | "reject_if" | "group_consecutive"
12474 | "after_n" | "before_n" | "clamp_list" | "normalize_list" | "softmax"
12475
12476 | "matrix_multiply" | "mat_mul"
12480 | "identity_matrix" | "eye" | "zeros_matrix" | "zeros" | "ones_matrix" | "ones"
12481
12482
12483
12484 | "vec_normalize" | "unit_vec" | "vec_add" | "vec_sub" | "vec_scale"
12485 | "linspace" | "arange"
12486 | "re_test" | "re_find_all" | "re_groups" | "re_escape"
12488 | "re_split_limit" | "glob_to_regex" | "is_regex_valid"
12489 | "cwd" | "pwd_str" | "cpu_count" | "is_root" | "uptime_secs"
12491 | "env_pairs" | "env_set" | "env_remove" | "hostname_str" | "is_tty" | "signal_name"
12492 | "stack_new" | "queue_new" | "lru_new"
12494 | "counter" | "counter_most_common" | "defaultdict" | "ordered_set"
12495 | "bitset_new" | "bitset_set" | "bitset_test" | "bitset_clear"
12496 | "abs_ceil" | "abs_each" | "abs_floor" | "ceil_each" | "dec_each"
12498 | "double_each" | "floor_each" | "half_each" | "inc_each" | "length_each"
12499 | "negate_each" | "not_each" | "offset_each" | "reverse_each" | "round_each"
12500 | "scale_each" | "sqrt_each" | "square_each" | "to_float_each" | "to_int_each"
12501 | "trim_each" | "type_each" | "upcase_each" | "downcase_each" | "bool_each"
12502 | "avogadro" | "boltzmann" | "golden_ratio" | "gravity" | "ln10" | "ln2"
12504 | "planck" | "speed_of_light" | "sqrt2"
12505 | "bmi_calc" | "compound_interest" | "dew_point" | "discount_amount"
12507 | "force_mass_acc" | "freq_wavelength" | "future_value" | "haversine"
12508 | "heat_index" | "kinetic_energy" | "margin_price" | "markup_price"
12509 | "mortgage_payment" | "ohms_law_i" | "ohms_law_r" | "ohms_law_v"
12510 | "potential_energy" | "present_value" | "simple_interest" | "speed_distance_time"
12511 | "tax_amount" | "tip_amount" | "wavelength_freq" | "wind_chill"
12512 | "angle_between_deg" | "approx_eq" | "chebyshev_distance" | "copysign"
12514 | "cosine_similarity" | "cube_root" | "entropy" | "float_bits" | "fma"
12515 | "int_bits" | "jaccard_similarity" | "log_base" | "mae" | "mse" | "nth_root"
12516 | "r_squared" | "reciprocal" | "relu" | "rmse" | "rotate_point" | "round_to"
12517 | "sigmoid" | "signum" | "square_root"
12518 | "cubes_seq" | "fibonacci_seq" | "powers_of_seq" | "primes_seq"
12520 | "squares_seq" | "triangular_seq"
12521 | "alternate_case" | "angle_bracket" | "bracket" | "byte_length"
12523 | "bytes_to_hex_str" | "camel_words" | "char_length" | "chars_to_string"
12524 | "chomp_str" | "chop_str" | "filter_chars" | "from_csv_line" | "hex_to_bytes"
12525 | "insert_str" | "intersperse_char" | "ljust" | "map_chars" | "mirror_string"
12526 | "normalize_whitespace" | "only_alnum" | "only_alpha" | "only_ascii"
12527 | "only_digits" | "parenthesize" | "remove_str" | "repeat_string" | "rjust"
12528 | "sentence_case" | "string_count" | "string_sort" | "string_to_chars"
12529 | "string_unique_chars" | "substring" | "to_csv_line" | "trim_left" | "trim_right"
12530 | "xor_strings"
12531 | "adjacent_difference" | "append_elem" | "consecutive_pairs" | "contains_elem"
12533 | "count_elem" | "drop_every" | "duplicate_count" | "elem_at" | "find_first"
12534 | "first_elem" | "flatten_once" | "fold_left" | "from_digits" | "from_pairs"
12535 | "group_by_size" | "hash_filter_keys" | "hash_from_list" | "hash_map_values"
12536 | "hash_merge_deep" | "hash_to_list" | "hash_zip" | "head_n" | "histogram_bins"
12537 | "index_of_elem" | "init_list" | "interleave_lists" | "last_elem" | "least_common"
12538 | "list_compact" | "list_eq" | "list_flatten_deep" | "max_list" | "mean_list"
12539 | "min_list" | "mode_list" | "most_common" | "partition_two" | "prefix_sums"
12540 | "prepend" | "product_list" | "remove_at" | "remove_elem" | "remove_first_elem"
12541 | "repeat_elem" | "running_max" | "running_min" | "sample_one" | "scan_left"
12542 | "second_elem" | "span" | "suffix_sums" | "sum_list" | "tail_n" | "take_every"
12543 | "third_elem" | "to_array" | "to_pairs" | "trimmed_mean" | "unique_count_of"
12544 | "wrap_index" | "digits_of"
12545 | "all_match" | "any_match" | "is_between" | "is_blank_or_nil" | "is_divisible_by"
12547 | "is_email" | "is_even" | "is_falsy" | "is_fibonacci" | "is_hex_color"
12548 | "is_in_range" | "is_ipv4" | "is_multiple_of" | "is_negative" | "is_nil"
12549 | "is_nonzero" | "is_odd" | "is_perfect_square" | "is_positive" | "is_power_of"
12550 | "is_prefix" | "is_present" | "is_strictly_decreasing" | "is_strictly_increasing"
12551 | "is_suffix" | "is_triangular" | "is_truthy" | "is_url" | "is_whole" | "is_zero"
12552 | "count_digits" | "count_letters" | "count_lower" | "count_match"
12554 | "count_punctuation" | "count_spaces" | "count_upper" | "defined_count"
12555 | "empty_count" | "falsy_count" | "nonempty_count" | "numeric_count"
12556 | "truthy_count" | "undef_count"
12557 | "assert_type" | "between" | "clamp_each" | "die_if" | "die_unless"
12559 | "join_colons" | "join_commas" | "join_dashes" | "join_dots" | "join_lines"
12560 | "join_pipes" | "join_slashes" | "join_spaces" | "join_tabs" | "measure"
12561 | "max_float" | "min_float" | "noop_val" | "nop" | "pass" | "pred" | "succ"
12562 | "tap_debug" | "to_bool" | "to_float" | "to_int" | "to_string" | "void"
12563 | "range_exclusive" | "range_inclusive"
12564 | "aliquot_sum" | "autocorrelation" | "bell_number" | "cagr" | "coeff_of_variation"
12566 | "collatz_length" | "collatz_sequence" | "convolution" | "cross_entropy"
12567 | "depreciation_double" | "depreciation_linear" | "discount" | "divisors"
12568 | "epsilon" | "euclidean_distance" | "euler_number" | "exponential_moving_average"
12569 | "f64_max" | "f64_min" | "fft_magnitude" | "goldbach" | "i64_max" | "i64_min"
12570 | "kurtosis" | "linear_regression" | "look_and_say" | "lucas" | "luhn_check"
12571 | "mean_absolute_error" | "mean_squared_error" | "median_absolute_deviation"
12572 | "minkowski_distance" | "moving_average" | "multinomial" | "neg_inf" | "npv"
12573 | "num_divisors" | "partition_number" | "pascals_triangle" | "skewness"
12574 | "standard_error" | "subfactorial" | "sum_divisors" | "totient_sum"
12575 | "tribonacci" | "weighted_mean" | "winsorize"
12576 | "chi_square_stat" | "describe" | "five_number_summary"
12578 | "gini" | "gini_coefficient" | "lorenz_curve" | "outliers_iqr"
12579 | "percentile_rank" | "quartiles" | "sample_stddev" | "sample_variance"
12580 | "spearman_correlation" | "t_test_one_sample" | "t_test_two_sample"
12581 | "z_score" | "z_scores"
12582 | "abundant_numbers" | "deficient_numbers" | "is_abundant" | "is_deficient"
12584 | "is_pentagonal" | "is_perfect" | "is_smith" | "next_prime" | "nth_prime"
12585 | "pentagonal_number" | "perfect_numbers" | "prev_prime" | "prime_factors"
12586 | "prime_pi" | "primes_up_to" | "triangular_number" | "twin_primes"
12587 | "area_circle" | "area_ellipse" | "area_rectangle" | "area_trapezoid" | "area_triangle"
12589 | "bearing" | "circumference" | "cone_volume" | "cylinder_volume" | "heron_area"
12590 | "midpoint" | "perimeter_rectangle" | "perimeter_triangle" | "point_distance"
12591 | "polygon_area" | "slope" | "sphere_surface" | "sphere_volume" | "triangle_hypotenuse"
12592 | "angle_between" | "arc_length" | "bounding_box" | "centroid"
12594 | "circle_from_three_points" | "convex_hull" | "ellipse_perimeter"
12595 | "frustum_volume" | "haversine_distance" | "line_intersection"
12596 | "point_in_polygon" | "polygon_perimeter" | "pyramid_volume"
12597 | "reflect_point" | "scale_point" | "sector_area"
12598 | "torus_surface" | "torus_volume" | "translate_point"
12599 | "vector_angle" | "vector_cross" | "vector_dot" | "vector_magnitude" | "vector_normalize"
12600 | "avogadro_number" | "boltzmann_constant" | "electron_mass" | "elementary_charge"
12602 | "gravitational_constant" | "phi" | "pi" | "planck_constant" | "proton_mass"
12603 | "sol" | "tau"
12604 | "bac_estimate" | "bmi" | "break_even" | "margin" | "markup" | "roi" | "tax" | "tip"
12606 | "amortization_schedule" | "black_scholes_call" | "black_scholes_put"
12608 | "bond_price" | "bond_yield" | "capm" | "continuous_compound"
12609 | "discounted_payback" | "duration" | "irr"
12610 | "max_drawdown" | "modified_duration" | "nper" | "num_periods" | "payback_period"
12611 | "pmt" | "pv" | "rule_of_72" | "sharpe_ratio" | "sortino_ratio"
12612 | "wacc" | "xirr"
12613 | "acronym" | "atbash" | "bigrams" | "camel_to_snake" | "char_frequencies"
12615 | "chunk_string" | "collapse_whitespace" | "dedent_text" | "indent_text"
12616 | "initials" | "leetspeak" | "mask_string" | "ngrams" | "pig_latin"
12617 | "remove_consonants" | "remove_vowels" | "reverse_each_word" | "snake_to_camel"
12618 | "sort_words" | "string_distance" | "string_multiply" | "strip_html"
12619 | "trigrams" | "unique_words" | "word_frequencies" | "zalgo"
12620 | "braille_encode" | "double_metaphone" | "metaphone" | "morse_decode"
12622 | "morse_encode" | "nato_phonetic" | "phonetic_digit" | "subscript" | "superscript"
12623 | "to_emoji_num"
12624 | "int_to_roman" | "roman_add" | "roman_numeral_list" | "roman_to_int"
12626 | "base_convert" | "binary_to_gray" | "gray_code_sequence" | "gray_to_binary"
12628 | "ansi_256" | "ansi_truecolor" | "color_blend" | "color_complement"
12630 | "color_darken" | "color_distance" | "color_grayscale" | "color_invert"
12631 | "color_lighten" | "hsl_to_rgb" | "hsv_to_rgb" | "random_color"
12632 | "rgb_to_hsl" | "rgb_to_hsv"
12633 | "matrix_flatten" | "matrix_from_rows" | "matrix_hadamard" | "matrix_inverse"
12635 | "matrix_map" | "matrix_max" | "matrix_min" | "matrix_power" | "matrix_sum"
12636 | "matrix_transpose"
12637 | "binary_insert" | "bucket" | "clamp_array" | "group_consecutive_by"
12639 | "histogram" | "merge_sorted" | "next_permutation" | "normalize_array"
12640 | "normalize_range" | "peak_detect" | "range_compress" | "range_expand"
12641 | "reservoir_sample" | "run_length_decode_str" | "run_length_encode_str"
12642 | "zero_crossings"
12643 | "apply_window" | "bandpass_filter" | "cross_correlation" | "dft"
12645 | "downsample" | "energy" | "envelope" | "highpass_filter" | "idft"
12646 | "lowpass_filter" | "median_filter" | "normalize_signal" | "phase_spectrum"
12647 | "power_spectrum" | "resample" | "spectral_centroid" | "spectrogram" | "upsample"
12648 | "window_blackman" | "window_hamming" | "window_hann" | "window_kaiser"
12649 | "is_anagram" | "is_balanced_parens" | "is_control" | "is_numeric_string"
12651 | "is_pangram" | "is_printable" | "is_valid_cidr" | "is_valid_cron"
12652 | "is_valid_hex_color" | "is_valid_latitude" | "is_valid_longitude" | "is_valid_mime"
12653 | "eval_rpn" | "fizzbuzz" | "game_of_life_step" | "mandelbrot_char"
12655 | "sierpinski" | "tower_of_hanoi" | "truth_table"
12656 | "byte_size" | "degrees_to_compass" | "to_string_val" | "type_of"
12658 | "quadratic_roots" | "quadratic_discriminant" | "arithmetic_series"
12660 | "geometric_series" | "stirling_approx"
12661 | "double_factorial" | "rising_factorial" | "falling_factorial"
12662 | "gamma_approx" | "erf_approx" | "normal_pdf" | "normal_cdf"
12663 | "poisson_pmf" | "exponential_pdf" | "inverse_lerp"
12664 | "map_range"
12665 | "momentum" | "impulse" | "work" | "power_phys" | "torque" | "angular_velocity"
12667 | "centripetal_force" | "escape_velocity" | "orbital_velocity" | "orbital_period"
12668 | "gravitational_force" | "coulomb_force" | "electric_field" | "capacitance"
12669 | "capacitor_energy" | "inductor_energy" | "resonant_frequency"
12670 | "rc_time_constant" | "rl_time_constant" | "impedance_rlc"
12671 | "relativistic_mass" | "lorentz_factor" | "time_dilation" | "length_contraction"
12672 | "relativistic_energy" | "rest_energy" | "de_broglie_wavelength"
12673 | "photon_energy" | "photon_energy_wavelength" | "schwarzschild_radius"
12674 | "stefan_boltzmann" | "wien_displacement" | "ideal_gas_pressure" | "ideal_gas_volume"
12675 | "projectile_range" | "projectile_max_height" | "projectile_time"
12676 | "spring_force" | "spring_energy" | "pendulum_period" | "doppler_frequency"
12677 | "decibel_ratio" | "snells_law" | "brewster_angle" | "critical_angle"
12678 | "lens_power" | "thin_lens" | "magnification_lens"
12679 | "euler_mascheroni" | "apery_constant" | "feigenbaum_delta" | "feigenbaum_alpha"
12681 | "catalan_constant" | "khinchin_constant" | "glaisher_constant"
12682 | "plastic_number" | "silver_ratio" | "supergolden_ratio"
12683 | "vacuum_permittivity" | "vacuum_permeability" | "coulomb_constant"
12685 | "fine_structure_constant" | "rydberg_constant" | "bohr_radius"
12686 | "bohr_magneton" | "nuclear_magneton" | "stefan_boltzmann_constant"
12687 | "wien_constant" | "gas_constant" | "faraday_constant" | "neutron_mass"
12688 | "atomic_mass_unit" | "earth_mass" | "earth_radius" | "sun_mass" | "sun_radius"
12689 | "astronomical_unit" | "light_year" | "parsec" | "hubble_constant"
12690 | "planck_length" | "planck_time" | "planck_mass" | "planck_temperature"
12691 | "matrix_solve" | "msolve" | "solve"
12693 | "matrix_lu" | "mlu" | "matrix_qr" | "mqr"
12694 | "matrix_eigenvalues" | "meig" | "eigenvalues" | "eig"
12695 | "matrix_norm" | "mnorm" | "matrix_cond" | "mcond" | "cond"
12696 | "matrix_pinv" | "mpinv" | "pinv"
12697 | "matrix_cholesky" | "mchol" | "cholesky"
12698 | "matrix_det_general" | "mdetg" | "det"
12699 | "welch_ttest" | "welcht" | "paired_ttest" | "pairedt"
12701 | "cohen_d" | "cohend" | "anova_oneway" | "anova" | "anova1"
12702 | "spearman_corr" | "rho" | "kendall_tau" | "kendall" | "ktau"
12703 | "confidence_interval" | "ci"
12704 | "beta_pdf" | "betapdf" | "gamma_pdf" | "gammapdf"
12706 | "chi2_pdf" | "chi2pdf" | "chi_squared_pdf"
12707 | "t_pdf" | "tpdf" | "student_pdf"
12708 | "f_pdf" | "fpdf" | "fisher_pdf"
12709 | "lognormal_pdf" | "lnormpdf" | "weibull_pdf" | "weibpdf"
12710 | "cauchy_pdf" | "cauchypdf" | "laplace_pdf" | "laplacepdf"
12711 | "pareto_pdf" | "paretopdf"
12712 | "lagrange_interp" | "lagrange" | "linterp"
12714 | "cubic_spline" | "cspline" | "spline"
12715 | "poly_eval" | "polyval" | "polynomial_fit" | "polyfit"
12716 | "trapz" | "trapezoid" | "simpson" | "simps"
12718 | "numerical_diff" | "numdiff" | "diff_array"
12719 | "cumtrapz" | "cumulative_trapz"
12720 | "bisection" | "bisect" | "newton_method" | "newton" | "newton_raphson"
12722 | "golden_section" | "golden" | "gss"
12723 | "rk4" | "runge_kutta" | "rk4_ode" | "euler_ode" | "euler_method"
12725 | "dijkstra" | "shortest_path" | "bellman_ford" | "bellmanford"
12727 | "floyd_warshall" | "floydwarshall" | "apsp"
12728 | "prim_mst" | "mst" | "prim"
12729 | "cot" | "sec" | "csc" | "acot" | "asec" | "acsc" | "sinc" | "versin" | "versine"
12731 | "leaky_relu" | "lrelu" | "elu" | "selu" | "gelu"
12733 | "silu" | "swish" | "mish" | "softplus"
12734 | "hard_sigmoid" | "hardsigmoid" | "hard_swish" | "hardswish"
12735 | "bessel_j0" | "j0" | "bessel_j1" | "j1"
12737 | "lambert_w" | "lambertw" | "productlog"
12738 | "mod_exp" | "modexp" | "powmod"
12740 | "mod_inv" | "modinv" | "chinese_remainder" | "crt"
12741 | "miller_rabin" | "millerrabin" | "is_probable_prime"
12742 | "derangements" | "stirling2" | "stirling_second"
12744 | "bernoulli_number" | "bernoulli" | "harmonic_number" | "harmonic"
12745 | "drag_force" | "fdrag" | "ideal_gas" | "pv_nrt"
12747 | "bs_delta" | "bsdelta" | "option_delta"
12749 | "bs_gamma" | "bsgamma" | "option_gamma"
12750 | "bs_vega" | "bsvega" | "option_vega"
12751 | "bs_theta" | "bstheta" | "option_theta"
12752 | "bs_rho" | "bsrho" | "option_rho"
12753 | "bond_duration" | "mac_duration"
12754 | "dct" | "idct" | "goertzel" | "chirp" | "chirp_signal"
12756 | "base85_encode" | "b85e" | "ascii85_encode" | "a85e"
12758 | "base85_decode" | "b85d" | "ascii85_decode" | "a85d"
12759 | "pnorm" | "qnorm" | "pbinom" | "dbinom" | "ppois"
12761 | "punif" | "pexp" | "pweibull" | "plnorm" | "pcauchy"
12762 | "rbind" | "cbind"
12764 | "row_sums" | "rowSums" | "col_sums" | "colSums"
12765 | "row_means" | "rowMeans" | "col_means" | "colMeans"
12766 | "outer_product" | "outer" | "crossprod" | "tcrossprod"
12767 | "nrow" | "ncol" | "prop_table" | "proptable"
12768 | "cummax" | "cummin" | "scale_vec" | "scale"
12770 | "which_fn" | "tabulate"
12771 | "duplicated" | "duped" | "rev_vec"
12772 | "seq_fn" | "rep_fn" | "rep"
12773 | "cut_bins" | "cut" | "find_interval" | "findInterval"
12774 | "ecdf_fn" | "ecdf" | "density_est" | "density"
12775 | "embed_ts" | "embed"
12776 | "shapiro_test" | "shapiro" | "ks_test" | "ks"
12778 | "wilcox_test" | "wilcox" | "mann_whitney"
12779 | "prop_test" | "proptest" | "binom_test" | "binomtest"
12780 | "sapply" | "tapply" | "do_call" | "docall"
12782 | "kmeans" | "prcomp" | "pca"
12784 | "rnorm" | "runif" | "rexp" | "rbinom" | "rpois" | "rgeom"
12786 | "rgamma" | "rbeta" | "rchisq" | "rt" | "rf"
12787 | "rweibull" | "rlnorm" | "rcauchy"
12788 | "qunif" | "qexp" | "qweibull" | "qlnorm" | "qcauchy"
12790 | "pgamma" | "pbeta" | "pchisq" | "pt_cdf" | "pt" | "pf_cdf" | "pf"
12792 | "dgeom" | "dunif" | "dnbinom" | "dhyper"
12794 | "lowess" | "loess" | "approx_fn" | "approx"
12796 | "lm_fit" | "lm"
12798 | "qgamma" | "qbeta" | "qchisq" | "qt_fn" | "qt" | "qf_fn" | "qf"
12800 | "qbinom" | "qpois"
12801 | "acf_fn" | "acf" | "pacf_fn" | "pacf"
12803 | "diff_lag" | "diff_ts" | "ts_filter" | "filter_ts"
12804 | "predict_lm" | "predict" | "confint_lm" | "confint"
12806 | "cor_matrix" | "cor_mat" | "cov_matrix" | "cov_mat"
12808 | "mahalanobis" | "mahal" | "dist_matrix" | "dist_mat"
12809 | "hclust" | "cutree" | "weighted_var" | "wvar" | "cov2cor"
12810 | "scatter_svg" | "scatter_plot" | "line_svg" | "line_plot"
12812 | "plot_svg" | "hist_svg" | "histogram_svg"
12813 | "boxplot_svg" | "box_plot" | "bar_svg" | "barchart_svg"
12814 | "pie_svg" | "pie_chart" | "heatmap_svg" | "heatmap"
12815 | "donut_svg" | "donut" | "area_svg" | "area_chart"
12816 | "hbar_svg" | "hbar" | "radar_svg" | "radar" | "spider"
12817 | "candlestick_svg" | "candlestick" | "ohlc"
12818 | "violin_svg" | "violin" | "cor_heatmap" | "cor_matrix_svg"
12819 | "stacked_bar_svg" | "stacked_bar"
12820 | "wordcloud_svg" | "wordcloud" | "wcloud"
12821 | "treemap_svg" | "treemap"
12822 | "pvw"
12823 | "cyber_city" | "cyber_grid" | "cyber_rain" | "matrix_rain"
12825 | "cyber_glitch" | "glitch_text" | "cyber_banner" | "neon_banner"
12826 | "cyber_circuit" | "cyber_skull" | "cyber_eye"
12827 => Some(name),
12828 _ => None,
12829 }
12830 }
12831
12832 fn is_reserved_hash_name(name: &str) -> bool {
12835 matches!(
12836 name,
12837 "b" | "pc"
12838 | "e"
12839 | "a"
12840 | "d"
12841 | "c"
12842 | "p"
12843 | "all"
12844 | "stryke::builtins"
12845 | "stryke::perl_compats"
12846 | "stryke::extensions"
12847 | "stryke::aliases"
12848 | "stryke::descriptions"
12849 | "stryke::categories"
12850 | "stryke::primaries"
12851 | "stryke::all"
12852 )
12853 }
12854
12855 fn check_udf_shadows_builtin(&self, name: &str, line: usize) -> PerlResult<()> {
12858 if Self::is_known_bareword(name) || Self::is_try_builtin_name(name) {
12859 return Err(self.syntax_err(
12860 format!(
12861"`{name}` is a stryke builtin and cannot be redefined (this is not Perl 5; use `fn` not `sub`, or pass --compat)"
12862 ),
12863 line,
12864 ));
12865 }
12866 Ok(())
12867 }
12868
12869 fn check_hash_shadows_reserved(&self, name: &str, line: usize) -> PerlResult<()> {
12872 if Self::is_reserved_hash_name(name) {
12873 return Err(self.syntax_err(
12874 format!(
12875"`%{name}` is a stryke reserved hash and cannot be redefined (this is not Perl 5; pass --compat for Perl 5 mode)"
12876 ),
12877 line,
12878 ));
12879 }
12880 Ok(())
12881 }
12882
12883 fn validate_hash_assignment(&self, value: &Expr, line: usize) -> PerlResult<()> {
12886 match &value.kind {
12887 ExprKind::Integer(_) | ExprKind::Float(_) => {
12888 return Err(self.syntax_err(
12889 "cannot assign scalar to hash — use %h = (key => value) or %h = %{$hashref}",
12890 line,
12891 ));
12892 }
12893 ExprKind::String(_) | ExprKind::InterpolatedString(_) | ExprKind::Bareword(_) => {
12894 return Err(self.syntax_err(
12895 "cannot assign string to hash — use %h = (key => value) or %h = %{$hashref}",
12896 line,
12897 ));
12898 }
12899 ExprKind::ArrayRef(_) => {
12900 return Err(self.syntax_err(
12901 "cannot assign arrayref to hash — use %h = @{$arrayref} for even-length list",
12902 line,
12903 ));
12904 }
12905 ExprKind::ScalarRef(inner) => {
12906 if matches!(inner.kind, ExprKind::ArrayVar(_)) {
12907 return Err(self.syntax_err(
12908 "cannot assign \\@array to hash — use %h = @array for even-length list",
12909 line,
12910 ));
12911 }
12912 if matches!(inner.kind, ExprKind::HashVar(_)) {
12913 return Err(self.syntax_err(
12914 "cannot assign \\%hash to hash — use %h = %other directly",
12915 line,
12916 ));
12917 }
12918 }
12919 ExprKind::HashRef(_) => {
12920 return Err(self.syntax_err(
12921 "cannot assign hashref to hash — use %h = %{$hashref} to dereference",
12922 line,
12923 ));
12924 }
12925 ExprKind::CodeRef { .. } => {
12926 return Err(self.syntax_err("cannot assign coderef to hash", line));
12927 }
12928 ExprKind::Undef => {
12929 return Err(
12930 self.syntax_err("cannot assign undef to hash — use %h = () to empty", line)
12931 );
12932 }
12933 ExprKind::List(items)
12934 if items.len() % 2 != 0
12935 && !items.iter().any(|e| {
12936 matches!(
12937 e.kind,
12938 ExprKind::ArrayVar(_)
12939 | ExprKind::HashVar(_)
12940 | ExprKind::FuncCall { .. }
12941 | ExprKind::Deref { .. }
12942 | ExprKind::ScalarVar(_)
12943 )
12944 }) =>
12945 {
12946 return Err(self.syntax_err(
12947 format!(
12948 "odd-length list ({} elements) in hash assignment — missing value for last key",
12949 items.len()
12950 ),
12951 line,
12952 ));
12953 }
12954 _ => {}
12955 }
12956 Ok(())
12957 }
12958
12959 fn validate_array_assignment(&self, value: &Expr, line: usize) -> PerlResult<()> {
12964 if let ExprKind::Undef = &value.kind {
12965 return Err(
12966 self.syntax_err("cannot assign undef to array — use @a = () to empty", line)
12967 );
12968 }
12969 Ok(())
12970 }
12971
12972 fn validate_scalar_assignment(&self, value: &Expr, line: usize) -> PerlResult<()> {
12975 if let ExprKind::List(items) = &value.kind {
12976 if items.len() > 1 {
12977 return Err(self.syntax_err(
12978 format!(
12979 "cannot assign {}-element list to scalar — Perl 5 silently takes last element; use ($x) = (list) or $x = $list[-1]",
12980 items.len()
12981 ),
12982 line,
12983 ));
12984 }
12985 }
12986 Ok(())
12987 }
12988
12989 fn validate_assignment(&self, target: &Expr, value: &Expr, line: usize) -> PerlResult<()> {
12991 if crate::compat_mode() {
12992 return Ok(());
12993 }
12994 match &target.kind {
12995 ExprKind::HashVar(_) => self.validate_hash_assignment(value, line),
12996 ExprKind::ArrayVar(_) => self.validate_array_assignment(value, line),
12997 ExprKind::ScalarVar(_) => self.validate_scalar_assignment(value, line),
12998 _ => Ok(()),
12999 }
13000 }
13001
13002 fn parse_block_or_bareword_cmp_block(&mut self) -> PerlResult<Block> {
13006 if matches!(self.peek(), Token::LBrace) {
13007 return self.parse_block();
13008 }
13009 let line = self.peek_line();
13010 if let Token::Ident(ref name) = self.peek().clone() {
13012 if matches!(
13013 self.peek_at(1),
13014 Token::Comma | Token::Semicolon | Token::RBrace | Token::Eof | Token::PipeForward
13015 ) {
13016 let name = name.clone();
13017 self.advance();
13018 let body = Expr {
13019 kind: ExprKind::FuncCall {
13020 name,
13021 args: vec![
13022 Expr {
13023 kind: ExprKind::ScalarVar("a".to_string()),
13024 line,
13025 },
13026 Expr {
13027 kind: ExprKind::ScalarVar("b".to_string()),
13028 line,
13029 },
13030 ],
13031 },
13032 line,
13033 };
13034 return Ok(vec![Statement::new(StmtKind::Expression(body), line)]);
13035 }
13036 }
13037 let expr = self.parse_assign_expr_stop_at_pipe()?;
13039 Ok(vec![Statement::new(StmtKind::Expression(expr), line)])
13040 }
13041
13042 fn parse_fan_optional_progress(
13044 &mut self,
13045 which: &'static str,
13046 ) -> PerlResult<Option<Box<Expr>>> {
13047 let line = self.peek_line();
13048 if self.eat(&Token::Comma) {
13049 match self.peek() {
13050 Token::Ident(ref kw)
13051 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) =>
13052 {
13053 self.advance();
13054 self.expect(&Token::FatArrow)?;
13055 return Ok(Some(Box::new(self.parse_assign_expr()?)));
13056 }
13057 _ => {
13058 return Err(self.syntax_err(
13059 format!("{which}: expected `progress => EXPR` after comma"),
13060 line,
13061 ));
13062 }
13063 }
13064 }
13065 if let Token::Ident(ref kw) = self.peek().clone() {
13066 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
13067 self.advance();
13068 self.expect(&Token::FatArrow)?;
13069 return Ok(Some(Box::new(self.parse_assign_expr()?)));
13070 }
13071 }
13072 Ok(None)
13073 }
13074
13075 fn parse_assign_expr_list_optional_progress(&mut self) -> PerlResult<(Expr, Option<Expr>)> {
13082 if self.in_pipe_rhs()
13088 && matches!(
13089 self.peek(),
13090 Token::Semicolon
13091 | Token::RBrace
13092 | Token::RParen
13093 | Token::Eof
13094 | Token::PipeForward
13095 | Token::Comma
13096 )
13097 {
13098 return Ok((self.pipe_placeholder_list(self.peek_line()), None));
13099 }
13100 let mut parts = vec![self.parse_assign_expr_stop_at_pipe()?];
13101 loop {
13102 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
13103 break;
13104 }
13105 if matches!(
13106 self.peek(),
13107 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof | Token::PipeForward
13108 ) {
13109 break;
13110 }
13111 if self.peek_is_postfix_stmt_modifier_keyword() {
13112 break;
13113 }
13114 if let Token::Ident(ref kw) = self.peek().clone() {
13115 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
13116 self.advance();
13117 self.expect(&Token::FatArrow)?;
13118 let prog = self.parse_assign_expr_stop_at_pipe()?;
13119 return Ok((merge_expr_list(parts), Some(prog)));
13120 }
13121 }
13122 parts.push(self.parse_assign_expr_stop_at_pipe()?);
13123 }
13124 Ok((merge_expr_list(parts), None))
13125 }
13126
13127 fn parse_one_arg(&mut self) -> PerlResult<Expr> {
13128 if matches!(self.peek(), Token::LParen) {
13129 self.advance();
13130 let expr = self.parse_expression()?;
13131 self.expect(&Token::RParen)?;
13132 Ok(expr)
13133 } else {
13134 self.parse_assign_expr_stop_at_pipe()
13135 }
13136 }
13137
13138 fn parse_one_arg_or_default(&mut self) -> PerlResult<Expr> {
13139 if matches!(
13146 self.peek(),
13147 Token::Semicolon
13149 | Token::RBrace
13150 | Token::RParen
13151 | Token::RBracket
13152 | Token::Eof
13153 | Token::Comma
13154 | Token::FatArrow
13155 | Token::PipeForward
13156 | Token::Question
13158 | Token::Colon
13159 | Token::NumEq | Token::NumNe | Token::NumLt | Token::NumGt
13161 | Token::NumLe | Token::NumGe | Token::Spaceship
13162 | Token::StrEq | Token::StrNe | Token::StrLt | Token::StrGt
13163 | Token::StrLe | Token::StrGe | Token::StrCmp
13164 | Token::LogAnd | Token::LogOr | Token::LogNot
13166 | Token::LogAndWord | Token::LogOrWord | Token::LogNotWord
13167 | Token::DefinedOr
13168 | Token::Range | Token::RangeExclusive
13170 | Token::Assign | Token::PlusAssign | Token::MinusAssign
13172 | Token::MulAssign | Token::DivAssign | Token::ModAssign
13173 | Token::PowAssign | Token::DotAssign | Token::AndAssign
13174 | Token::OrAssign | Token::XorAssign | Token::DefinedOrAssign
13175 | Token::ShiftLeftAssign | Token::ShiftRightAssign
13176 | Token::BitAndAssign | Token::BitOrAssign
13177 ) {
13178 return Ok(Expr {
13179 kind: ExprKind::ScalarVar("_".into()),
13180 line: self.peek_line(),
13181 });
13182 }
13183 if matches!(self.peek(), Token::LParen) && matches!(self.peek_at(1), Token::RParen) {
13187 let line = self.peek_line();
13188 self.advance(); self.advance(); return Ok(Expr {
13191 kind: ExprKind::ScalarVar("_".into()),
13192 line,
13193 });
13194 }
13195 self.parse_one_arg()
13196 }
13197
13198 fn parse_one_arg_or_argv(&mut self) -> PerlResult<Expr> {
13200 let line = self.prev_line(); if matches!(self.peek(), Token::LParen) {
13202 self.advance();
13203 if matches!(self.peek(), Token::RParen) {
13204 self.advance();
13205 return Ok(Expr {
13206 kind: ExprKind::ArrayVar("_".into()),
13207 line: self.peek_line(),
13208 });
13209 }
13210 let expr = self.parse_expression()?;
13211 self.expect(&Token::RParen)?;
13212 return Ok(expr);
13213 }
13214 if matches!(
13216 self.peek(),
13217 Token::Semicolon
13218 | Token::RBrace
13219 | Token::RParen
13220 | Token::Eof
13221 | Token::Comma
13222 | Token::PipeForward
13223 ) || self.peek_line() > line
13224 {
13225 Ok(Expr {
13226 kind: ExprKind::ArrayVar("_".into()),
13227 line,
13228 })
13229 } else {
13230 self.parse_assign_expr()
13231 }
13232 }
13233
13234 fn parse_builtin_args(&mut self) -> PerlResult<Vec<Expr>> {
13235 if matches!(self.peek(), Token::LParen) {
13236 self.advance();
13237 let args = self.parse_arg_list()?;
13238 self.expect(&Token::RParen)?;
13239 Ok(args)
13240 } else if self.suppress_parenless_call > 0 && matches!(self.peek(), Token::Ident(_)) {
13241 Ok(vec![])
13244 } else {
13245 self.parse_list_until_terminator()
13246 }
13247 }
13248
13249 #[inline]
13253 fn fat_arrow_autoquote(&self, name: &str, line: usize) -> Option<Expr> {
13254 if matches!(self.peek(), Token::FatArrow) {
13255 Some(Expr {
13256 kind: ExprKind::String(name.to_string()),
13257 line,
13258 })
13259 } else {
13260 None
13261 }
13262 }
13263
13264 fn parse_hash_subscript_key(&mut self) -> PerlResult<Expr> {
13269 let line = self.peek_line();
13270 if let Token::Ident(ref k) = self.peek().clone() {
13271 if matches!(self.peek_at(1), Token::RBrace) {
13272 let s = k.clone();
13273 self.advance();
13274 return Ok(Expr {
13275 kind: ExprKind::String(s),
13276 line,
13277 });
13278 }
13279 }
13280 self.parse_expression()
13281 }
13282
13283 #[inline]
13285 fn peek_is_glob_par_progress_kw(&self) -> bool {
13286 matches!(self.peek(), Token::Ident(ref kw) if kw == "progress")
13287 && matches!(self.peek_at(1), Token::FatArrow)
13288 }
13289
13290 fn parse_pattern_list_until_rparen_or_progress(&mut self) -> PerlResult<Vec<Expr>> {
13292 let mut args = Vec::new();
13293 loop {
13294 if matches!(self.peek(), Token::RParen | Token::Eof) {
13295 break;
13296 }
13297 if self.peek_is_glob_par_progress_kw() {
13298 break;
13299 }
13300 args.push(self.parse_assign_expr()?);
13301 match self.peek() {
13302 Token::RParen => break,
13303 Token::Comma => {
13304 self.advance();
13305 if matches!(self.peek(), Token::RParen) {
13306 break;
13307 }
13308 if self.peek_is_glob_par_progress_kw() {
13309 break;
13310 }
13311 }
13312 _ => {
13313 return Err(self.syntax_err(
13314 "expected `,`, `)`, or `progress =>` after argument in `glob_par` / `par_sed`",
13315 self.peek_line(),
13316 ));
13317 }
13318 }
13319 }
13320 Ok(args)
13321 }
13322
13323 fn parse_pattern_list_glob_par_bare(&mut self) -> PerlResult<Vec<Expr>> {
13325 let mut args = Vec::new();
13326 loop {
13327 if matches!(
13328 self.peek(),
13329 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof
13330 ) {
13331 break;
13332 }
13333 if self.peek_is_postfix_stmt_modifier_keyword() {
13334 break;
13335 }
13336 if self.peek_is_glob_par_progress_kw() {
13337 break;
13338 }
13339 args.push(self.parse_assign_expr()?);
13340 if !self.eat(&Token::Comma) {
13341 break;
13342 }
13343 if self.peek_is_glob_par_progress_kw() {
13344 break;
13345 }
13346 }
13347 Ok(args)
13348 }
13349
13350 fn parse_glob_par_or_par_sed_args(&mut self) -> PerlResult<(Vec<Expr>, Option<Box<Expr>>)> {
13352 if matches!(self.peek(), Token::LParen) {
13353 self.advance();
13354 let args = self.parse_pattern_list_until_rparen_or_progress()?;
13355 let progress = if self.peek_is_glob_par_progress_kw() {
13356 self.advance();
13357 self.expect(&Token::FatArrow)?;
13358 Some(Box::new(self.parse_assign_expr()?))
13359 } else {
13360 None
13361 };
13362 self.expect(&Token::RParen)?;
13363 Ok((args, progress))
13364 } else {
13365 let args = self.parse_pattern_list_glob_par_bare()?;
13366 let progress = if self.peek_is_glob_par_progress_kw() {
13368 self.advance();
13369 self.expect(&Token::FatArrow)?;
13370 Some(Box::new(self.parse_assign_expr()?))
13371 } else {
13372 None
13373 };
13374 Ok((args, progress))
13375 }
13376 }
13377
13378 pub(crate) fn parse_arg_list(&mut self) -> PerlResult<Vec<Expr>> {
13379 let mut args = Vec::new();
13380 let saved_no_pf = self.no_pipe_forward_depth;
13384 self.no_pipe_forward_depth = 0;
13385 while !matches!(
13386 self.peek(),
13387 Token::RParen | Token::RBracket | Token::RBrace | Token::Eof
13388 ) {
13389 let arg = match self.parse_assign_expr() {
13390 Ok(e) => e,
13391 Err(err) => {
13392 self.no_pipe_forward_depth = saved_no_pf;
13393 return Err(err);
13394 }
13395 };
13396 args.push(arg);
13397 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
13398 break;
13399 }
13400 }
13401 self.no_pipe_forward_depth = saved_no_pf;
13402 Ok(args)
13403 }
13404
13405 fn parse_method_arg_list_no_paren(&mut self) -> PerlResult<Vec<Expr>> {
13409 let mut args = Vec::new();
13410 let call_line = self.prev_line();
13411 loop {
13412 if args.is_empty() && matches!(self.peek(), Token::LBrace) {
13415 break;
13416 }
13417 if matches!(
13418 self.peek(),
13419 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof | Token::PipeForward
13420 ) {
13421 break;
13422 }
13423 if let Token::Ident(ref kw) = self.peek().clone() {
13424 if matches!(
13425 kw.as_str(),
13426 "if" | "unless" | "while" | "until" | "for" | "foreach"
13427 ) {
13428 break;
13429 }
13430 }
13431 if args.is_empty()
13434 && (self.peek_method_arg_infix_terminator() || matches!(self.peek(), Token::Comma))
13435 {
13436 break;
13437 }
13438 if args.is_empty() && self.peek_line() > call_line {
13441 break;
13442 }
13443 args.push(self.parse_assign_expr()?);
13444 if !self.eat(&Token::Comma) {
13445 break;
13446 }
13447 }
13448 Ok(args)
13449 }
13450
13451 fn peek_method_arg_infix_terminator(&self) -> bool {
13454 matches!(
13455 self.peek(),
13456 Token::Plus
13457 | Token::Minus
13458 | Token::Star
13459 | Token::Slash
13460 | Token::Percent
13461 | Token::Power
13462 | Token::Dot
13463 | Token::X
13464 | Token::NumEq
13465 | Token::NumNe
13466 | Token::NumLt
13467 | Token::NumGt
13468 | Token::NumLe
13469 | Token::NumGe
13470 | Token::Spaceship
13471 | Token::StrEq
13472 | Token::StrNe
13473 | Token::StrLt
13474 | Token::StrGt
13475 | Token::StrLe
13476 | Token::StrGe
13477 | Token::StrCmp
13478 | Token::LogAnd
13479 | Token::LogOr
13480 | Token::LogAndWord
13481 | Token::LogOrWord
13482 | Token::DefinedOr
13483 | Token::BitAnd
13484 | Token::BitOr
13485 | Token::BitXor
13486 | Token::ShiftLeft
13487 | Token::ShiftRight
13488 | Token::Range
13489 | Token::RangeExclusive
13490 | Token::BindMatch
13491 | Token::BindNotMatch
13492 | Token::Arrow
13493 | Token::Question
13495 | Token::Colon
13496 | Token::Assign
13498 | Token::PlusAssign
13499 | Token::MinusAssign
13500 | Token::MulAssign
13501 | Token::DivAssign
13502 | Token::ModAssign
13503 | Token::PowAssign
13504 | Token::DotAssign
13505 | Token::AndAssign
13506 | Token::OrAssign
13507 | Token::XorAssign
13508 | Token::DefinedOrAssign
13509 | Token::ShiftLeftAssign
13510 | Token::ShiftRightAssign
13511 | Token::BitAndAssign
13512 | Token::BitOrAssign
13513 )
13514 }
13515
13516 fn parse_list_until_terminator(&mut self) -> PerlResult<Vec<Expr>> {
13517 let mut args = Vec::new();
13518 let call_line = self.prev_line();
13523 loop {
13524 if matches!(
13525 self.peek(),
13526 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof | Token::PipeForward
13527 ) {
13528 break;
13529 }
13530 if let Token::Ident(ref kw) = self.peek().clone() {
13532 if matches!(
13533 kw.as_str(),
13534 "if" | "unless" | "while" | "until" | "for" | "foreach"
13535 ) {
13536 break;
13537 }
13538 }
13539 if args.is_empty() && self.peek_line() > call_line {
13546 break;
13547 }
13548 args.push(self.parse_assign_expr_stop_at_pipe()?);
13551 if !self.eat(&Token::Comma) {
13552 break;
13553 }
13554 }
13555 Ok(args)
13556 }
13557
13558 fn try_parse_hash_ref(&mut self) -> PerlResult<Vec<(Expr, Expr)>> {
13559 let mut pairs = Vec::new();
13560 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
13561 let line = self.peek_line();
13564 let key = if let Token::Ident(ref name) = self.peek().clone() {
13565 if matches!(self.peek_at(1), Token::FatArrow) {
13566 self.advance();
13567 Expr {
13568 kind: ExprKind::String(name.clone()),
13569 line,
13570 }
13571 } else {
13572 self.parse_assign_expr()?
13573 }
13574 } else {
13575 self.parse_assign_expr()?
13576 };
13577 if matches!(self.peek(), Token::RBrace | Token::Comma)
13581 && matches!(
13582 key.kind,
13583 ExprKind::HashVar(_)
13584 | ExprKind::Deref {
13585 kind: Sigil::Hash,
13586 ..
13587 }
13588 )
13589 {
13590 let sentinel_key = Expr {
13594 kind: ExprKind::String("__HASH_SPREAD__".into()),
13595 line,
13596 };
13597 pairs.push((sentinel_key, key));
13598 self.eat(&Token::Comma);
13599 continue;
13600 }
13601 if self.eat(&Token::FatArrow) || self.eat(&Token::Comma) {
13603 let val = self.parse_assign_expr()?;
13604 pairs.push((key, val));
13605 self.eat(&Token::Comma);
13606 } else {
13607 return Err(self.syntax_err("Expected => or , in hash ref", key.line));
13608 }
13609 }
13610 self.expect(&Token::RBrace)?;
13611 Ok(pairs)
13612 }
13613
13614 fn parse_hashref_pairs_until(&mut self, term: &Token) -> PerlResult<Vec<(Expr, Expr)>> {
13619 let mut pairs = Vec::new();
13620 while !matches!(&self.peek(), t if std::mem::discriminant(*t) == std::mem::discriminant(term))
13621 && !matches!(self.peek(), Token::Eof)
13622 {
13623 let line = self.peek_line();
13624 let key = if let Token::Ident(ref name) = self.peek().clone() {
13625 if matches!(self.peek_at(1), Token::FatArrow) {
13626 self.advance();
13627 Expr {
13628 kind: ExprKind::String(name.clone()),
13629 line,
13630 }
13631 } else {
13632 self.parse_assign_expr()?
13633 }
13634 } else {
13635 self.parse_assign_expr()?
13636 };
13637 if self.eat(&Token::FatArrow) || self.eat(&Token::Comma) {
13638 let val = self.parse_assign_expr()?;
13639 pairs.push((key, val));
13640 self.eat(&Token::Comma);
13641 } else {
13642 return Err(self.syntax_err("Expected => or , in hash ref", key.line));
13643 }
13644 }
13645 Ok(pairs)
13646 }
13647
13648 fn interp_chain_subscripts(
13654 &self,
13655 chars: &[char],
13656 i: &mut usize,
13657 mut base: Expr,
13658 line: usize,
13659 ) -> Expr {
13660 loop {
13661 let (after, requires_subscript) =
13663 if *i + 1 < chars.len() && chars[*i] == '-' && chars[*i + 1] == '>' {
13664 (*i + 2, true)
13665 } else {
13666 (*i, false)
13667 };
13668 if after >= chars.len() {
13669 break;
13670 }
13671 match chars[after] {
13672 '[' => {
13673 *i = after + 1;
13674 let mut idx_str = String::new();
13675 while *i < chars.len() && chars[*i] != ']' {
13676 idx_str.push(chars[*i]);
13677 *i += 1;
13678 }
13679 if *i < chars.len() {
13680 *i += 1;
13681 }
13682 let idx_expr = if let Some(rest) = idx_str.strip_prefix('$') {
13683 Expr {
13684 kind: ExprKind::ScalarVar(rest.to_string()),
13685 line,
13686 }
13687 } else if let Ok(n) = idx_str.parse::<i64>() {
13688 Expr {
13689 kind: ExprKind::Integer(n),
13690 line,
13691 }
13692 } else {
13693 Expr {
13694 kind: ExprKind::String(idx_str),
13695 line,
13696 }
13697 };
13698 base = Expr {
13699 kind: ExprKind::ArrowDeref {
13700 expr: Box::new(base),
13701 index: Box::new(idx_expr),
13702 kind: DerefKind::Array,
13703 },
13704 line,
13705 };
13706 }
13707 '{' => {
13708 *i = after + 1;
13709 let mut key = String::new();
13710 let mut depth = 1usize;
13711 while *i < chars.len() && depth > 0 {
13712 if chars[*i] == '{' {
13713 depth += 1;
13714 } else if chars[*i] == '}' {
13715 depth -= 1;
13716 if depth == 0 {
13717 break;
13718 }
13719 }
13720 key.push(chars[*i]);
13721 *i += 1;
13722 }
13723 if *i < chars.len() {
13724 *i += 1;
13725 }
13726 let key_expr = if let Some(rest) = key.strip_prefix('$') {
13727 Expr {
13728 kind: ExprKind::ScalarVar(rest.to_string()),
13729 line,
13730 }
13731 } else {
13732 Expr {
13733 kind: ExprKind::String(key),
13734 line,
13735 }
13736 };
13737 base = Expr {
13738 kind: ExprKind::ArrowDeref {
13739 expr: Box::new(base),
13740 index: Box::new(key_expr),
13741 kind: DerefKind::Hash,
13742 },
13743 line,
13744 };
13745 }
13746 _ => {
13747 if requires_subscript {
13748 }
13750 break;
13751 }
13752 }
13753 }
13754 base
13755 }
13756
13757 fn parse_interpolated_string(&self, s: &str, line: usize) -> PerlResult<Expr> {
13758 let mut parts = Vec::new();
13760 let mut literal = String::new();
13761 let chars: Vec<char> = s.chars().collect();
13762 let mut i = 0;
13763
13764 'istr: while i < chars.len() {
13765 if chars[i] == LITERAL_DOLLAR_IN_DQUOTE {
13766 literal.push('$');
13767 i += 1;
13768 continue;
13769 }
13770 if chars[i] == '\\' && i + 1 < chars.len() && chars[i + 1] == '$' {
13772 literal.push('\\');
13773 i += 1;
13774 }
13776 if chars[i] == '$' && i + 1 < chars.len() {
13777 if !literal.is_empty() {
13778 parts.push(StringPart::Literal(std::mem::take(&mut literal)));
13779 }
13780 i += 1; while i < chars.len() && chars[i].is_whitespace() {
13783 i += 1;
13784 }
13785 if i >= chars.len() {
13786 return Err(self.syntax_err("Final $ should be \\$ or $name", line));
13787 }
13788 if chars[i] == '#' {
13790 i += 1;
13791 let mut sname = String::from("#");
13792 while i < chars.len()
13793 && (chars[i].is_alphanumeric() || chars[i] == '_' || chars[i] == ':')
13794 {
13795 sname.push(chars[i]);
13796 i += 1;
13797 }
13798 while i + 1 < chars.len() && chars[i] == ':' && chars[i + 1] == ':' {
13799 sname.push_str("::");
13800 i += 2;
13801 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
13802 sname.push(chars[i]);
13803 i += 1;
13804 }
13805 }
13806 parts.push(StringPart::ScalarVar(sname));
13807 continue;
13808 }
13809 if chars[i] == '$' {
13813 let next_c = chars.get(i + 1).copied();
13814 let is_pid = match next_c {
13815 None => true,
13816 Some(c)
13817 if !c.is_ascii_digit() && !matches!(c, 'A'..='Z' | 'a'..='z' | '_') =>
13818 {
13819 true
13820 }
13821 _ => false,
13822 };
13823 if is_pid {
13824 parts.push(StringPart::ScalarVar("$$".to_string()));
13825 i += 1; continue;
13827 }
13828 i += 1; }
13830 if chars[i] == '{' {
13831 i += 1;
13837 let mut inner = String::new();
13838 let mut depth = 1usize;
13839 while i < chars.len() && depth > 0 {
13840 match chars[i] {
13841 '{' => depth += 1,
13842 '}' => {
13843 depth -= 1;
13844 if depth == 0 {
13845 break;
13846 }
13847 }
13848 _ => {}
13849 }
13850 inner.push(chars[i]);
13851 i += 1;
13852 }
13853 if i < chars.len() {
13854 i += 1; }
13856
13857 let trimmed = inner.trim();
13861 let is_expr = trimmed.starts_with('$')
13862 || trimmed.starts_with('\\')
13863 || trimmed.starts_with('@') || trimmed.starts_with('%') || trimmed.contains(['(', '+', '-', '*', '/', '.', '?', '&', '|']);
13866 let mut base: Expr = if is_expr {
13867 match parse_expression_from_str(trimmed, "<interp>") {
13871 Ok(e) => Expr {
13872 kind: ExprKind::Deref {
13873 expr: Box::new(e),
13874 kind: Sigil::Scalar,
13875 },
13876 line,
13877 },
13878 Err(_) => Expr {
13879 kind: ExprKind::ScalarVar(inner.clone()),
13880 line,
13881 },
13882 }
13883 } else {
13884 Expr {
13886 kind: ExprKind::ScalarVar(inner),
13887 line,
13888 }
13889 };
13890
13891 base = self.interp_chain_subscripts(&chars, &mut i, base, line);
13895 parts.push(StringPart::Expr(base));
13896 } else if chars[i] == '^' {
13897 let mut name = String::from("^");
13899 i += 1;
13900 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
13901 name.push(chars[i]);
13902 i += 1;
13903 }
13904 if i < chars.len() && chars[i] == '{' {
13905 i += 1; let mut key = String::new();
13907 let mut depth = 1;
13908 while i < chars.len() && depth > 0 {
13909 if chars[i] == '{' {
13910 depth += 1;
13911 } else if chars[i] == '}' {
13912 depth -= 1;
13913 if depth == 0 {
13914 break;
13915 }
13916 }
13917 key.push(chars[i]);
13918 i += 1;
13919 }
13920 if i < chars.len() {
13921 i += 1;
13922 }
13923 let key_expr = if let Some(rest) = key.strip_prefix('$') {
13924 Expr {
13925 kind: ExprKind::ScalarVar(rest.to_string()),
13926 line,
13927 }
13928 } else {
13929 Expr {
13930 kind: ExprKind::String(key),
13931 line,
13932 }
13933 };
13934 parts.push(StringPart::Expr(Expr {
13935 kind: ExprKind::HashElement {
13936 hash: name,
13937 key: Box::new(key_expr),
13938 },
13939 line,
13940 }));
13941 } else if i < chars.len() && chars[i] == '[' {
13942 i += 1;
13943 let mut idx_str = String::new();
13944 while i < chars.len() && chars[i] != ']' {
13945 idx_str.push(chars[i]);
13946 i += 1;
13947 }
13948 if i < chars.len() {
13949 i += 1;
13950 }
13951 let idx_expr = if let Some(rest) = idx_str.strip_prefix('$') {
13952 Expr {
13953 kind: ExprKind::ScalarVar(rest.to_string()),
13954 line,
13955 }
13956 } else if let Ok(n) = idx_str.parse::<i64>() {
13957 Expr {
13958 kind: ExprKind::Integer(n),
13959 line,
13960 }
13961 } else {
13962 Expr {
13963 kind: ExprKind::String(idx_str),
13964 line,
13965 }
13966 };
13967 parts.push(StringPart::Expr(Expr {
13968 kind: ExprKind::ArrayElement {
13969 array: name,
13970 index: Box::new(idx_expr),
13971 },
13972 line,
13973 }));
13974 } else {
13975 parts.push(StringPart::ScalarVar(name));
13976 }
13977 } else if chars[i].is_alphabetic() || chars[i] == '_' {
13978 let mut name = String::new();
13979 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
13980 name.push(chars[i]);
13981 i += 1;
13982 }
13983 if name == "_" {
13985 while i < chars.len() && chars[i] == '<' {
13986 name.push('<');
13987 i += 1;
13988 }
13989 }
13990 let mut base = if i < chars.len() && chars[i] == '{' {
13995 i += 1; let mut key = String::new();
13998 let mut depth = 1;
13999 while i < chars.len() && depth > 0 {
14000 if chars[i] == '{' {
14001 depth += 1;
14002 } else if chars[i] == '}' {
14003 depth -= 1;
14004 if depth == 0 {
14005 break;
14006 }
14007 }
14008 key.push(chars[i]);
14009 i += 1;
14010 }
14011 if i < chars.len() {
14012 i += 1;
14013 } let key_expr = if let Some(rest) = key.strip_prefix('$') {
14015 Expr {
14016 kind: ExprKind::ScalarVar(rest.to_string()),
14017 line,
14018 }
14019 } else {
14020 Expr {
14021 kind: ExprKind::String(key),
14022 line,
14023 }
14024 };
14025 Expr {
14026 kind: ExprKind::HashElement {
14027 hash: name,
14028 key: Box::new(key_expr),
14029 },
14030 line,
14031 }
14032 } else if i < chars.len() && chars[i] == '[' {
14033 i += 1;
14035 let mut idx_str = String::new();
14036 while i < chars.len() && chars[i] != ']' {
14037 idx_str.push(chars[i]);
14038 i += 1;
14039 }
14040 if i < chars.len() {
14041 i += 1;
14042 }
14043 let idx_expr = if let Some(rest) = idx_str.strip_prefix('$') {
14044 Expr {
14045 kind: ExprKind::ScalarVar(rest.to_string()),
14046 line,
14047 }
14048 } else if let Ok(n) = idx_str.parse::<i64>() {
14049 Expr {
14050 kind: ExprKind::Integer(n),
14051 line,
14052 }
14053 } else {
14054 Expr {
14055 kind: ExprKind::String(idx_str),
14056 line,
14057 }
14058 };
14059 Expr {
14060 kind: ExprKind::ArrayElement {
14061 array: name,
14062 index: Box::new(idx_expr),
14063 },
14064 line,
14065 }
14066 } else {
14067 Expr {
14069 kind: ExprKind::ScalarVar(name),
14070 line,
14071 }
14072 };
14073
14074 base = self.interp_chain_subscripts(&chars, &mut i, base, line);
14078 parts.push(StringPart::Expr(base));
14079 } else if chars[i].is_ascii_digit() {
14080 if chars[i] == '0' {
14082 i += 1;
14083 if i < chars.len() && chars[i].is_ascii_digit() {
14084 return Err(self.syntax_err(
14085 "Numeric variables with more than one digit may not start with '0'",
14086 line,
14087 ));
14088 }
14089 parts.push(StringPart::ScalarVar("0".into()));
14090 } else {
14091 let start = i;
14092 while i < chars.len() && chars[i].is_ascii_digit() {
14093 i += 1;
14094 }
14095 parts.push(StringPart::ScalarVar(chars[start..i].iter().collect()));
14096 }
14097 } else {
14098 let c = chars[i];
14099 let probe = c.to_string();
14100 if Interpreter::is_special_scalar_name_for_get(&probe)
14101 || matches!(c, '\'' | '`')
14102 {
14103 i += 1;
14104 if i < chars.len() && chars[i] == '{' {
14106 i += 1; let mut key = String::new();
14108 let mut depth = 1;
14109 while i < chars.len() && depth > 0 {
14110 if chars[i] == '{' {
14111 depth += 1;
14112 } else if chars[i] == '}' {
14113 depth -= 1;
14114 if depth == 0 {
14115 break;
14116 }
14117 }
14118 key.push(chars[i]);
14119 i += 1;
14120 }
14121 if i < chars.len() {
14122 i += 1;
14123 } let key_expr = if let Some(rest) = key.strip_prefix('$') {
14125 Expr {
14126 kind: ExprKind::ScalarVar(rest.to_string()),
14127 line,
14128 }
14129 } else {
14130 Expr {
14131 kind: ExprKind::String(key),
14132 line,
14133 }
14134 };
14135 let mut base = Expr {
14136 kind: ExprKind::HashElement {
14137 hash: probe,
14138 key: Box::new(key_expr),
14139 },
14140 line,
14141 };
14142 base = self.interp_chain_subscripts(&chars, &mut i, base, line);
14143 parts.push(StringPart::Expr(base));
14144 } else {
14145 let mut base = Expr {
14147 kind: ExprKind::ScalarVar(probe),
14148 line,
14149 };
14150 base = self.interp_chain_subscripts(&chars, &mut i, base, line);
14151 if matches!(base.kind, ExprKind::ScalarVar(_)) {
14152 if let ExprKind::ScalarVar(name) = base.kind {
14154 parts.push(StringPart::ScalarVar(name));
14155 }
14156 } else {
14157 parts.push(StringPart::Expr(base));
14158 }
14159 }
14160 } else {
14161 literal.push('$');
14162 literal.push(c);
14163 i += 1;
14164 }
14165 }
14166 } else if chars[i] == '@' && i + 1 < chars.len() {
14167 let next = chars[i + 1];
14168 if next == '$' {
14170 if !literal.is_empty() {
14171 parts.push(StringPart::Literal(std::mem::take(&mut literal)));
14172 }
14173 i += 1; debug_assert_eq!(chars[i], '$');
14175 i += 1; while i < chars.len() && chars[i].is_whitespace() {
14177 i += 1;
14178 }
14179 if i >= chars.len() {
14180 return Err(self.syntax_err(
14181 "Expected variable or block after `@$` in double-quoted string",
14182 line,
14183 ));
14184 }
14185 let inner_expr = if chars[i] == '{' {
14186 i += 1;
14187 let start = i;
14188 let mut depth = 1usize;
14189 while i < chars.len() && depth > 0 {
14190 match chars[i] {
14191 '{' => depth += 1,
14192 '}' => {
14193 depth -= 1;
14194 if depth == 0 {
14195 break;
14196 }
14197 }
14198 _ => {}
14199 }
14200 i += 1;
14201 }
14202 if depth != 0 {
14203 return Err(self.syntax_err(
14204 "Unterminated `${ ... }` after `@` in double-quoted string",
14205 line,
14206 ));
14207 }
14208 let inner: String = chars[start..i].iter().collect();
14209 i += 1; parse_expression_from_str(inner.trim(), "-e")?
14211 } else {
14212 let mut name = String::new();
14213 if chars[i] == '^' {
14214 name.push('^');
14215 i += 1;
14216 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_')
14217 {
14218 name.push(chars[i]);
14219 i += 1;
14220 }
14221 } else {
14222 while i < chars.len()
14223 && (chars[i].is_alphanumeric()
14224 || chars[i] == '_'
14225 || chars[i] == ':')
14226 {
14227 name.push(chars[i]);
14228 i += 1;
14229 }
14230 while i + 1 < chars.len() && chars[i] == ':' && chars[i + 1] == ':' {
14231 name.push_str("::");
14232 i += 2;
14233 while i < chars.len()
14234 && (chars[i].is_alphanumeric() || chars[i] == '_')
14235 {
14236 name.push(chars[i]);
14237 i += 1;
14238 }
14239 }
14240 }
14241 if name.is_empty() {
14242 return Err(self.syntax_err(
14243 "Expected identifier after `@$` in double-quoted string",
14244 line,
14245 ));
14246 }
14247 Expr {
14248 kind: ExprKind::ScalarVar(name),
14249 line,
14250 }
14251 };
14252 parts.push(StringPart::Expr(Expr {
14253 kind: ExprKind::Deref {
14254 expr: Box::new(inner_expr),
14255 kind: Sigil::Array,
14256 },
14257 line,
14258 }));
14259 continue 'istr;
14260 }
14261 if next == '{' {
14262 if !literal.is_empty() {
14263 parts.push(StringPart::Literal(std::mem::take(&mut literal)));
14264 }
14265 i += 2; let start = i;
14267 let mut depth = 1usize;
14268 while i < chars.len() && depth > 0 {
14269 match chars[i] {
14270 '{' => depth += 1,
14271 '}' => {
14272 depth -= 1;
14273 if depth == 0 {
14274 break;
14275 }
14276 }
14277 _ => {}
14278 }
14279 i += 1;
14280 }
14281 if depth != 0 {
14282 return Err(
14283 self.syntax_err("Unterminated @{ ... } in double-quoted string", line)
14284 );
14285 }
14286 let inner: String = chars[start..i].iter().collect();
14287 i += 1; let inner_expr = parse_expression_from_str(inner.trim(), "-e")?;
14289 parts.push(StringPart::Expr(Expr {
14290 kind: ExprKind::Deref {
14291 expr: Box::new(inner_expr),
14292 kind: Sigil::Array,
14293 },
14294 line,
14295 }));
14296 continue 'istr;
14297 }
14298 if !(next.is_alphabetic() || next == '_' || next == '+' || next == '-') {
14299 literal.push(chars[i]);
14300 i += 1;
14301 } else {
14302 if !literal.is_empty() {
14303 parts.push(StringPart::Literal(std::mem::take(&mut literal)));
14304 }
14305 i += 1;
14306 let mut name = String::new();
14307 if i < chars.len() && (chars[i] == '+' || chars[i] == '-') {
14308 name.push(chars[i]);
14309 i += 1;
14310 } else {
14311 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
14312 name.push(chars[i]);
14313 i += 1;
14314 }
14315 while i + 1 < chars.len() && chars[i] == ':' && chars[i + 1] == ':' {
14316 name.push_str("::");
14317 i += 2;
14318 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_')
14319 {
14320 name.push(chars[i]);
14321 i += 1;
14322 }
14323 }
14324 }
14325 if i < chars.len() && chars[i] == '[' {
14326 i += 1;
14327 let start_inner = i;
14328 let mut depth = 1usize;
14329 while i < chars.len() && depth > 0 {
14330 match chars[i] {
14331 '[' => depth += 1,
14332 ']' => depth -= 1,
14333 _ => {}
14334 }
14335 if depth == 0 {
14336 let inner: String = chars[start_inner..i].iter().collect();
14337 i += 1; let indices = parse_slice_indices_from_str(inner.trim(), "-e")?;
14339 parts.push(StringPart::Expr(Expr {
14340 kind: ExprKind::ArraySlice {
14341 array: name.clone(),
14342 indices,
14343 },
14344 line,
14345 }));
14346 continue 'istr;
14347 }
14348 i += 1;
14349 }
14350 return Err(self.syntax_err(
14351 "Unterminated [ in array slice inside quoted string",
14352 line,
14353 ));
14354 }
14355 parts.push(StringPart::ArrayVar(name));
14356 }
14357 } else if chars[i] == '#'
14358 && i + 1 < chars.len()
14359 && chars[i + 1] == '{'
14360 && !crate::compat_mode()
14361 {
14362 if !literal.is_empty() {
14364 parts.push(StringPart::Literal(std::mem::take(&mut literal)));
14365 }
14366 i += 2; let mut inner = String::new();
14368 let mut depth = 1usize;
14369 while i < chars.len() && depth > 0 {
14370 match chars[i] {
14371 '{' => depth += 1,
14372 '}' => {
14373 depth -= 1;
14374 if depth == 0 {
14375 break;
14376 }
14377 }
14378 _ => {}
14379 }
14380 inner.push(chars[i]);
14381 i += 1;
14382 }
14383 if i < chars.len() {
14384 i += 1; }
14386 let expr = parse_block_from_str(inner.trim(), "-e", line)?;
14387 parts.push(StringPart::Expr(expr));
14388 } else {
14389 literal.push(chars[i]);
14390 i += 1;
14391 }
14392 }
14393 if !literal.is_empty() {
14394 parts.push(StringPart::Literal(literal));
14395 }
14396
14397 if parts.len() == 1 {
14398 if let StringPart::Literal(s) = &parts[0] {
14399 return Ok(Expr {
14400 kind: ExprKind::String(s.clone()),
14401 line,
14402 });
14403 }
14404 }
14405 if parts.is_empty() {
14406 return Ok(Expr {
14407 kind: ExprKind::String(String::new()),
14408 line,
14409 });
14410 }
14411
14412 Ok(Expr {
14413 kind: ExprKind::InterpolatedString(parts),
14414 line,
14415 })
14416 }
14417
14418 fn expr_to_overload_key(&self, e: &Expr) -> PerlResult<String> {
14419 match &e.kind {
14420 ExprKind::String(s) => Ok(s.clone()),
14421 _ => Err(self.syntax_err(
14422 "overload key must be a string literal (e.g. '\"\"' or '+')",
14423 e.line,
14424 )),
14425 }
14426 }
14427
14428 fn expr_to_overload_sub(&self, e: &Expr) -> PerlResult<String> {
14429 match &e.kind {
14430 ExprKind::String(s) => Ok(s.clone()),
14431 ExprKind::Integer(n) => Ok(n.to_string()),
14432 ExprKind::SubroutineRef(s) | ExprKind::SubroutineCodeRef(s) => Ok(s.clone()),
14433 _ => Err(self.syntax_err(
14434 "overload handler must be a string literal, number (e.g. fallback => 1), or \\&subname (method in current package)",
14435 e.line,
14436 )),
14437 }
14438 }
14439}
14440
14441fn merge_expr_list(parts: Vec<Expr>) -> Expr {
14442 if parts.len() == 1 {
14443 parts.into_iter().next().unwrap()
14444 } else {
14445 let line = parts.first().map(|e| e.line).unwrap_or(0);
14446 Expr {
14447 kind: ExprKind::List(parts),
14448 line,
14449 }
14450 }
14451}
14452
14453pub fn parse_expression_from_str(s: &str, file: &str) -> PerlResult<Expr> {
14455 let mut lexer = Lexer::new_with_file(s, file);
14456 let tokens = lexer.tokenize()?;
14457 let mut parser = Parser::new_with_file(tokens, file);
14458 let e = parser.parse_expression()?;
14459 if !parser.at_eof() {
14460 return Err(parser.syntax_err(
14461 "Extra tokens in embedded string expression",
14462 parser.peek_line(),
14463 ));
14464 }
14465 Ok(e)
14466}
14467
14468pub fn parse_block_from_str(s: &str, file: &str, line: usize) -> PerlResult<Expr> {
14470 let mut lexer = Lexer::new_with_file(s, file);
14471 let tokens = lexer.tokenize()?;
14472 let mut parser = Parser::new_with_file(tokens, file);
14473 let stmts = parser.parse_statements()?;
14474 let inner_line = stmts.first().map(|st| st.line).unwrap_or(line);
14475 let inner = Expr {
14476 kind: ExprKind::CodeRef {
14477 params: vec![],
14478 body: stmts,
14479 },
14480 line: inner_line,
14481 };
14482 Ok(Expr {
14483 kind: ExprKind::Do(Box::new(inner)),
14484 line,
14485 })
14486}
14487
14488pub fn parse_slice_indices_from_str(s: &str, file: &str) -> PerlResult<Vec<Expr>> {
14491 let mut lexer = Lexer::new_with_file(s, file);
14492 let tokens = lexer.tokenize()?;
14493 let mut parser = Parser::new_with_file(tokens, file);
14494 parser.parse_arg_list()
14495}
14496
14497pub fn parse_format_value_line(line: &str) -> PerlResult<Vec<Expr>> {
14498 let trimmed = line.trim();
14499 if trimmed.is_empty() {
14500 return Ok(vec![]);
14501 }
14502 let mut lexer = Lexer::new(trimmed);
14503 let tokens = lexer.tokenize()?;
14504 let mut parser = Parser::new(tokens);
14505 let mut exprs = Vec::new();
14506 loop {
14507 if parser.at_eof() {
14508 break;
14509 }
14510 exprs.push(parser.parse_assign_expr()?);
14512 if parser.eat(&Token::Comma) {
14513 continue;
14514 }
14515 if !parser.at_eof() {
14516 return Err(parser.syntax_err("Extra tokens in format value line", parser.peek_line()));
14517 }
14518 break;
14519 }
14520 Ok(exprs)
14521}
14522
14523#[cfg(test)]
14524mod tests {
14525 use super::*;
14526
14527 fn parse_ok(code: &str) -> Program {
14528 let mut lexer = Lexer::new(code);
14529 let tokens = lexer.tokenize().expect("tokenize");
14530 let mut parser = Parser::new(tokens);
14531 parser.parse_program().expect("parse")
14532 }
14533
14534 fn parse_err(code: &str) -> String {
14535 let mut lexer = Lexer::new(code);
14536 let tokens = match lexer.tokenize() {
14537 Ok(t) => t,
14538 Err(e) => return e.message,
14539 };
14540 let mut parser = Parser::new(tokens);
14541 parser.parse_program().unwrap_err().message
14542 }
14543
14544 #[test]
14545 fn parse_empty_program() {
14546 let p = parse_ok("");
14547 assert!(p.statements.is_empty());
14548 }
14549
14550 #[test]
14551 fn parse_semicolons_only() {
14552 let p = parse_ok(";;");
14553 assert!(p.statements.len() <= 3);
14554 }
14555
14556 #[test]
14557 fn parse_simple_scalar_assignment() {
14558 let p = parse_ok("$x = 1");
14559 assert_eq!(p.statements.len(), 1);
14560 }
14561
14562 #[test]
14563 fn parse_simple_array_assignment() {
14564 let p = parse_ok("@arr = (1, 2, 3)");
14565 assert_eq!(p.statements.len(), 1);
14566 }
14567
14568 #[test]
14569 fn parse_simple_hash_assignment() {
14570 let p = parse_ok("%h = (a => 1, b => 2)");
14571 assert_eq!(p.statements.len(), 1);
14572 }
14573
14574 #[test]
14575 fn parse_subroutine_decl() {
14576 let p = parse_ok("fn foo { 1 }");
14577 assert_eq!(p.statements.len(), 1);
14578 match &p.statements[0].kind {
14579 StmtKind::SubDecl { name, .. } => assert_eq!(name, "foo"),
14580 _ => panic!("expected SubDecl"),
14581 }
14582 }
14583
14584 #[test]
14585 fn parse_subroutine_with_prototype() {
14586 let p = parse_ok("fn foo ($$) { 1 }");
14587 assert_eq!(p.statements.len(), 1);
14588 match &p.statements[0].kind {
14589 StmtKind::SubDecl { prototype, .. } => {
14590 assert!(prototype.is_some());
14591 }
14592 _ => panic!("expected SubDecl"),
14593 }
14594 }
14595
14596 #[test]
14597 fn parse_anonymous_fn() {
14598 let p = parse_ok("my $f = fn { 1 }");
14599 assert_eq!(p.statements.len(), 1);
14600 }
14601
14602 #[test]
14603 fn parse_if_statement() {
14604 let p = parse_ok("if (1) { 2 }");
14605 assert_eq!(p.statements.len(), 1);
14606 matches!(&p.statements[0].kind, StmtKind::If { .. });
14607 }
14608
14609 #[test]
14610 fn parse_if_elsif_else() {
14611 let p = parse_ok("if (0) { 1 } elsif (1) { 2 } else { 3 }");
14612 assert_eq!(p.statements.len(), 1);
14613 }
14614
14615 #[test]
14616 fn parse_unless_statement() {
14617 let p = parse_ok("unless (0) { 1 }");
14618 assert_eq!(p.statements.len(), 1);
14619 }
14620
14621 #[test]
14622 fn parse_while_loop() {
14623 let p = parse_ok("while ($x) { $x-- }");
14624 assert_eq!(p.statements.len(), 1);
14625 }
14626
14627 #[test]
14628 fn parse_until_loop() {
14629 let p = parse_ok("until ($x) { $x++ }");
14630 assert_eq!(p.statements.len(), 1);
14631 }
14632
14633 #[test]
14634 fn parse_for_c_style() {
14635 let p = parse_ok("for (my $i=0; $i<10; $i++) { 1 }");
14636 assert_eq!(p.statements.len(), 1);
14637 }
14638
14639 #[test]
14640 fn parse_foreach_loop() {
14641 let p = parse_ok("foreach my $x (@arr) { 1 }");
14642 assert_eq!(p.statements.len(), 1);
14643 }
14644
14645 #[test]
14646 fn parse_loop_with_label() {
14647 let p = parse_ok("OUTER: for my $i (1..10) { last OUTER }");
14648 assert_eq!(p.statements.len(), 1);
14649 assert_eq!(p.statements[0].label.as_deref(), Some("OUTER"));
14650 }
14651
14652 #[test]
14653 fn parse_begin_block() {
14654 let p = parse_ok("BEGIN { 1 }");
14655 assert_eq!(p.statements.len(), 1);
14656 matches!(&p.statements[0].kind, StmtKind::Begin(_));
14657 }
14658
14659 #[test]
14660 fn parse_end_block() {
14661 let p = parse_ok("END { 1 }");
14662 assert_eq!(p.statements.len(), 1);
14663 matches!(&p.statements[0].kind, StmtKind::End(_));
14664 }
14665
14666 #[test]
14667 fn parse_package_statement() {
14668 let p = parse_ok("package Foo::Bar");
14669 assert_eq!(p.statements.len(), 1);
14670 match &p.statements[0].kind {
14671 StmtKind::Package { name } => assert_eq!(name, "Foo::Bar"),
14672 _ => panic!("expected Package"),
14673 }
14674 }
14675
14676 #[test]
14677 fn parse_use_statement() {
14678 let p = parse_ok("use strict");
14679 assert_eq!(p.statements.len(), 1);
14680 }
14681
14682 #[test]
14683 fn parse_no_statement() {
14684 let p = parse_ok("no warnings");
14685 assert_eq!(p.statements.len(), 1);
14686 }
14687
14688 #[test]
14689 fn parse_require_bareword() {
14690 let p = parse_ok("require Foo::Bar");
14691 assert_eq!(p.statements.len(), 1);
14692 }
14693
14694 #[test]
14695 fn parse_require_string() {
14696 let p = parse_ok(r#"require "foo.pl""#);
14697 assert_eq!(p.statements.len(), 1);
14698 }
14699
14700 #[test]
14701 fn parse_eval_block() {
14702 let p = parse_ok("eval { 1 }");
14703 assert_eq!(p.statements.len(), 1);
14704 }
14705
14706 #[test]
14707 fn parse_eval_string() {
14708 let p = parse_ok(r#"eval "1 + 2""#);
14709 assert_eq!(p.statements.len(), 1);
14710 }
14711
14712 #[test]
14713 fn parse_qw_word_list() {
14714 let p = parse_ok("my @a = qw(foo bar baz)");
14715 assert_eq!(p.statements.len(), 1);
14716 }
14717
14718 #[test]
14719 fn parse_q_string() {
14720 let p = parse_ok("my $s = q{hello}");
14721 assert_eq!(p.statements.len(), 1);
14722 }
14723
14724 #[test]
14725 fn parse_qq_string() {
14726 let p = parse_ok(r#"my $s = qq(hello $x)"#);
14727 assert_eq!(p.statements.len(), 1);
14728 }
14729
14730 #[test]
14731 fn parse_regex_match() {
14732 let p = parse_ok(r#"$x =~ /foo/"#);
14733 assert_eq!(p.statements.len(), 1);
14734 }
14735
14736 #[test]
14737 fn parse_regex_substitution() {
14738 let p = parse_ok(r#"$x =~ s/foo/bar/g"#);
14739 assert_eq!(p.statements.len(), 1);
14740 }
14741
14742 #[test]
14743 fn parse_transliterate() {
14744 let p = parse_ok(r#"$x =~ tr/a-z/A-Z/"#);
14745 assert_eq!(p.statements.len(), 1);
14746 }
14747
14748 #[test]
14749 fn parse_ternary_operator() {
14750 let p = parse_ok("my $x = $a ? 1 : 2");
14751 assert_eq!(p.statements.len(), 1);
14752 }
14753
14754 #[test]
14755 fn parse_arrow_method_call() {
14756 let p = parse_ok("$obj->method()");
14757 assert_eq!(p.statements.len(), 1);
14758 }
14759
14760 #[test]
14761 fn parse_arrow_deref_hash() {
14762 let p = parse_ok("$r->{key}");
14763 assert_eq!(p.statements.len(), 1);
14764 }
14765
14766 #[test]
14767 fn parse_arrow_deref_array() {
14768 let p = parse_ok("$r->[0]");
14769 assert_eq!(p.statements.len(), 1);
14770 }
14771
14772 #[test]
14773 fn parse_chained_arrow_deref() {
14774 let p = parse_ok("$r->{a}[0]{b}");
14775 assert_eq!(p.statements.len(), 1);
14776 }
14777
14778 #[test]
14779 fn parse_my_multiple_vars() {
14780 let p = parse_ok("my ($a, $b, $c) = (1, 2, 3)");
14781 assert_eq!(p.statements.len(), 1);
14782 }
14783
14784 #[test]
14785 fn parse_our_scalar() {
14786 let p = parse_ok("our $VERSION = '1.0'");
14787 assert_eq!(p.statements.len(), 1);
14788 }
14789
14790 #[test]
14791 fn parse_local_scalar() {
14792 let p = parse_ok("local $/ = undef");
14793 assert_eq!(p.statements.len(), 1);
14794 }
14795
14796 #[test]
14797 fn parse_state_variable() {
14798 let p = parse_ok("fn my_counter { state $n = 0; $n++ }");
14799 assert_eq!(p.statements.len(), 1);
14800 }
14801
14802 #[test]
14803 fn parse_postfix_if() {
14804 let p = parse_ok("print 1 if $x");
14805 assert_eq!(p.statements.len(), 1);
14806 }
14807
14808 #[test]
14809 fn parse_postfix_unless() {
14810 let p = parse_ok("die 'error' unless $ok");
14811 assert_eq!(p.statements.len(), 1);
14812 }
14813
14814 #[test]
14815 fn parse_postfix_while() {
14816 let p = parse_ok("$x++ while $x < 10");
14817 assert_eq!(p.statements.len(), 1);
14818 }
14819
14820 #[test]
14821 fn parse_postfix_for() {
14822 let p = parse_ok("print for @arr");
14823 assert_eq!(p.statements.len(), 1);
14824 }
14825
14826 #[test]
14827 fn parse_last_next_redo() {
14828 let p = parse_ok("for (@a) { next if $_ < 0; last if $_ > 10 }");
14829 assert_eq!(p.statements.len(), 1);
14830 }
14831
14832 #[test]
14833 fn parse_return_statement() {
14834 let p = parse_ok("fn foo { return 42 }");
14835 assert_eq!(p.statements.len(), 1);
14836 }
14837
14838 #[test]
14839 fn parse_wantarray() {
14840 let p = parse_ok("fn foo { wantarray ? @a : $a }");
14841 assert_eq!(p.statements.len(), 1);
14842 }
14843
14844 #[test]
14845 fn parse_caller_builtin() {
14846 let p = parse_ok("my @c = caller");
14847 assert_eq!(p.statements.len(), 1);
14848 }
14849
14850 #[test]
14851 fn parse_ref_to_array() {
14852 let p = parse_ok("my $r = \\@arr");
14853 assert_eq!(p.statements.len(), 1);
14854 }
14855
14856 #[test]
14857 fn parse_ref_to_hash() {
14858 let p = parse_ok("my $r = \\%hash");
14859 assert_eq!(p.statements.len(), 1);
14860 }
14861
14862 #[test]
14863 fn parse_ref_to_scalar() {
14864 let p = parse_ok("my $r = \\$x");
14865 assert_eq!(p.statements.len(), 1);
14866 }
14867
14868 #[test]
14869 fn parse_deref_scalar() {
14870 let p = parse_ok("my $v = $$r");
14871 assert_eq!(p.statements.len(), 1);
14872 }
14873
14874 #[test]
14875 fn parse_deref_array() {
14876 let p = parse_ok("my @a = @$r");
14877 assert_eq!(p.statements.len(), 1);
14878 }
14879
14880 #[test]
14881 fn parse_deref_hash() {
14882 let p = parse_ok("my %h = %$r");
14883 assert_eq!(p.statements.len(), 1);
14884 }
14885
14886 #[test]
14887 fn parse_blessed_ref() {
14888 let p = parse_ok("bless $r, 'Foo'");
14889 assert_eq!(p.statements.len(), 1);
14890 }
14891
14892 #[test]
14893 fn parse_heredoc_basic() {
14894 let p = parse_ok("my $s = <<END;\nfoo\nEND");
14895 assert_eq!(p.statements.len(), 1);
14896 }
14897
14898 #[test]
14899 fn parse_heredoc_quoted() {
14900 let p = parse_ok("my $s = <<'END';\nfoo\nEND");
14901 assert_eq!(p.statements.len(), 1);
14902 }
14903
14904 #[test]
14905 fn parse_do_block() {
14906 let p = parse_ok("my $x = do { 1 + 2 }");
14907 assert_eq!(p.statements.len(), 1);
14908 }
14909
14910 #[test]
14911 fn parse_do_file() {
14912 let p = parse_ok(r#"do "foo.pl""#);
14913 assert_eq!(p.statements.len(), 1);
14914 }
14915
14916 #[test]
14917 fn parse_map_expression() {
14918 let p = parse_ok("my @b = map { $_ * 2 } @a");
14919 assert_eq!(p.statements.len(), 1);
14920 }
14921
14922 #[test]
14923 fn parse_grep_expression() {
14924 let p = parse_ok("my @b = grep { $_ > 0 } @a");
14925 assert_eq!(p.statements.len(), 1);
14926 }
14927
14928 #[test]
14929 fn parse_sort_expression() {
14930 let p = parse_ok("my @b = sort { $a <=> $b } @a");
14931 assert_eq!(p.statements.len(), 1);
14932 }
14933
14934 #[test]
14935 fn parse_pipe_forward() {
14936 let p = parse_ok("@a |> map { $_ * 2 }");
14937 assert_eq!(p.statements.len(), 1);
14938 }
14939
14940 #[test]
14941 fn parse_expression_from_str_simple() {
14942 let e = parse_expression_from_str("$x + 1", "-e").unwrap();
14943 assert!(matches!(e.kind, ExprKind::BinOp { .. }));
14944 }
14945
14946 #[test]
14947 fn parse_expression_from_str_extra_tokens_error() {
14948 let err = parse_expression_from_str("$x; $y", "-e").unwrap_err();
14949 assert!(err.message.contains("Extra tokens"));
14950 }
14951
14952 #[test]
14953 fn parse_slice_indices_from_str_basic() {
14954 let indices = parse_slice_indices_from_str("0, 1, 2", "-e").unwrap();
14955 assert_eq!(indices.len(), 3);
14956 }
14957
14958 #[test]
14959 fn parse_format_value_line_empty() {
14960 let exprs = parse_format_value_line("").unwrap();
14961 assert!(exprs.is_empty());
14962 }
14963
14964 #[test]
14965 fn parse_format_value_line_single() {
14966 let exprs = parse_format_value_line("$x").unwrap();
14967 assert_eq!(exprs.len(), 1);
14968 }
14969
14970 #[test]
14971 fn parse_format_value_line_multiple() {
14972 let exprs = parse_format_value_line("$a, $b, $c").unwrap();
14973 assert_eq!(exprs.len(), 3);
14974 }
14975
14976 #[test]
14977 fn parse_unclosed_brace_error() {
14978 let err = parse_err("fn foo {");
14979 assert!(!err.is_empty());
14980 }
14981
14982 #[test]
14983 fn parse_unclosed_paren_error() {
14984 let err = parse_err("print (1, 2");
14985 assert!(!err.is_empty());
14986 }
14987
14988 #[test]
14989 fn parse_invalid_statement_error() {
14990 let err = parse_err("???");
14991 assert!(!err.is_empty());
14992 }
14993
14994 #[test]
14995 fn merge_expr_list_single() {
14996 let e = Expr {
14997 kind: ExprKind::Integer(1),
14998 line: 1,
14999 };
15000 let merged = merge_expr_list(vec![e.clone()]);
15001 matches!(merged.kind, ExprKind::Integer(1));
15002 }
15003
15004 #[test]
15005 fn merge_expr_list_multiple() {
15006 let e1 = Expr {
15007 kind: ExprKind::Integer(1),
15008 line: 1,
15009 };
15010 let e2 = Expr {
15011 kind: ExprKind::Integer(2),
15012 line: 1,
15013 };
15014 let merged = merge_expr_list(vec![e1, e2]);
15015 matches!(merged.kind, ExprKind::List(_));
15016 }
15017}