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}
123
124impl Parser {
125 pub fn new(tokens: Vec<(Token, usize)>) -> Self {
126 Self::new_with_file(tokens, "-e")
127 }
128
129 pub fn new_with_file(tokens: Vec<(Token, usize)>, file: impl Into<String>) -> Self {
130 Self {
131 tokens,
132 pos: 0,
133 next_rate_limit_slot: 0,
134 suppress_indirect_paren_call: 0,
135 pipe_rhs_depth: 0,
136 no_pipe_forward_depth: 0,
137 suppress_scalar_hash_brace: 0,
138 next_desugar_tmp: 0,
139 error_file: file.into(),
140 declared_subs: std::collections::HashSet::new(),
141 suppress_parenless_call: 0,
142 suppress_slash_as_div: 0,
143 suppress_m_regex: 0,
144 }
145 }
146
147 fn alloc_desugar_tmp(&mut self) -> u32 {
148 let n = self.next_desugar_tmp;
149 self.next_desugar_tmp = self.next_desugar_tmp.saturating_add(1);
150 n
151 }
152
153 #[inline]
157 fn in_pipe_rhs(&self) -> bool {
158 self.pipe_rhs_depth > 0
159 }
160
161 fn pipe_supplies_slurped_list_operand(&self) -> bool {
163 self.in_pipe_rhs()
164 && matches!(
165 self.peek(),
166 Token::Semicolon
167 | Token::RBrace
168 | Token::RParen
169 | Token::Eof
170 | Token::Comma
171 | Token::PipeForward
172 )
173 }
174
175 #[inline]
180 fn pipe_placeholder_list(&self, line: usize) -> Expr {
181 Expr {
182 kind: ExprKind::List(vec![]),
183 line,
184 }
185 }
186
187 fn lift_bareword_to_topic_call(expr: Expr) -> Expr {
198 let line = expr.line;
199 let topic = || Expr {
200 kind: ExprKind::ScalarVar("_".into()),
201 line,
202 };
203 match expr.kind {
204 ExprKind::Bareword(ref name) => Expr {
205 kind: ExprKind::FuncCall {
206 name: name.clone(),
207 args: vec![topic()],
208 },
209 line,
210 },
211 ExprKind::Unlink(ref args) if args.is_empty() => Expr {
213 kind: ExprKind::Unlink(vec![topic()]),
214 line,
215 },
216 ExprKind::Chmod(ref args) if args.is_empty() => Expr {
217 kind: ExprKind::Chmod(vec![topic()]),
218 line,
219 },
220 ExprKind::Stat(_) => expr,
222 ExprKind::Lstat(_) => expr,
223 ExprKind::Readlink(_) => expr,
224 ExprKind::ScalarReverse(ref inner) => {
226 if matches!(inner.kind, ExprKind::List(ref v) if v.is_empty()) {
227 Expr {
228 kind: ExprKind::ScalarReverse(Box::new(topic())),
229 line,
230 }
231 } else {
232 expr
233 }
234 }
235 _ => expr,
236 }
237 }
238
239 fn parse_assign_expr_stop_at_pipe(&mut self) -> PerlResult<Expr> {
247 self.no_pipe_forward_depth = self.no_pipe_forward_depth.saturating_add(1);
248 let r = self.parse_assign_expr();
249 self.no_pipe_forward_depth = self.no_pipe_forward_depth.saturating_sub(1);
250 r
251 }
252
253 fn syntax_err(&self, message: impl Into<String>, line: usize) -> PerlError {
254 PerlError::new(ErrorKind::Syntax, message, line, self.error_file.clone())
255 }
256
257 fn alloc_rate_limit_slot(&mut self) -> u32 {
258 let s = self.next_rate_limit_slot;
259 self.next_rate_limit_slot = self.next_rate_limit_slot.saturating_add(1);
260 s
261 }
262
263 fn peek(&self) -> &Token {
264 self.tokens
265 .get(self.pos)
266 .map(|(t, _)| t)
267 .unwrap_or(&Token::Eof)
268 }
269
270 fn peek_line(&self) -> usize {
271 self.tokens.get(self.pos).map(|(_, l)| *l).unwrap_or(0)
272 }
273
274 fn peek_at(&self, offset: usize) -> &Token {
275 self.tokens
276 .get(self.pos + offset)
277 .map(|(t, _)| t)
278 .unwrap_or(&Token::Eof)
279 }
280
281 fn advance(&mut self) -> (Token, usize) {
282 let tok = self
283 .tokens
284 .get(self.pos)
285 .cloned()
286 .unwrap_or((Token::Eof, 0));
287 self.pos += 1;
288 tok
289 }
290
291 fn prev_line(&self) -> usize {
293 if self.pos > 0 {
294 self.tokens.get(self.pos - 1).map(|(_, l)| *l).unwrap_or(0)
295 } else {
296 0
297 }
298 }
299
300 fn expect(&mut self, expected: &Token) -> PerlResult<usize> {
301 let (tok, line) = self.advance();
302 if std::mem::discriminant(&tok) == std::mem::discriminant(expected) {
303 Ok(line)
304 } else {
305 Err(self.syntax_err(format!("Expected {:?}, got {:?}", expected, tok), line))
306 }
307 }
308
309 fn eat(&mut self, expected: &Token) -> bool {
310 if std::mem::discriminant(self.peek()) == std::mem::discriminant(expected) {
311 self.advance();
312 true
313 } else {
314 false
315 }
316 }
317
318 fn at_eof(&self) -> bool {
319 matches!(self.peek(), Token::Eof)
320 }
321
322 fn filetest_allows_implicit_topic(tok: &Token) -> bool {
324 matches!(
325 tok,
326 Token::RParen
327 | Token::Semicolon
328 | Token::Comma
329 | Token::RBrace
330 | Token::Eof
331 | Token::LogAnd
332 | Token::LogOr
333 | Token::LogAndWord
334 | Token::LogOrWord
335 | Token::PipeForward
336 )
337 }
338
339 fn next_is_new_stmt_keyword(&self, stmt_line: usize) -> bool {
343 if crate::compat_mode() {
345 return false;
346 }
347 if self.peek_line() == stmt_line {
348 return false;
349 }
350 matches!(
351 self.peek(),
352 Token::Ident(ref kw) if matches!(kw.as_str(),
353 "use" | "no" | "my" | "our" | "local" | "sub" | "struct" | "enum"
354 | "if" | "unless" | "while" | "until" | "for" | "foreach"
355 | "return" | "last" | "next" | "redo" | "package" | "require"
356 | "BEGIN" | "END" | "UNITCHECK" | "frozen" | "const" | "typed"
357 )
358 )
359 }
360
361 fn next_is_new_statement_start(&self, stmt_line: usize) -> bool {
365 if crate::compat_mode() {
366 return false;
367 }
368 if self.peek_line() == stmt_line {
369 return false;
370 }
371 matches!(
372 self.peek(),
373 Token::ScalarVar(_)
374 | Token::DerefScalarVar(_)
375 | Token::ArrayVar(_)
376 | Token::HashVar(_)
377 | Token::LBrace
378 ) || self.next_is_new_stmt_keyword(stmt_line)
379 }
380
381 pub fn parse_program(&mut self) -> PerlResult<Program> {
384 let statements = self.parse_statements()?;
385 Ok(Program { statements })
386 }
387
388 pub fn parse_statements(&mut self) -> PerlResult<Vec<Statement>> {
390 let mut statements = Vec::new();
391 while !self.at_eof() {
392 if matches!(self.peek(), Token::Semicolon) {
393 let line = self.peek_line();
394 self.advance();
395 statements.push(Statement {
396 label: None,
397 kind: StmtKind::Empty,
398 line,
399 });
400 continue;
401 }
402 statements.push(self.parse_statement()?);
403 }
404 Ok(statements)
405 }
406
407 fn parse_statement(&mut self) -> PerlResult<Statement> {
410 let line = self.peek_line();
411
412 let label = match self.peek().clone() {
415 Token::Ident(_) => {
416 if matches!(self.peek_at(1), Token::Colon)
417 && !matches!(self.peek_at(2), Token::Colon)
418 {
419 let (tok, _) = self.advance();
420 let l = match tok {
421 Token::Ident(l) => l,
422 _ => unreachable!(),
423 };
424 self.advance(); Some(l)
426 } else {
427 None
428 }
429 }
430 _ => None,
431 };
432
433 let mut stmt = match self.peek().clone() {
434 Token::FormatDecl { .. } => {
435 let tok_line = self.peek_line();
436 let (tok, _) = self.advance();
437 match tok {
438 Token::FormatDecl { name, lines } => Statement {
439 label: label.clone(),
440 kind: StmtKind::FormatDecl { name, lines },
441 line: tok_line,
442 },
443 _ => unreachable!(),
444 }
445 }
446 Token::Ident(ref kw) => match kw.as_str() {
447 "if" => self.parse_if()?,
448 "unless" => self.parse_unless()?,
449 "while" => {
450 let mut s = self.parse_while()?;
451 if let StmtKind::While {
452 label: ref mut lbl, ..
453 } = s.kind
454 {
455 *lbl = label.clone();
456 }
457 s
458 }
459 "until" => {
460 let mut s = self.parse_until()?;
461 if let StmtKind::Until {
462 label: ref mut lbl, ..
463 } = s.kind
464 {
465 *lbl = label.clone();
466 }
467 s
468 }
469 "for" => {
470 let mut s = self.parse_for_or_foreach()?;
471 match s.kind {
472 StmtKind::For {
473 label: ref mut lbl, ..
474 }
475 | StmtKind::Foreach {
476 label: ref mut lbl, ..
477 } => *lbl = label.clone(),
478 _ => {}
479 }
480 s
481 }
482 "foreach" => {
483 let mut s = self.parse_foreach()?;
484 if let StmtKind::Foreach {
485 label: ref mut lbl, ..
486 } = s.kind
487 {
488 *lbl = label.clone();
489 }
490 s
491 }
492 "sub" | "fn" => self.parse_sub_decl()?,
493 "struct" => {
494 if crate::compat_mode() {
495 return Err(self.syntax_err(
496 "`struct` is a stryke extension (disabled by --compat)",
497 self.peek_line(),
498 ));
499 }
500 self.parse_struct_decl()?
501 }
502 "enum" => {
503 if crate::compat_mode() {
504 return Err(self.syntax_err(
505 "`enum` is a stryke extension (disabled by --compat)",
506 self.peek_line(),
507 ));
508 }
509 self.parse_enum_decl()?
510 }
511 "class" => {
512 if crate::compat_mode() {
513 return Err(self.syntax_err(
515 "Perl 5.38 `class` syntax not yet implemented in --compat mode",
516 self.peek_line(),
517 ));
518 }
519 self.parse_class_decl(false, false)?
520 }
521 "abstract" => {
522 self.advance(); if !matches!(self.peek(), Token::Ident(ref s) if s == "class") {
524 return Err(self.syntax_err(
525 "`abstract` must be followed by `class`",
526 self.peek_line(),
527 ));
528 }
529 self.parse_class_decl(true, false)?
530 }
531 "final" => {
532 self.advance(); if !matches!(self.peek(), Token::Ident(ref s) if s == "class") {
534 return Err(self
535 .syntax_err("`final` must be followed by `class`", self.peek_line()));
536 }
537 self.parse_class_decl(false, true)?
538 }
539 "trait" => {
540 if crate::compat_mode() {
541 return Err(self.syntax_err(
542 "`trait` is a stryke extension (disabled by --compat)",
543 self.peek_line(),
544 ));
545 }
546 self.parse_trait_decl()?
547 }
548 "my" => self.parse_my_our_local("my", false)?,
549 "state" => self.parse_my_our_local("state", false)?,
550 "mysync" => {
551 if crate::compat_mode() {
552 return Err(self.syntax_err(
553 "`mysync` is a stryke extension (disabled by --compat)",
554 self.peek_line(),
555 ));
556 }
557 self.parse_my_our_local("mysync", false)?
558 }
559 "frozen" | "const" => {
560 let leading = kw.as_str().to_string();
561 if crate::compat_mode() {
562 return Err(self.syntax_err(
563 format!("`{leading}` is a stryke extension (disabled by --compat)"),
564 self.peek_line(),
565 ));
566 }
567 self.advance(); if let Token::Ident(ref kw) = self.peek().clone() {
573 if kw == "my" {
574 let mut stmt = self.parse_my_our_local("my", false)?;
575 if let StmtKind::My(ref mut decls) = stmt.kind {
576 for decl in decls.iter_mut() {
577 decl.frozen = true;
578 }
579 }
580 stmt
581 } else {
582 return Err(self.syntax_err(
583 format!("Expected 'my' after '{leading}'"),
584 self.peek_line(),
585 ));
586 }
587 } else {
588 return Err(self.syntax_err(
589 format!("Expected 'my' after '{leading}'"),
590 self.peek_line(),
591 ));
592 }
593 }
594 "typed" => {
595 if crate::compat_mode() {
596 return Err(self.syntax_err(
597 "`typed` is a stryke extension (disabled by --compat)",
598 self.peek_line(),
599 ));
600 }
601 self.advance();
602 if let Token::Ident(ref kw) = self.peek().clone() {
603 if kw == "my" {
604 self.parse_my_our_local("my", true)?
605 } else {
606 return Err(
607 self.syntax_err("Expected 'my' after 'typed'", self.peek_line())
608 );
609 }
610 } else {
611 return Err(
612 self.syntax_err("Expected 'my' after 'typed'", self.peek_line())
613 );
614 }
615 }
616 "our" => self.parse_my_our_local("our", false)?,
617 "local" => self.parse_my_our_local("local", false)?,
618 "package" => self.parse_package()?,
619 "use" => self.parse_use()?,
620 "no" => self.parse_no()?,
621 "return" => self.parse_return()?,
622 "last" => {
623 self.advance();
624 let lbl = if let Token::Ident(ref s) = self.peek() {
625 if s.chars().all(|c| c.is_uppercase() || c == '_') {
626 let (Token::Ident(l), _) = self.advance() else {
627 unreachable!()
628 };
629 Some(l)
630 } else {
631 None
632 }
633 } else {
634 None
635 };
636 let stmt = Statement {
637 label: None,
638 kind: StmtKind::Last(lbl.or(label.clone())),
639 line,
640 };
641 self.parse_stmt_postfix_modifier(stmt)?
642 }
643 "next" => {
644 self.advance();
645 let lbl = if let Token::Ident(ref s) = self.peek() {
646 if s.chars().all(|c| c.is_uppercase() || c == '_') {
647 let (Token::Ident(l), _) = self.advance() else {
648 unreachable!()
649 };
650 Some(l)
651 } else {
652 None
653 }
654 } else {
655 None
656 };
657 let stmt = Statement {
658 label: None,
659 kind: StmtKind::Next(lbl.or(label.clone())),
660 line,
661 };
662 self.parse_stmt_postfix_modifier(stmt)?
663 }
664 "redo" => {
665 self.advance();
666 self.eat(&Token::Semicolon);
667 Statement {
668 label: None,
669 kind: StmtKind::Redo(label.clone()),
670 line,
671 }
672 }
673 "BEGIN" => {
674 self.advance();
675 let block = self.parse_block()?;
676 Statement {
677 label: None,
678 kind: StmtKind::Begin(block),
679 line,
680 }
681 }
682 "END" => {
683 self.advance();
684 let block = self.parse_block()?;
685 Statement {
686 label: None,
687 kind: StmtKind::End(block),
688 line,
689 }
690 }
691 "UNITCHECK" => {
692 self.advance();
693 let block = self.parse_block()?;
694 Statement {
695 label: None,
696 kind: StmtKind::UnitCheck(block),
697 line,
698 }
699 }
700 "CHECK" => {
701 self.advance();
702 let block = self.parse_block()?;
703 Statement {
704 label: None,
705 kind: StmtKind::Check(block),
706 line,
707 }
708 }
709 "INIT" => {
710 self.advance();
711 let block = self.parse_block()?;
712 Statement {
713 label: None,
714 kind: StmtKind::Init(block),
715 line,
716 }
717 }
718 "goto" => {
719 self.advance();
720 let target = self.parse_expression()?;
721 let stmt = Statement {
722 label: None,
723 kind: StmtKind::Goto {
724 target: Box::new(target),
725 },
726 line,
727 };
728 self.parse_stmt_postfix_modifier(stmt)?
730 }
731 "continue" => {
732 self.advance();
733 let block = self.parse_block()?;
734 Statement {
735 label: None,
736 kind: StmtKind::Continue(block),
737 line,
738 }
739 }
740 "try" => self.parse_try_catch()?,
741 "defer" => self.parse_defer_stmt()?,
742 "tie" => self.parse_tie_stmt()?,
743 "given" => self.parse_given()?,
744 "when" => self.parse_when_stmt()?,
745 "default" => self.parse_default_stmt()?,
746 "eval_timeout" => self.parse_eval_timeout()?,
747 "do" => {
748 if matches!(self.peek_at(1), Token::LBrace) {
749 self.advance();
750 let body = self.parse_block()?;
751 if let Token::Ident(ref w) = self.peek().clone() {
752 if w == "while" {
753 self.advance();
754 self.expect(&Token::LParen)?;
755 let mut condition = self.parse_expression()?;
756 Self::mark_match_scalar_g_for_boolean_condition(&mut condition);
757 self.expect(&Token::RParen)?;
758 self.eat(&Token::Semicolon);
759 Statement {
760 label: label.clone(),
761 kind: StmtKind::DoWhile { body, condition },
762 line,
763 }
764 } else {
765 let inner_line = body.first().map(|s| s.line).unwrap_or(line);
766 let inner = Expr {
767 kind: ExprKind::CodeRef {
768 params: vec![],
769 body,
770 },
771 line: inner_line,
772 };
773 let expr = Expr {
774 kind: ExprKind::Do(Box::new(inner)),
775 line,
776 };
777 let stmt = Statement {
778 label: label.clone(),
779 kind: StmtKind::Expression(expr),
780 line,
781 };
782 self.parse_stmt_postfix_modifier(stmt)?
784 }
785 } else {
786 let inner_line = body.first().map(|s| s.line).unwrap_or(line);
787 let inner = Expr {
788 kind: ExprKind::CodeRef {
789 params: vec![],
790 body,
791 },
792 line: inner_line,
793 };
794 let expr = Expr {
795 kind: ExprKind::Do(Box::new(inner)),
796 line,
797 };
798 let stmt = Statement {
799 label: label.clone(),
800 kind: StmtKind::Expression(expr),
801 line,
802 };
803 self.parse_stmt_postfix_modifier(stmt)?
804 }
805 } else {
806 if let Some(expr) = self.try_parse_bareword_stmt_call() {
807 let stmt = self.maybe_postfix_modifier(expr)?;
808 self.parse_stmt_postfix_modifier(stmt)?
809 } else {
810 let expr = self.parse_expression()?;
811 let stmt = self.maybe_postfix_modifier(expr)?;
812 self.parse_stmt_postfix_modifier(stmt)?
813 }
814 }
815 }
816 _ => {
817 if let Some(expr) = self.try_parse_bareword_stmt_call() {
819 let stmt = self.maybe_postfix_modifier(expr)?;
820 self.parse_stmt_postfix_modifier(stmt)?
821 } else {
822 let expr = self.parse_expression()?;
823 let stmt = self.maybe_postfix_modifier(expr)?;
824 self.parse_stmt_postfix_modifier(stmt)?
825 }
826 }
827 },
828 Token::LBrace => {
829 let block = self.parse_block()?;
830 let stmt = Statement {
831 label: None,
832 kind: StmtKind::Block(block),
833 line,
834 };
835 self.parse_stmt_postfix_modifier(stmt)?
837 }
838 _ => {
839 let expr = self.parse_expression()?;
840 let stmt = self.maybe_postfix_modifier(expr)?;
841 self.parse_stmt_postfix_modifier(stmt)?
842 }
843 };
844
845 stmt.label = label;
846 Ok(stmt)
847 }
848
849 fn parse_stmt_postfix_modifier(&mut self, stmt: Statement) -> PerlResult<Statement> {
851 let line = stmt.line;
852 if self.peek_line() > self.prev_line() {
857 self.eat(&Token::Semicolon);
858 return Ok(stmt);
859 }
860 if let Token::Ident(ref kw) = self.peek().clone() {
861 match kw.as_str() {
862 "if" => {
863 self.advance();
864 let mut cond = self.parse_expression()?;
865 Self::mark_match_scalar_g_for_boolean_condition(&mut cond);
866 self.eat(&Token::Semicolon);
867 return Ok(Statement {
868 label: None,
869 kind: StmtKind::If {
870 condition: cond,
871 body: vec![stmt],
872 elsifs: vec![],
873 else_block: None,
874 },
875 line,
876 });
877 }
878 "unless" => {
879 self.advance();
880 let mut cond = self.parse_expression()?;
881 Self::mark_match_scalar_g_for_boolean_condition(&mut cond);
882 self.eat(&Token::Semicolon);
883 return Ok(Statement {
884 label: None,
885 kind: StmtKind::Unless {
886 condition: cond,
887 body: vec![stmt],
888 else_block: None,
889 },
890 line,
891 });
892 }
893 "while" | "until" | "for" | "foreach" => {
894 if let Some(expr) = Self::stmt_into_postfix_body_expr(stmt) {
897 let out = self.maybe_postfix_modifier(expr)?;
898 self.eat(&Token::Semicolon);
899 return Ok(out);
900 }
901 return Err(self.syntax_err(
902 format!("postfix `{}` is not supported on this statement form", kw),
903 self.peek_line(),
904 ));
905 }
906 "pmap" | "pflat_map" | "pgrep" | "pfor" | "preduce" | "pcache" => {
908 let line = stmt.line;
909 let block = self.stmt_into_parallel_block(stmt)?;
910 let which = kw.as_str();
911 self.advance();
912 self.eat(&Token::Comma);
913 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
914 self.eat(&Token::Semicolon);
915 let list = Box::new(list);
916 let progress = progress.map(Box::new);
917 let kind = match which {
918 "pmap" => ExprKind::PMapExpr {
919 block,
920 list,
921 progress,
922 flat_outputs: false,
923 on_cluster: None,
924 stream: false,
925 },
926 "pflat_map" => ExprKind::PMapExpr {
927 block,
928 list,
929 progress,
930 flat_outputs: true,
931 on_cluster: None,
932 stream: false,
933 },
934 "pgrep" => ExprKind::PGrepExpr {
935 block,
936 list,
937 progress,
938 stream: false,
939 },
940 "pfor" => ExprKind::PForExpr {
941 block,
942 list,
943 progress,
944 },
945 "preduce" => ExprKind::PReduceExpr {
946 block,
947 list,
948 progress,
949 },
950 "pcache" => ExprKind::PcacheExpr {
951 block,
952 list,
953 progress,
954 },
955 _ => unreachable!(),
956 };
957 return Ok(Statement {
958 label: None,
959 kind: StmtKind::Expression(Expr { kind, line }),
960 line,
961 });
962 }
963 _ => {}
964 }
965 }
966 self.eat(&Token::Semicolon);
967 Ok(stmt)
968 }
969
970 fn stmt_into_parallel_block(&self, stmt: Statement) -> PerlResult<Block> {
973 let line = stmt.line;
974 match stmt.kind {
975 StmtKind::Block(block) => Ok(block),
976 StmtKind::Expression(expr) => {
977 if let ExprKind::Do(ref inner) = expr.kind {
978 if let ExprKind::CodeRef { ref body, .. } = inner.kind {
979 return Ok(body.clone());
980 }
981 }
982 Ok(vec![Statement {
983 label: None,
984 kind: StmtKind::Expression(expr),
985 line,
986 }])
987 }
988 _ => Err(self.syntax_err(
989 "postfix parallel op expects `do { }`, a bare `{ }` block, or an expression statement",
990 line,
991 )),
992 }
993 }
994
995 fn stmt_into_postfix_body_expr(stmt: Statement) -> Option<Expr> {
998 match stmt.kind {
999 StmtKind::Expression(expr) => Some(expr),
1000 StmtKind::Block(block) => {
1001 let line = stmt.line;
1002 let inner = Expr {
1003 kind: ExprKind::CodeRef {
1004 params: vec![],
1005 body: block,
1006 },
1007 line,
1008 };
1009 Some(Expr {
1010 kind: ExprKind::Do(Box::new(inner)),
1011 line,
1012 })
1013 }
1014 _ => None,
1015 }
1016 }
1017
1018 fn peek_is_postfix_stmt_modifier_keyword(&self) -> bool {
1021 matches!(
1022 self.peek(),
1023 Token::Ident(ref kw)
1024 if matches!(
1025 kw.as_str(),
1026 "if" | "unless" | "while" | "until" | "for" | "foreach"
1027 )
1028 )
1029 }
1030
1031 fn maybe_postfix_modifier(&mut self, expr: Expr) -> PerlResult<Statement> {
1032 let line = expr.line;
1033 if self.peek_line() > self.prev_line() {
1035 return Ok(Statement {
1036 label: None,
1037 kind: StmtKind::Expression(expr),
1038 line,
1039 });
1040 }
1041 match self.peek() {
1042 Token::Ident(ref kw) => match kw.as_str() {
1043 "if" => {
1044 self.advance();
1045 let cond = self.parse_expression()?;
1046 Ok(Statement {
1047 label: None,
1048 kind: StmtKind::Expression(Expr {
1049 kind: ExprKind::PostfixIf {
1050 expr: Box::new(expr),
1051 condition: Box::new(cond),
1052 },
1053 line,
1054 }),
1055 line,
1056 })
1057 }
1058 "unless" => {
1059 self.advance();
1060 let cond = self.parse_expression()?;
1061 Ok(Statement {
1062 label: None,
1063 kind: StmtKind::Expression(Expr {
1064 kind: ExprKind::PostfixUnless {
1065 expr: Box::new(expr),
1066 condition: Box::new(cond),
1067 },
1068 line,
1069 }),
1070 line,
1071 })
1072 }
1073 "while" => {
1074 self.advance();
1075 let cond = self.parse_expression()?;
1076 Ok(Statement {
1077 label: None,
1078 kind: StmtKind::Expression(Expr {
1079 kind: ExprKind::PostfixWhile {
1080 expr: Box::new(expr),
1081 condition: Box::new(cond),
1082 },
1083 line,
1084 }),
1085 line,
1086 })
1087 }
1088 "until" => {
1089 self.advance();
1090 let cond = self.parse_expression()?;
1091 Ok(Statement {
1092 label: None,
1093 kind: StmtKind::Expression(Expr {
1094 kind: ExprKind::PostfixUntil {
1095 expr: Box::new(expr),
1096 condition: Box::new(cond),
1097 },
1098 line,
1099 }),
1100 line,
1101 })
1102 }
1103 "for" | "foreach" => {
1104 self.advance();
1105 let list = self.parse_expression()?;
1106 Ok(Statement {
1107 label: None,
1108 kind: StmtKind::Expression(Expr {
1109 kind: ExprKind::PostfixForeach {
1110 expr: Box::new(expr),
1111 list: Box::new(list),
1112 },
1113 line,
1114 }),
1115 line,
1116 })
1117 }
1118 _ => Ok(Statement {
1119 label: None,
1120 kind: StmtKind::Expression(expr),
1121 line,
1122 }),
1123 },
1124 _ => Ok(Statement {
1125 label: None,
1126 kind: StmtKind::Expression(expr),
1127 line,
1128 }),
1129 }
1130 }
1131
1132 fn try_parse_bareword_stmt_call(&mut self) -> Option<Expr> {
1134 let saved = self.pos;
1135 let line = self.peek_line();
1136 let mut name = match self.peek() {
1137 Token::Ident(n) => n.clone(),
1138 _ => return None,
1139 };
1140 if name.starts_with('\x00') || !Self::bareword_stmt_may_be_sub(&name) {
1142 return None;
1143 }
1144 self.advance();
1145 while self.eat(&Token::PackageSep) {
1146 match self.advance() {
1147 (Token::Ident(part), _) => {
1148 name = format!("{}::{}", name, part);
1149 }
1150 _ => {
1151 self.pos = saved;
1152 return None;
1153 }
1154 }
1155 }
1156 match self.peek() {
1157 Token::Semicolon | Token::RBrace => Some(Expr {
1158 kind: ExprKind::FuncCall { name, args: vec![] },
1159 line,
1160 }),
1161 _ => {
1162 self.pos = saved;
1163 None
1164 }
1165 }
1166 }
1167
1168 fn bareword_stmt_may_be_sub(name: &str) -> bool {
1170 !matches!(
1171 name,
1172 "__FILE__"
1173 | "__LINE__"
1174 | "abs"
1175 | "async"
1176 | "spawn"
1177 | "atan2"
1178 | "await"
1179 | "barrier"
1180 | "bless"
1181 | "caller"
1182 | "capture"
1183 | "cat"
1184 | "chdir"
1185 | "chmod"
1186 | "chomp"
1187 | "chop"
1188 | "chr"
1189 | "chown"
1190 | "closedir"
1191 | "close"
1192 | "collect"
1193 | "cos"
1194 | "crypt"
1195 | "defined"
1196 | "dec"
1197 | "delete"
1198 | "die"
1199 | "deque"
1200 | "do"
1201 | "each"
1202 | "eof"
1203 | "fore"
1204 | "eval"
1205 | "exec"
1206 | "exists"
1207 | "exit"
1208 | "exp"
1209 | "fan"
1210 | "fan_cap"
1211 | "fc"
1212 | "fetch_url"
1213 | "d"
1214 | "dirs"
1215 | "dr"
1216 | "f"
1217 | "files"
1218 | "filesf"
1219 | "filter"
1220 | "fr"
1221 | "getcwd"
1222 | "glob_par"
1223 | "par_sed"
1224 | "glob"
1225 | "grep"
1226 | "greps"
1227 | "heap"
1228 | "hex"
1229 | "inc"
1230 | "index"
1231 | "int"
1232 | "join"
1233 | "keys"
1234 | "lcfirst"
1235 | "lc"
1236 | "length"
1237 | "link"
1238 | "log"
1239 | "lstat"
1240 | "map"
1241 | "flat_map"
1242 | "maps"
1243 | "flat_maps"
1244 | "flatten"
1245 | "frequencies"
1246 | "freq"
1247 | "interleave"
1248 | "ddump"
1249 | "stringify"
1250 | "str"
1251 | "s"
1252 | "input"
1253 | "lines"
1254 | "words"
1255 | "chars"
1256 | "digits"
1257 | "letters"
1258 | "letters_uc"
1259 | "letters_lc"
1260 | "punctuation"
1261 | "sentences"
1262 | "paragraphs"
1263 | "sections"
1264 | "numbers"
1265 | "graphemes"
1266 | "columns"
1267 | "trim"
1268 | "avg"
1269 | "top"
1270 | "pager"
1271 | "pg"
1272 | "less"
1273 | "count_by"
1274 | "to_file"
1275 | "to_json"
1276 | "to_csv"
1277 | "grep_v"
1278 | "select_keys"
1279 | "pluck"
1280 | "clamp"
1281 | "normalize"
1282 | "stddev"
1283 | "squared"
1284 | "square"
1285 | "cubed"
1286 | "cube"
1287 | "expt"
1288 | "pow"
1289 | "pw"
1290 | "snake_case"
1291 | "camel_case"
1292 | "kebab_case"
1293 | "to_toml"
1294 | "to_yaml"
1295 | "to_xml"
1296 | "to_html"
1297 | "to_markdown"
1298 | "xopen"
1299 | "clip"
1300 | "paste"
1301 | "to_table"
1302 | "sparkline"
1303 | "bar_chart"
1304 | "flame"
1305 | "set"
1306 | "list_count"
1307 | "list_size"
1308 | "count"
1309 | "size"
1310 | "cnt"
1311 | "len"
1312 | "all"
1313 | "any"
1314 | "none"
1315 | "take_while"
1316 | "drop_while"
1317 | "skip_while"
1318 | "skip"
1319 | "first_or"
1320 | "tap"
1321 | "peek"
1322 | "partition"
1323 | "min_by"
1324 | "max_by"
1325 | "zip_with"
1326 | "group_by"
1327 | "chunk_by"
1328 | "with_index"
1329 | "puniq"
1330 | "pfirst"
1331 | "pany"
1332 | "uniq"
1333 | "distinct"
1334 | "shuffle"
1335 | "shuffled"
1336 | "chunked"
1337 | "windowed"
1338 | "match"
1339 | "mkdir"
1340 | "every"
1341 | "gen"
1342 | "oct"
1343 | "open"
1344 | "p"
1345 | "opendir"
1346 | "ord"
1347 | "par_lines"
1348 | "par_walk"
1349 | "pipe"
1350 | "pipes"
1351 | "block_devices"
1352 | "char_devices"
1353 | "rate_limit"
1354 | "retry"
1355 | "pcache"
1356 | "pchannel"
1357 | "pfor"
1358 | "pgrep"
1359 | "pgreps"
1360 | "pipeline"
1361 | "pmap_chunked"
1362 | "pmap_reduce"
1363 | "pmap_on"
1364 | "pflat_map_on"
1365 | "pmap"
1366 | "pmaps"
1367 | "pflat_map"
1368 | "pflat_maps"
1369 | "pop"
1370 | "pos"
1371 | "ppool"
1372 | "preduce_init"
1373 | "preduce"
1374 | "pselect"
1375 | "printf"
1376 | "print"
1377 | "pr"
1378 | "psort"
1379 | "push"
1380 | "pwatch"
1381 | "rand"
1382 | "readdir"
1383 | "readlink"
1384 | "reduce"
1385 | "fold"
1386 | "inject"
1387 | "first"
1388 | "detect"
1389 | "find"
1390 | "find_all"
1391 | "ref"
1392 | "rename"
1393 | "require"
1394 | "rev"
1395 | "reverse"
1396 | "reversed"
1397 | "rewinddir"
1398 | "rindex"
1399 | "rmdir"
1400 | "rm"
1401 | "say"
1402 | "scalar"
1403 | "seekdir"
1404 | "shift"
1405 | "sin"
1406 | "slurp"
1407 | "sockets"
1408 | "sort"
1409 | "splice"
1410 | "split"
1411 | "sprintf"
1412 | "sqrt"
1413 | "srand"
1414 | "stat"
1415 | "study"
1416 | "substr"
1417 | "symlink"
1418 | "sym_links"
1419 | "system"
1420 | "telldir"
1421 | "timer"
1422 | "trace"
1423 | "ucfirst"
1424 | "uc"
1425 | "undef"
1426 | "umask"
1427 | "unlink"
1428 | "unshift"
1429 | "utime"
1430 | "values"
1431 | "wantarray"
1432 | "warn"
1433 | "watch"
1434 | "yield"
1435 | "sub"
1436 )
1437 }
1438
1439 fn parse_block(&mut self) -> PerlResult<Block> {
1440 self.expect(&Token::LBrace)?;
1441 let mut stmts = Vec::new();
1442 if let Some(param_stmts) = self.try_parse_block_params()? {
1446 stmts.extend(param_stmts);
1447 }
1448 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
1449 if self.eat(&Token::Semicolon) {
1450 continue;
1451 }
1452 stmts.push(self.parse_statement()?);
1453 }
1454 self.expect(&Token::RBrace)?;
1455 Self::default_topic_for_sole_bareword(&mut stmts);
1456 Ok(stmts)
1457 }
1458
1459 fn try_parse_block_params(&mut self) -> PerlResult<Option<Vec<Statement>>> {
1464 if !matches!(self.peek(), Token::BitOr) {
1465 return Ok(None);
1466 }
1467 let mut i = 1; loop {
1470 match self.peek_at(i) {
1471 Token::ScalarVar(_) => i += 1,
1472 _ => return Ok(None), }
1474 match self.peek_at(i) {
1475 Token::BitOr => break, Token::Comma => i += 1, _ => return Ok(None), }
1479 }
1480 let line = self.peek_line();
1482 self.advance(); let mut names = Vec::new();
1484 loop {
1485 if let Token::ScalarVar(ref name) = self.peek().clone() {
1486 names.push(name.clone());
1487 self.advance();
1488 }
1489 if self.eat(&Token::BitOr) {
1490 break;
1491 }
1492 self.expect(&Token::Comma)?;
1493 }
1494 let sources: Vec<&str> = match names.len() {
1499 1 => vec!["_"],
1500 2 => vec!["a", "b"],
1501 n => {
1502 let _ = n;
1504 vec![] }
1506 };
1507 let mut stmts = Vec::with_capacity(names.len());
1508 if !sources.is_empty() {
1509 for (name, src) in names.iter().zip(sources.iter()) {
1510 stmts.push(Statement {
1511 label: None,
1512 kind: StmtKind::My(vec![VarDecl {
1513 sigil: Sigil::Scalar,
1514 name: name.clone(),
1515 initializer: Some(Expr {
1516 kind: ExprKind::ScalarVar(src.to_string()),
1517 line,
1518 }),
1519 frozen: false,
1520 type_annotation: None,
1521 }]),
1522 line,
1523 });
1524 }
1525 } else {
1526 for (idx, name) in names.iter().enumerate() {
1528 let src = if idx == 0 {
1529 "_".to_string()
1530 } else {
1531 format!("_{idx}")
1532 };
1533 stmts.push(Statement {
1534 label: None,
1535 kind: StmtKind::My(vec![VarDecl {
1536 sigil: Sigil::Scalar,
1537 name: name.clone(),
1538 initializer: Some(Expr {
1539 kind: ExprKind::ScalarVar(src),
1540 line,
1541 }),
1542 frozen: false,
1543 type_annotation: None,
1544 }]),
1545 line,
1546 });
1547 }
1548 }
1549 Ok(Some(stmts))
1550 }
1551
1552 fn default_topic_for_sole_bareword(stmts: &mut [Statement]) {
1567 let [only] = stmts else { return };
1568 let StmtKind::Expression(ref mut expr) = only.kind else {
1569 return;
1570 };
1571 let topic_line = expr.line;
1572 let topic_arg = || Expr {
1573 kind: ExprKind::ScalarVar("_".to_string()),
1574 line: topic_line,
1575 };
1576 match expr.kind {
1577 ExprKind::FuncCall {
1579 ref name,
1580 ref mut args,
1581 } if args.is_empty()
1582 && (Self::is_known_bareword(name) || Self::is_try_builtin_name(name)) =>
1583 {
1584 args.push(topic_arg());
1585 }
1586 ExprKind::Bareword(ref name)
1590 if (Self::is_known_bareword(name) || Self::is_try_builtin_name(name)) =>
1591 {
1592 let n = name.clone();
1593 expr.kind = ExprKind::FuncCall {
1594 name: n,
1595 args: vec![topic_arg()],
1596 };
1597 }
1598 _ => {}
1599 }
1600 }
1601
1602 fn parse_defer_stmt(&mut self) -> PerlResult<Statement> {
1606 let line = self.peek_line();
1607 self.advance(); let body = self.parse_block()?;
1609 self.eat(&Token::Semicolon);
1610 let coderef = Expr {
1612 kind: ExprKind::CodeRef {
1613 params: vec![],
1614 body,
1615 },
1616 line,
1617 };
1618 Ok(Statement {
1619 label: None,
1620 kind: StmtKind::Expression(Expr {
1621 kind: ExprKind::FuncCall {
1622 name: "defer__internal".to_string(),
1623 args: vec![coderef],
1624 },
1625 line,
1626 }),
1627 line,
1628 })
1629 }
1630
1631 fn parse_try_catch(&mut self) -> PerlResult<Statement> {
1633 let line = self.peek_line();
1634 self.advance(); let try_block = self.parse_block()?;
1636 match self.peek() {
1637 Token::Ident(ref k) if k == "catch" => {
1638 self.advance();
1639 }
1640 _ => {
1641 return Err(self.syntax_err("expected 'catch' after try block", self.peek_line()));
1642 }
1643 }
1644 self.expect(&Token::LParen)?;
1645 let catch_var = self.parse_scalar_var_name()?;
1646 self.expect(&Token::RParen)?;
1647 let catch_block = self.parse_block()?;
1648 let finally_block = match self.peek() {
1649 Token::Ident(ref k) if k == "finally" => {
1650 self.advance();
1651 Some(self.parse_block()?)
1652 }
1653 _ => None,
1654 };
1655 self.eat(&Token::Semicolon);
1656 Ok(Statement {
1657 label: None,
1658 kind: StmtKind::TryCatch {
1659 try_block,
1660 catch_var,
1661 catch_block,
1662 finally_block,
1663 },
1664 line,
1665 })
1666 }
1667
1668 fn parse_thread_macro(&mut self, _line: usize) -> PerlResult<Expr> {
1680 let pipe_rhs_wrap = self.in_pipe_rhs();
1681 let mut result = if pipe_rhs_wrap {
1682 Expr {
1683 kind: ExprKind::ArrayElement {
1684 array: "_".to_string(),
1685 index: Box::new(Expr {
1686 kind: ExprKind::Integer(0),
1687 line: _line,
1688 }),
1689 },
1690 line: _line,
1691 }
1692 } else {
1693 self.suppress_parenless_call = self.suppress_parenless_call.saturating_add(1);
1696 let expr = self.parse_thread_input();
1697 self.suppress_parenless_call = self.suppress_parenless_call.saturating_sub(1);
1698 expr?
1699 };
1700
1701 loop {
1703 match self.peek() {
1705 Token::Semicolon
1706 | Token::Newline
1707 | Token::RBrace
1708 | Token::RParen
1709 | Token::RBracket
1710 | Token::PipeForward
1711 | Token::Eof => break,
1712 _ => {}
1713 }
1714
1715 let stage_line = self.peek_line();
1716
1717 match self.peek().clone() {
1719 Token::ArrowBrace => {
1721 self.advance(); let mut stmts = Vec::new();
1723 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
1724 if self.eat(&Token::Semicolon) {
1725 continue;
1726 }
1727 stmts.push(self.parse_statement()?);
1728 }
1729 self.expect(&Token::RBrace)?;
1730 let code_ref = Expr {
1731 kind: ExprKind::CodeRef {
1732 params: vec![],
1733 body: stmts,
1734 },
1735 line: stage_line,
1736 };
1737 result = self.pipe_forward_apply(result, code_ref, stage_line)?;
1738 }
1739 Token::Ident(ref name) if name == "sub" || name == "fn" => {
1741 self.advance(); let (params, _prototype) = self.parse_sub_sig_or_prototype_opt()?;
1743 let body = self.parse_block()?;
1744 let code_ref = Expr {
1745 kind: ExprKind::CodeRef { params, body },
1746 line: stage_line,
1747 };
1748 result = self.pipe_forward_apply(result, code_ref, stage_line)?;
1749 }
1750 Token::Ident(ref name) => {
1752 let func_name = name.clone();
1753 self.advance();
1754
1755 if func_name.starts_with('\x00') {
1757 let parts: Vec<&str> = func_name.split('\x00').collect();
1758 if parts.len() >= 4 && parts[1] == "s" {
1759 let delim = parts.get(5).and_then(|s| s.chars().next()).unwrap_or('/');
1760 let stage = Expr {
1761 kind: ExprKind::Substitution {
1762 expr: Box::new(result.clone()),
1763 pattern: parts[2].to_string(),
1764 replacement: parts[3].to_string(),
1765 flags: format!("{}r", parts.get(4).unwrap_or(&"")),
1766 delim,
1767 },
1768 line: stage_line,
1769 };
1770 result = stage;
1771 continue;
1772 }
1773 if parts.len() >= 4 && parts[1] == "tr" {
1774 let delim = parts.get(5).and_then(|s| s.chars().next()).unwrap_or('/');
1775 let stage = Expr {
1776 kind: ExprKind::Transliterate {
1777 expr: Box::new(result.clone()),
1778 from: parts[2].to_string(),
1779 to: parts[3].to_string(),
1780 flags: format!("{}r", parts.get(4).unwrap_or(&"")),
1781 delim,
1782 },
1783 line: stage_line,
1784 };
1785 result = stage;
1786 continue;
1787 }
1788 return Err(
1789 self.syntax_err("Unexpected encoded token in thread", stage_line)
1790 );
1791 }
1792
1793 if matches!(self.peek(), Token::Plus)
1798 && matches!(self.peek_at(1), Token::LBrace)
1799 {
1800 self.advance(); self.expect(&Token::LBrace)?;
1802 let pairs = self.try_parse_hash_ref()?;
1804 let hashref_expr = Expr {
1805 kind: ExprKind::HashRef(pairs),
1806 line: stage_line,
1807 };
1808 let flatten_array_refs =
1809 matches!(func_name.as_str(), "flat_map" | "flat_maps");
1810 let stream = matches!(func_name.as_str(), "maps" | "flat_maps");
1811 let placeholder = Expr {
1813 kind: ExprKind::Undef,
1814 line: stage_line,
1815 };
1816 let map_node = Expr {
1817 kind: ExprKind::MapExprComma {
1818 expr: Box::new(hashref_expr),
1819 list: Box::new(placeholder),
1820 flatten_array_refs,
1821 stream,
1822 },
1823 line: stage_line,
1824 };
1825 result = self.pipe_forward_apply(result, map_node, stage_line)?;
1826 } else if func_name == "pmap_chunked" {
1828 let chunk_size = self.parse_assign_expr()?;
1829 let block = self.parse_block_or_bareword_block()?;
1830 let placeholder = self.pipe_placeholder_list(stage_line);
1831 let stage = Expr {
1832 kind: ExprKind::PMapChunkedExpr {
1833 chunk_size: Box::new(chunk_size),
1834 block,
1835 list: Box::new(placeholder),
1836 progress: None,
1837 },
1838 line: stage_line,
1839 };
1840 result = self.pipe_forward_apply(result, stage, stage_line)?;
1841 } else if func_name == "preduce_init" {
1843 let init = self.parse_assign_expr()?;
1844 let block = self.parse_block_or_bareword_block()?;
1845 let placeholder = self.pipe_placeholder_list(stage_line);
1846 let stage = Expr {
1847 kind: ExprKind::PReduceInitExpr {
1848 init: Box::new(init),
1849 block,
1850 list: Box::new(placeholder),
1851 progress: None,
1852 },
1853 line: stage_line,
1854 };
1855 result = self.pipe_forward_apply(result, stage, stage_line)?;
1856 } else if func_name == "pmap_reduce" {
1858 let map_block = self.parse_block_or_bareword_block()?;
1859 let reduce_block = if matches!(self.peek(), Token::LBrace) {
1860 self.parse_block()?
1861 } else {
1862 self.expect(&Token::Comma)?;
1863 self.parse_block_or_bareword_cmp_block()?
1864 };
1865 let placeholder = self.pipe_placeholder_list(stage_line);
1866 let stage = Expr {
1867 kind: ExprKind::PMapReduceExpr {
1868 map_block,
1869 reduce_block,
1870 list: Box::new(placeholder),
1871 progress: None,
1872 },
1873 line: stage_line,
1874 };
1875 result = self.pipe_forward_apply(result, stage, stage_line)?;
1876 } else if matches!(self.peek(), Token::LBrace) {
1878 self.pipe_rhs_depth = self.pipe_rhs_depth.saturating_add(1);
1880 let stage = self.parse_thread_stage_with_block(&func_name, stage_line)?;
1881 self.pipe_rhs_depth = self.pipe_rhs_depth.saturating_sub(1);
1882 result = self.pipe_forward_apply(result, stage, stage_line)?;
1883 } else if matches!(self.peek(), Token::LParen) {
1884 self.advance(); let mut call_args = Vec::new();
1895 while !matches!(self.peek(), Token::RParen | Token::Eof) {
1896 call_args.push(self.parse_assign_expr()?);
1897 if !self.eat(&Token::Comma) {
1898 break;
1899 }
1900 }
1901 self.expect(&Token::RParen)?;
1902 if !call_args.iter().any(Self::expr_contains_topic_var) {
1905 call_args.insert(
1906 0,
1907 Expr {
1908 kind: ExprKind::ScalarVar("_".to_string()),
1909 line: stage_line,
1910 },
1911 );
1912 }
1913 let call_expr = Expr {
1914 kind: ExprKind::FuncCall {
1915 name: func_name.clone(),
1916 args: call_args,
1917 },
1918 line: stage_line,
1919 };
1920 let code_ref = Expr {
1921 kind: ExprKind::CodeRef {
1922 params: vec![],
1923 body: vec![Statement {
1924 label: None,
1925 kind: StmtKind::Expression(call_expr),
1926 line: stage_line,
1927 }],
1928 },
1929 line: stage_line,
1930 };
1931 result = self.pipe_forward_apply(result, code_ref, stage_line)?;
1932 } else {
1933 result = self.thread_apply_bare_func(&func_name, result, stage_line)?;
1935 }
1936 }
1937 Token::Regex(ref pattern, ref flags, delim) => {
1939 let pattern = pattern.clone();
1940 let flags = flags.clone();
1941 self.advance();
1942 result =
1943 self.thread_regex_grep_stage(result, pattern, flags, delim, stage_line);
1944 }
1945 Token::Slash => {
1948 self.advance(); if let Token::Ident(ref ident_s) = self.peek().clone() {
1954 if matches!(ident_s.as_str(), "m" | "s" | "tr" | "y" | "qr")
1955 && matches!(self.peek_at(1), Token::Regex(..))
1956 {
1957 self.advance(); if let Token::Regex(ref misparsed_pattern, ref misparsed_flags, _) =
1964 self.peek().clone()
1965 {
1966 let _ = (misparsed_pattern, misparsed_flags);
1976 }
1977 }
1978 }
1979
1980 let mut pattern = String::new();
1982 loop {
1983 match self.peek().clone() {
1984 Token::Slash => {
1985 self.advance(); break;
1987 }
1988 Token::Eof | Token::Semicolon | Token::Newline => {
1989 return Err(self
1990 .syntax_err("Unterminated regex in thread stage", stage_line));
1991 }
1992 Token::Regex(ref inner_pattern, ref inner_flags, delim) => {
1994 if pattern.is_empty()
2002 || matches!(pattern.as_str(), "m" | "s" | "tr" | "y" | "qr")
2003 {
2004 let _ = (inner_pattern, inner_flags, delim);
2010 }
2011 return Err(self.syntax_err(
2013 "Complex regex in thread stage - use m/pattern/ syntax instead",
2014 stage_line,
2015 ));
2016 }
2017 Token::Ident(ref s) => {
2018 pattern.push_str(s);
2019 self.advance();
2020 }
2021 Token::Integer(n) => {
2022 pattern.push_str(&n.to_string());
2023 self.advance();
2024 }
2025 Token::ScalarVar(ref v) => {
2026 pattern.push('$');
2027 pattern.push_str(v);
2028 self.advance();
2029 }
2030 Token::Dot => {
2031 pattern.push('.');
2032 self.advance();
2033 }
2034 Token::Star => {
2035 pattern.push('*');
2036 self.advance();
2037 }
2038 Token::Plus => {
2039 pattern.push('+');
2040 self.advance();
2041 }
2042 Token::Question => {
2043 pattern.push('?');
2044 self.advance();
2045 }
2046 Token::LParen => {
2047 pattern.push('(');
2048 self.advance();
2049 }
2050 Token::RParen => {
2051 pattern.push(')');
2052 self.advance();
2053 }
2054 Token::LBracket => {
2055 pattern.push('[');
2056 self.advance();
2057 }
2058 Token::RBracket => {
2059 pattern.push(']');
2060 self.advance();
2061 }
2062 Token::Backslash => {
2063 pattern.push('\\');
2064 self.advance();
2065 }
2066 Token::BitOr => {
2067 pattern.push('|');
2068 self.advance();
2069 }
2070 Token::Power => {
2071 pattern.push_str("**");
2072 self.advance();
2073 }
2074 Token::BitXor => {
2075 pattern.push('^');
2076 self.advance();
2077 }
2078 Token::Minus => {
2079 pattern.push('-');
2080 self.advance();
2081 }
2082 _ => {
2083 return Err(self.syntax_err(
2084 format!("Unexpected token in regex pattern: {:?}", self.peek()),
2085 stage_line,
2086 ));
2087 }
2088 }
2089 }
2090 let mut flags = String::new();
2094 if let Token::Ident(ref s) = self.peek().clone() {
2095 let is_flag_only =
2096 s.chars().all(|c| "gimsxecor".contains(c)) && s.len() <= 6;
2097 let followed_by_brace = matches!(self.peek_at(1), Token::LBrace);
2098 if is_flag_only && !followed_by_brace {
2099 flags.push_str(s);
2100 self.advance();
2101 }
2102 }
2103 result = self.thread_regex_grep_stage(result, pattern, flags, '/', stage_line);
2104 }
2105 tok => {
2106 return Err(self.syntax_err(
2107 format!(
2108 "thread: expected stage (ident, sub {{}}, s///, tr///, or /re/), got {:?}",
2109 tok
2110 ),
2111 stage_line,
2112 ));
2113 }
2114 };
2115 }
2116
2117 if pipe_rhs_wrap {
2118 let body_line = result.line;
2121 return Ok(Expr {
2122 kind: ExprKind::CodeRef {
2123 params: vec![],
2124 body: vec![Statement {
2125 label: None,
2126 kind: StmtKind::Expression(result),
2127 line: body_line,
2128 }],
2129 },
2130 line: _line,
2131 });
2132 }
2133 Ok(result)
2134 }
2135
2136 fn thread_regex_grep_stage(
2138 &self,
2139 list: Expr,
2140 pattern: String,
2141 flags: String,
2142 delim: char,
2143 line: usize,
2144 ) -> Expr {
2145 let topic = Expr {
2146 kind: ExprKind::ScalarVar("_".to_string()),
2147 line,
2148 };
2149 let match_expr = Expr {
2150 kind: ExprKind::Match {
2151 expr: Box::new(topic),
2152 pattern,
2153 flags,
2154 scalar_g: false,
2155 delim,
2156 },
2157 line,
2158 };
2159 let block = vec![Statement {
2160 label: None,
2161 kind: StmtKind::Expression(match_expr),
2162 line,
2163 }];
2164 Expr {
2165 kind: ExprKind::GrepExpr {
2166 block,
2167 list: Box::new(list),
2168 keyword: crate::ast::GrepBuiltinKeyword::Grep,
2169 },
2170 line,
2171 }
2172 }
2173
2174 fn expr_contains_topic_var(e: &Expr) -> bool {
2185 format!("{:?}", e).contains("ScalarVar(\"_\")")
2186 }
2187
2188 fn thread_apply_bare_func(&self, name: &str, arg: Expr, line: usize) -> PerlResult<Expr> {
2190 let kind = match name {
2191 "uc" => ExprKind::Uc(Box::new(arg)),
2193 "lc" => ExprKind::Lc(Box::new(arg)),
2194 "ucfirst" | "ufc" => ExprKind::Ucfirst(Box::new(arg)),
2195 "lcfirst" | "lfc" => ExprKind::Lcfirst(Box::new(arg)),
2196 "fc" => ExprKind::Fc(Box::new(arg)),
2197 "chomp" => ExprKind::Chomp(Box::new(arg)),
2198 "chop" => ExprKind::Chop(Box::new(arg)),
2199 "length" => ExprKind::Length(Box::new(arg)),
2200 "len" | "cnt" => ExprKind::FuncCall {
2201 name: "count".to_string(),
2202 args: vec![arg],
2203 },
2204 "quotemeta" | "qm" => ExprKind::FuncCall {
2205 name: "quotemeta".to_string(),
2206 args: vec![arg],
2207 },
2208 "abs" => ExprKind::Abs(Box::new(arg)),
2210 "int" => ExprKind::Int(Box::new(arg)),
2211 "sqrt" | "sq" => ExprKind::Sqrt(Box::new(arg)),
2212 "sin" => ExprKind::Sin(Box::new(arg)),
2213 "cos" => ExprKind::Cos(Box::new(arg)),
2214 "exp" => ExprKind::Exp(Box::new(arg)),
2215 "log" => ExprKind::Log(Box::new(arg)),
2216 "hex" => ExprKind::Hex(Box::new(arg)),
2217 "oct" => ExprKind::Oct(Box::new(arg)),
2218 "chr" => ExprKind::Chr(Box::new(arg)),
2219 "ord" => ExprKind::Ord(Box::new(arg)),
2220 "defined" | "def" => ExprKind::Defined(Box::new(arg)),
2222 "ref" => ExprKind::Ref(Box::new(arg)),
2223 "scalar" => ExprKind::ScalarContext(Box::new(arg)),
2224 "keys" => ExprKind::Keys(Box::new(arg)),
2226 "values" => ExprKind::Values(Box::new(arg)),
2227 "each" => ExprKind::Each(Box::new(arg)),
2228 "pop" => ExprKind::Pop(Box::new(arg)),
2229 "shift" => ExprKind::Shift(Box::new(arg)),
2230 "reverse" | "reversed" | "rv" => ExprKind::ReverseExpr(Box::new(arg)),
2231 "rev" => ExprKind::ScalarReverse(Box::new(arg)),
2232 "sort" | "so" => ExprKind::SortExpr {
2233 cmp: None,
2234 list: Box::new(arg),
2235 },
2236 "uniq" | "distinct" | "uq" => ExprKind::FuncCall {
2237 name: "uniq".to_string(),
2238 args: vec![arg],
2239 },
2240 "trim" | "tm" => ExprKind::FuncCall {
2241 name: "trim".to_string(),
2242 args: vec![arg],
2243 },
2244 "flatten" | "fl" => ExprKind::FuncCall {
2245 name: "flatten".to_string(),
2246 args: vec![arg],
2247 },
2248 "compact" | "cpt" => ExprKind::FuncCall {
2249 name: "compact".to_string(),
2250 args: vec![arg],
2251 },
2252 "shuffle" | "shuf" => ExprKind::FuncCall {
2253 name: "shuffle".to_string(),
2254 args: vec![arg],
2255 },
2256 "frequencies" | "freq" | "frq" => ExprKind::FuncCall {
2257 name: "frequencies".to_string(),
2258 args: vec![arg],
2259 },
2260 "dedup" | "dup" => ExprKind::FuncCall {
2261 name: "dedup".to_string(),
2262 args: vec![arg],
2263 },
2264 "enumerate" | "en" => ExprKind::FuncCall {
2265 name: "enumerate".to_string(),
2266 args: vec![arg],
2267 },
2268 "lines" | "ln" => ExprKind::FuncCall {
2269 name: "lines".to_string(),
2270 args: vec![arg],
2271 },
2272 "words" | "wd" => ExprKind::FuncCall {
2273 name: "words".to_string(),
2274 args: vec![arg],
2275 },
2276 "chars" | "ch" => ExprKind::FuncCall {
2277 name: "chars".to_string(),
2278 args: vec![arg],
2279 },
2280 "digits" | "dg" => ExprKind::FuncCall {
2281 name: "digits".to_string(),
2282 args: vec![arg],
2283 },
2284 "letters" | "lts" => ExprKind::FuncCall {
2285 name: "letters".to_string(),
2286 args: vec![arg],
2287 },
2288 "letters_uc" => ExprKind::FuncCall {
2289 name: "letters_uc".to_string(),
2290 args: vec![arg],
2291 },
2292 "letters_lc" => ExprKind::FuncCall {
2293 name: "letters_lc".to_string(),
2294 args: vec![arg],
2295 },
2296 "punctuation" | "punct" => ExprKind::FuncCall {
2297 name: "punctuation".to_string(),
2298 args: vec![arg],
2299 },
2300 "sentences" | "sents" => ExprKind::FuncCall {
2301 name: "sentences".to_string(),
2302 args: vec![arg],
2303 },
2304 "paragraphs" | "paras" => ExprKind::FuncCall {
2305 name: "paragraphs".to_string(),
2306 args: vec![arg],
2307 },
2308 "sections" | "sects" => ExprKind::FuncCall {
2309 name: "sections".to_string(),
2310 args: vec![arg],
2311 },
2312 "numbers" | "nums" => ExprKind::FuncCall {
2313 name: "numbers".to_string(),
2314 args: vec![arg],
2315 },
2316 "graphemes" | "grs" => ExprKind::FuncCall {
2317 name: "graphemes".to_string(),
2318 args: vec![arg],
2319 },
2320 "columns" | "cols" => ExprKind::FuncCall {
2321 name: "columns".to_string(),
2322 args: vec![arg],
2323 },
2324 "slurp" | "sl" => ExprKind::Slurp(Box::new(arg)),
2326 "chdir" => ExprKind::Chdir(Box::new(arg)),
2327 "stat" => ExprKind::Stat(Box::new(arg)),
2328 "lstat" => ExprKind::Lstat(Box::new(arg)),
2329 "readlink" => ExprKind::Readlink(Box::new(arg)),
2330 "readdir" => ExprKind::Readdir(Box::new(arg)),
2331 "close" => ExprKind::Close(Box::new(arg)),
2332 "basename" | "bn" => ExprKind::FuncCall {
2333 name: "basename".to_string(),
2334 args: vec![arg],
2335 },
2336 "dirname" | "dn" => ExprKind::FuncCall {
2337 name: "dirname".to_string(),
2338 args: vec![arg],
2339 },
2340 "realpath" | "rp" => ExprKind::FuncCall {
2341 name: "realpath".to_string(),
2342 args: vec![arg],
2343 },
2344 "which" | "wh" => ExprKind::FuncCall {
2345 name: "which".to_string(),
2346 args: vec![arg],
2347 },
2348 "eval" => ExprKind::Eval(Box::new(arg)),
2350 "require" => ExprKind::Require(Box::new(arg)),
2351 "study" => ExprKind::Study(Box::new(arg)),
2352 "snake_case" | "sc" => ExprKind::FuncCall {
2354 name: "snake_case".to_string(),
2355 args: vec![arg],
2356 },
2357 "camel_case" | "cc" => ExprKind::FuncCall {
2358 name: "camel_case".to_string(),
2359 args: vec![arg],
2360 },
2361 "kebab_case" | "kc" => ExprKind::FuncCall {
2362 name: "kebab_case".to_string(),
2363 args: vec![arg],
2364 },
2365 "to_json" | "tj" => ExprKind::FuncCall {
2367 name: "to_json".to_string(),
2368 args: vec![arg],
2369 },
2370 "to_yaml" | "ty" => ExprKind::FuncCall {
2371 name: "to_yaml".to_string(),
2372 args: vec![arg],
2373 },
2374 "to_toml" | "tt" => ExprKind::FuncCall {
2375 name: "to_toml".to_string(),
2376 args: vec![arg],
2377 },
2378 "to_csv" | "tc" => ExprKind::FuncCall {
2379 name: "to_csv".to_string(),
2380 args: vec![arg],
2381 },
2382 "to_xml" | "tx" => ExprKind::FuncCall {
2383 name: "to_xml".to_string(),
2384 args: vec![arg],
2385 },
2386 "to_html" | "th" => ExprKind::FuncCall {
2387 name: "to_html".to_string(),
2388 args: vec![arg],
2389 },
2390 "to_markdown" | "to_md" | "tmd" => ExprKind::FuncCall {
2391 name: "to_markdown".to_string(),
2392 args: vec![arg],
2393 },
2394 "xopen" | "xo" => ExprKind::FuncCall {
2395 name: "xopen".to_string(),
2396 args: vec![arg],
2397 },
2398 "clip" | "clipboard" | "pbcopy" => ExprKind::FuncCall {
2399 name: "clip".to_string(),
2400 args: vec![arg],
2401 },
2402 "to_table" | "table" | "tbl" => ExprKind::FuncCall {
2403 name: "to_table".to_string(),
2404 args: vec![arg],
2405 },
2406 "sparkline" | "spark" => ExprKind::FuncCall {
2407 name: "sparkline".to_string(),
2408 args: vec![arg],
2409 },
2410 "bar_chart" | "bars" => ExprKind::FuncCall {
2411 name: "bar_chart".to_string(),
2412 args: vec![arg],
2413 },
2414 "flame" | "flamechart" => ExprKind::FuncCall {
2415 name: "flame".to_string(),
2416 args: vec![arg],
2417 },
2418 "ddump" | "dd" => ExprKind::FuncCall {
2419 name: "ddump".to_string(),
2420 args: vec![arg],
2421 },
2422 "stringify" | "str" => ExprKind::FuncCall {
2423 name: "stringify".to_string(),
2424 args: vec![arg],
2425 },
2426 "json_decode" | "jd" => ExprKind::FuncCall {
2427 name: "json_decode".to_string(),
2428 args: vec![arg],
2429 },
2430 "yaml_decode" | "yd" => ExprKind::FuncCall {
2431 name: "yaml_decode".to_string(),
2432 args: vec![arg],
2433 },
2434 "toml_decode" | "td" => ExprKind::FuncCall {
2435 name: "toml_decode".to_string(),
2436 args: vec![arg],
2437 },
2438 "xml_decode" | "xd" => ExprKind::FuncCall {
2439 name: "xml_decode".to_string(),
2440 args: vec![arg],
2441 },
2442 "json_encode" | "je" => ExprKind::FuncCall {
2443 name: "json_encode".to_string(),
2444 args: vec![arg],
2445 },
2446 "yaml_encode" | "ye" => ExprKind::FuncCall {
2447 name: "yaml_encode".to_string(),
2448 args: vec![arg],
2449 },
2450 "toml_encode" | "te" => ExprKind::FuncCall {
2451 name: "toml_encode".to_string(),
2452 args: vec![arg],
2453 },
2454 "xml_encode" | "xe" => ExprKind::FuncCall {
2455 name: "xml_encode".to_string(),
2456 args: vec![arg],
2457 },
2458 "base64_encode" | "b64e" => ExprKind::FuncCall {
2460 name: "base64_encode".to_string(),
2461 args: vec![arg],
2462 },
2463 "base64_decode" | "b64d" => ExprKind::FuncCall {
2464 name: "base64_decode".to_string(),
2465 args: vec![arg],
2466 },
2467 "hex_encode" | "hxe" => ExprKind::FuncCall {
2468 name: "hex_encode".to_string(),
2469 args: vec![arg],
2470 },
2471 "hex_decode" | "hxd" => ExprKind::FuncCall {
2472 name: "hex_decode".to_string(),
2473 args: vec![arg],
2474 },
2475 "url_encode" | "uri_escape" | "ue" => ExprKind::FuncCall {
2476 name: "url_encode".to_string(),
2477 args: vec![arg],
2478 },
2479 "url_decode" | "uri_unescape" | "ud" => ExprKind::FuncCall {
2480 name: "url_decode".to_string(),
2481 args: vec![arg],
2482 },
2483 "gzip" | "gz" => ExprKind::FuncCall {
2484 name: "gzip".to_string(),
2485 args: vec![arg],
2486 },
2487 "gunzip" | "ugz" => ExprKind::FuncCall {
2488 name: "gunzip".to_string(),
2489 args: vec![arg],
2490 },
2491 "zstd" | "zst" => ExprKind::FuncCall {
2492 name: "zstd".to_string(),
2493 args: vec![arg],
2494 },
2495 "zstd_decode" | "uzst" => ExprKind::FuncCall {
2496 name: "zstd_decode".to_string(),
2497 args: vec![arg],
2498 },
2499 "sha256" | "s256" => ExprKind::FuncCall {
2501 name: "sha256".to_string(),
2502 args: vec![arg],
2503 },
2504 "sha1" | "s1" => ExprKind::FuncCall {
2505 name: "sha1".to_string(),
2506 args: vec![arg],
2507 },
2508 "md5" | "m5" => ExprKind::FuncCall {
2509 name: "md5".to_string(),
2510 args: vec![arg],
2511 },
2512 "uuid" | "uid" => ExprKind::FuncCall {
2513 name: "uuid".to_string(),
2514 args: vec![arg],
2515 },
2516 "datetime_utc" | "utc" => ExprKind::FuncCall {
2518 name: "datetime_utc".to_string(),
2519 args: vec![arg],
2520 },
2521 "p" | "say" => ExprKind::Say {
2523 handle: None,
2524 args: vec![arg],
2525 },
2526 "print" | "pr" => ExprKind::Print {
2527 handle: None,
2528 args: vec![arg],
2529 },
2530 "e" | "fore" | "ep" => ExprKind::ForEachExpr {
2533 block: vec![Statement {
2534 label: None,
2535 kind: StmtKind::Expression(Expr {
2536 kind: ExprKind::Say {
2537 handle: None,
2538 args: vec![Expr {
2539 kind: ExprKind::ScalarVar("_".into()),
2540 line,
2541 }],
2542 },
2543 line,
2544 }),
2545 line,
2546 }],
2547 list: Box::new(arg),
2548 },
2549 _ => ExprKind::FuncCall {
2551 name: name.to_string(),
2552 args: vec![arg],
2553 },
2554 };
2555 Ok(Expr { kind, line })
2556 }
2557
2558 fn parse_thread_stage_with_block(&mut self, name: &str, line: usize) -> PerlResult<Expr> {
2561 let block = self.parse_block()?;
2562 let placeholder = self.pipe_placeholder_list(line);
2564
2565 match name {
2566 "map" | "flat_map" | "maps" | "flat_maps" => {
2567 let flatten_array_refs = matches!(name, "flat_map" | "flat_maps");
2568 let stream = matches!(name, "maps" | "flat_maps");
2569 Ok(Expr {
2570 kind: ExprKind::MapExpr {
2571 block,
2572 list: Box::new(placeholder),
2573 flatten_array_refs,
2574 stream,
2575 },
2576 line,
2577 })
2578 }
2579 "grep" | "greps" | "filter" | "f" | "find_all" | "gr" => {
2580 let keyword = match name {
2581 "grep" | "gr" => crate::ast::GrepBuiltinKeyword::Grep,
2582 "greps" => crate::ast::GrepBuiltinKeyword::Greps,
2583 "filter" | "f" => crate::ast::GrepBuiltinKeyword::Filter,
2584 "find_all" => crate::ast::GrepBuiltinKeyword::FindAll,
2585 _ => unreachable!(),
2586 };
2587 Ok(Expr {
2588 kind: ExprKind::GrepExpr {
2589 block,
2590 list: Box::new(placeholder),
2591 keyword,
2592 },
2593 line,
2594 })
2595 }
2596 "sort" | "so" => Ok(Expr {
2597 kind: ExprKind::SortExpr {
2598 cmp: Some(SortComparator::Block(block)),
2599 list: Box::new(placeholder),
2600 },
2601 line,
2602 }),
2603 "reduce" | "rd" => Ok(Expr {
2604 kind: ExprKind::ReduceExpr {
2605 block,
2606 list: Box::new(placeholder),
2607 },
2608 line,
2609 }),
2610 "fore" | "e" | "ep" => Ok(Expr {
2611 kind: ExprKind::ForEachExpr {
2612 block,
2613 list: Box::new(placeholder),
2614 },
2615 line,
2616 }),
2617 "pmap" | "pflat_map" | "pmaps" | "pflat_maps" => Ok(Expr {
2618 kind: ExprKind::PMapExpr {
2619 block,
2620 list: Box::new(placeholder),
2621 progress: None,
2622 flat_outputs: name == "pflat_map" || name == "pflat_maps",
2623 on_cluster: None,
2624 stream: name == "pmaps" || name == "pflat_maps",
2625 },
2626 line,
2627 }),
2628 "pgrep" | "pgreps" => Ok(Expr {
2629 kind: ExprKind::PGrepExpr {
2630 block,
2631 list: Box::new(placeholder),
2632 progress: None,
2633 stream: name == "pgreps",
2634 },
2635 line,
2636 }),
2637 "pfor" => Ok(Expr {
2638 kind: ExprKind::PForExpr {
2639 block,
2640 list: Box::new(placeholder),
2641 progress: None,
2642 },
2643 line,
2644 }),
2645 "preduce" => Ok(Expr {
2646 kind: ExprKind::PReduceExpr {
2647 block,
2648 list: Box::new(placeholder),
2649 progress: None,
2650 },
2651 line,
2652 }),
2653 "pcache" => Ok(Expr {
2654 kind: ExprKind::PcacheExpr {
2655 block,
2656 list: Box::new(placeholder),
2657 progress: None,
2658 },
2659 line,
2660 }),
2661 "psort" => Ok(Expr {
2662 kind: ExprKind::PSortExpr {
2663 cmp: Some(block),
2664 list: Box::new(placeholder),
2665 progress: None,
2666 },
2667 line,
2668 }),
2669 _ => {
2670 let code_ref = Expr {
2672 kind: ExprKind::CodeRef {
2673 params: vec![],
2674 body: block,
2675 },
2676 line,
2677 };
2678 Ok(Expr {
2679 kind: ExprKind::FuncCall {
2680 name: name.to_string(),
2681 args: vec![code_ref],
2682 },
2683 line,
2684 })
2685 }
2686 }
2687 }
2688
2689 fn parse_tie_stmt(&mut self) -> PerlResult<Statement> {
2691 let line = self.peek_line();
2692 self.advance(); let target = match self.peek().clone() {
2694 Token::HashVar(h) => {
2695 self.advance();
2696 TieTarget::Hash(h)
2697 }
2698 Token::ArrayVar(a) => {
2699 self.advance();
2700 TieTarget::Array(a)
2701 }
2702 Token::ScalarVar(s) => {
2703 self.advance();
2704 TieTarget::Scalar(s)
2705 }
2706 tok => {
2707 return Err(self.syntax_err(
2708 format!("tie expects $scalar, @array, or %hash, got {:?}", tok),
2709 self.peek_line(),
2710 ));
2711 }
2712 };
2713 self.expect(&Token::Comma)?;
2714 let class = self.parse_assign_expr()?;
2715 let mut args = Vec::new();
2716 while self.eat(&Token::Comma) {
2717 if matches!(self.peek(), Token::Semicolon | Token::RBrace | Token::Eof) {
2718 break;
2719 }
2720 args.push(self.parse_assign_expr()?);
2721 }
2722 self.eat(&Token::Semicolon);
2723 Ok(Statement {
2724 label: None,
2725 kind: StmtKind::Tie {
2726 target,
2727 class,
2728 args,
2729 },
2730 line,
2731 })
2732 }
2733
2734 fn parse_given(&mut self) -> PerlResult<Statement> {
2736 let line = self.peek_line();
2737 self.advance();
2738 self.expect(&Token::LParen)?;
2739 let topic = self.parse_expression()?;
2740 self.expect(&Token::RParen)?;
2741 let body = self.parse_block()?;
2742 self.eat(&Token::Semicolon);
2743 Ok(Statement {
2744 label: None,
2745 kind: StmtKind::Given { topic, body },
2746 line,
2747 })
2748 }
2749
2750 fn parse_when_stmt(&mut self) -> PerlResult<Statement> {
2752 let line = self.peek_line();
2753 self.advance();
2754 self.expect(&Token::LParen)?;
2755 let cond = self.parse_expression()?;
2756 self.expect(&Token::RParen)?;
2757 let body = self.parse_block()?;
2758 self.eat(&Token::Semicolon);
2759 Ok(Statement {
2760 label: None,
2761 kind: StmtKind::When { cond, body },
2762 line,
2763 })
2764 }
2765
2766 fn parse_default_stmt(&mut self) -> PerlResult<Statement> {
2768 let line = self.peek_line();
2769 self.advance();
2770 let body = self.parse_block()?;
2771 self.eat(&Token::Semicolon);
2772 Ok(Statement {
2773 label: None,
2774 kind: StmtKind::DefaultCase { body },
2775 line,
2776 })
2777 }
2778
2779 fn parse_algebraic_match_expr(&mut self, line: usize) -> PerlResult<Expr> {
2781 self.expect(&Token::LParen)?;
2782 let subject = self.parse_expression()?;
2783 self.expect(&Token::RParen)?;
2784 self.expect(&Token::LBrace)?;
2785 let mut arms = Vec::new();
2786 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
2787 if self.eat(&Token::Semicolon) {
2788 continue;
2789 }
2790 let pattern = self.parse_match_pattern()?;
2791 let guard = if matches!(self.peek(), Token::Ident(ref s) if s == "if") {
2792 self.advance();
2793 Some(Box::new(self.parse_assign_expr()?))
2796 } else {
2797 None
2798 };
2799 self.expect(&Token::FatArrow)?;
2800 let body = self.parse_assign_expr()?;
2802 arms.push(MatchArm {
2803 pattern,
2804 guard,
2805 body,
2806 });
2807 self.eat(&Token::Comma);
2808 }
2809 self.expect(&Token::RBrace)?;
2810 Ok(Expr {
2811 kind: ExprKind::AlgebraicMatch {
2812 subject: Box::new(subject),
2813 arms,
2814 },
2815 line,
2816 })
2817 }
2818
2819 fn parse_match_pattern(&mut self) -> PerlResult<MatchPattern> {
2820 match self.peek().clone() {
2821 Token::Regex(pattern, flags, _delim) => {
2822 self.advance();
2823 Ok(MatchPattern::Regex { pattern, flags })
2824 }
2825 Token::Ident(ref s) if s == "_" => {
2826 self.advance();
2827 Ok(MatchPattern::Any)
2828 }
2829 Token::Ident(ref s) if s == "Some" => {
2830 self.advance();
2831 self.expect(&Token::LParen)?;
2832 let name = self.parse_scalar_var_name()?;
2833 self.expect(&Token::RParen)?;
2834 Ok(MatchPattern::OptionSome(name))
2835 }
2836 Token::LBracket => self.parse_match_array_pattern(),
2837 Token::LBrace => self.parse_match_hash_pattern(),
2838 Token::LParen => {
2839 self.advance();
2840 let e = self.parse_expression()?;
2841 self.expect(&Token::RParen)?;
2842 Ok(MatchPattern::Value(Box::new(e)))
2843 }
2844 _ => {
2845 let e = self.parse_assign_expr()?;
2846 Ok(MatchPattern::Value(Box::new(e)))
2847 }
2848 }
2849 }
2850
2851 fn parse_match_array_elems_until_rbracket(&mut self) -> PerlResult<Vec<MatchArrayElem>> {
2853 let mut elems = Vec::new();
2854 if self.eat(&Token::RBracket) {
2855 return Ok(vec![]);
2856 }
2857 loop {
2858 if matches!(self.peek(), Token::Star) {
2859 self.advance();
2860 elems.push(MatchArrayElem::Rest);
2861 self.eat(&Token::Comma);
2862 if !matches!(self.peek(), Token::RBracket) {
2863 return Err(self.syntax_err(
2864 "`*` must be the last element in an array match pattern",
2865 self.peek_line(),
2866 ));
2867 }
2868 self.expect(&Token::RBracket)?;
2869 return Ok(elems);
2870 }
2871 if let Token::ArrayVar(name) = self.peek().clone() {
2872 self.advance();
2873 elems.push(MatchArrayElem::RestBind(name));
2874 self.eat(&Token::Comma);
2875 if !matches!(self.peek(), Token::RBracket) {
2876 return Err(self.syntax_err(
2877 "`@name` rest bind must be the last element in an array match pattern",
2878 self.peek_line(),
2879 ));
2880 }
2881 self.expect(&Token::RBracket)?;
2882 return Ok(elems);
2883 }
2884 if let Token::ScalarVar(name) = self.peek().clone() {
2885 self.advance();
2886 elems.push(MatchArrayElem::CaptureScalar(name));
2887 if self.eat(&Token::Comma) {
2888 if matches!(self.peek(), Token::RBracket) {
2889 break;
2890 }
2891 continue;
2892 }
2893 break;
2894 }
2895 let e = self.parse_assign_expr()?;
2896 elems.push(MatchArrayElem::Expr(e));
2897 if self.eat(&Token::Comma) {
2898 if matches!(self.peek(), Token::RBracket) {
2899 break;
2900 }
2901 continue;
2902 }
2903 break;
2904 }
2905 self.expect(&Token::RBracket)?;
2906 Ok(elems)
2907 }
2908
2909 fn parse_match_array_pattern(&mut self) -> PerlResult<MatchPattern> {
2910 self.expect(&Token::LBracket)?;
2911 let elems = self.parse_match_array_elems_until_rbracket()?;
2912 Ok(MatchPattern::Array(elems))
2913 }
2914
2915 fn parse_match_hash_pattern(&mut self) -> PerlResult<MatchPattern> {
2916 self.expect(&Token::LBrace)?;
2917 let mut pairs = Vec::new();
2918 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
2919 if self.eat(&Token::Semicolon) {
2920 continue;
2921 }
2922 let key = self.parse_assign_expr()?;
2923 self.expect(&Token::FatArrow)?;
2924 match self.advance().0 {
2925 Token::Ident(ref s) if s == "_" => {
2926 pairs.push(MatchHashPair::KeyOnly { key });
2927 }
2928 Token::ScalarVar(name) => {
2929 pairs.push(MatchHashPair::Capture { key, name });
2930 }
2931 tok => {
2932 return Err(self.syntax_err(
2933 format!(
2934 "hash match pattern must bind with `=> $name` or `=> _`, got {:?}",
2935 tok
2936 ),
2937 self.peek_line(),
2938 ));
2939 }
2940 }
2941 self.eat(&Token::Comma);
2942 }
2943 self.expect(&Token::RBrace)?;
2944 Ok(MatchPattern::Hash(pairs))
2945 }
2946
2947 fn parse_eval_timeout(&mut self) -> PerlResult<Statement> {
2949 let line = self.peek_line();
2950 self.advance();
2951 let timeout = self.parse_postfix()?;
2952 let body = self.parse_block_or_bareword_block_no_args()?;
2953 self.eat(&Token::Semicolon);
2954 Ok(Statement {
2955 label: None,
2956 kind: StmtKind::EvalTimeout { timeout, body },
2957 line,
2958 })
2959 }
2960
2961 fn mark_match_scalar_g_for_boolean_condition(cond: &mut Expr) {
2962 match &mut cond.kind {
2963 ExprKind::Match {
2964 flags, scalar_g, ..
2965 } if flags.contains('g') => {
2966 *scalar_g = true;
2967 }
2968 ExprKind::UnaryOp {
2969 op: UnaryOp::LogNot,
2970 expr,
2971 } => {
2972 if let ExprKind::Match {
2973 flags, scalar_g, ..
2974 } = &mut expr.kind
2975 {
2976 if flags.contains('g') {
2977 *scalar_g = true;
2978 }
2979 }
2980 }
2981 _ => {}
2982 }
2983 }
2984
2985 fn parse_if(&mut self) -> PerlResult<Statement> {
2986 let line = self.peek_line();
2987 self.advance(); if matches!(self.peek(), Token::Ident(ref s) if s == "let") {
2989 if crate::compat_mode() {
2990 return Err(self.syntax_err(
2991 "`if let` is a stryke extension (disabled by --compat)",
2992 line,
2993 ));
2994 }
2995 return self.parse_if_let(line);
2996 }
2997 self.expect(&Token::LParen)?;
2998 let mut cond = self.parse_expression()?;
2999 Self::mark_match_scalar_g_for_boolean_condition(&mut cond);
3000 self.expect(&Token::RParen)?;
3001 let body = self.parse_block()?;
3002
3003 let mut elsifs = Vec::new();
3004 let mut else_block = None;
3005
3006 loop {
3007 if let Token::Ident(ref kw) = self.peek().clone() {
3008 if kw == "elsif" {
3009 self.advance();
3010 self.expect(&Token::LParen)?;
3011 let mut c = self.parse_expression()?;
3012 Self::mark_match_scalar_g_for_boolean_condition(&mut c);
3013 self.expect(&Token::RParen)?;
3014 let b = self.parse_block()?;
3015 elsifs.push((c, b));
3016 continue;
3017 }
3018 if kw == "else" {
3019 self.advance();
3020 else_block = Some(self.parse_block()?);
3021 }
3022 }
3023 break;
3024 }
3025
3026 Ok(Statement {
3027 label: None,
3028 kind: StmtKind::If {
3029 condition: cond,
3030 body,
3031 elsifs,
3032 else_block,
3033 },
3034 line,
3035 })
3036 }
3037
3038 fn parse_if_let(&mut self, line: usize) -> PerlResult<Statement> {
3040 self.advance(); let pattern = self.parse_match_pattern()?;
3042 self.expect(&Token::Assign)?;
3043 self.suppress_scalar_hash_brace = self.suppress_scalar_hash_brace.saturating_add(1);
3045 let rhs = self.parse_assign_expr();
3046 self.suppress_scalar_hash_brace = self.suppress_scalar_hash_brace.saturating_sub(1);
3047 let rhs = rhs?;
3048 let then_block = self.parse_block()?;
3049 let else_block_opt = match self.peek().clone() {
3050 Token::Ident(ref kw) if kw == "else" => {
3051 self.advance();
3052 Some(self.parse_block()?)
3053 }
3054 Token::Ident(ref kw) if kw == "elsif" => {
3055 return Err(self.syntax_err(
3056 "`if let` does not support `elsif`; use `else { }` or a full `match`",
3057 self.peek_line(),
3058 ));
3059 }
3060 _ => None,
3061 };
3062 let then_expr = Self::expr_do_anon_block(then_block, line);
3063 let else_expr = if let Some(eb) = else_block_opt {
3064 Self::expr_do_anon_block(eb, line)
3065 } else {
3066 Expr {
3067 kind: ExprKind::Undef,
3068 line,
3069 }
3070 };
3071 let arms = vec![
3072 MatchArm {
3073 pattern,
3074 guard: None,
3075 body: then_expr,
3076 },
3077 MatchArm {
3078 pattern: MatchPattern::Any,
3079 guard: None,
3080 body: else_expr,
3081 },
3082 ];
3083 Ok(Statement {
3084 label: None,
3085 kind: StmtKind::Expression(Expr {
3086 kind: ExprKind::AlgebraicMatch {
3087 subject: Box::new(rhs),
3088 arms,
3089 },
3090 line,
3091 }),
3092 line,
3093 })
3094 }
3095
3096 fn expr_do_anon_block(block: Block, outer_line: usize) -> Expr {
3097 let inner_line = block.first().map(|s| s.line).unwrap_or(outer_line);
3098 Expr {
3099 kind: ExprKind::Do(Box::new(Expr {
3100 kind: ExprKind::CodeRef {
3101 params: vec![],
3102 body: block,
3103 },
3104 line: inner_line,
3105 })),
3106 line: outer_line,
3107 }
3108 }
3109
3110 fn parse_unless(&mut self) -> PerlResult<Statement> {
3111 let line = self.peek_line();
3112 self.advance(); self.expect(&Token::LParen)?;
3114 let mut cond = self.parse_expression()?;
3115 Self::mark_match_scalar_g_for_boolean_condition(&mut cond);
3116 self.expect(&Token::RParen)?;
3117 let body = self.parse_block()?;
3118 let else_block = if let Token::Ident(ref kw) = self.peek().clone() {
3119 if kw == "else" {
3120 self.advance();
3121 Some(self.parse_block()?)
3122 } else {
3123 None
3124 }
3125 } else {
3126 None
3127 };
3128 Ok(Statement {
3129 label: None,
3130 kind: StmtKind::Unless {
3131 condition: cond,
3132 body,
3133 else_block,
3134 },
3135 line,
3136 })
3137 }
3138
3139 fn parse_while(&mut self) -> PerlResult<Statement> {
3140 let line = self.peek_line();
3141 self.advance(); if matches!(self.peek(), Token::Ident(ref s) if s == "let") {
3143 if crate::compat_mode() {
3144 return Err(self.syntax_err(
3145 "`while let` is a stryke extension (disabled by --compat)",
3146 line,
3147 ));
3148 }
3149 return self.parse_while_let(line);
3150 }
3151 self.expect(&Token::LParen)?;
3152 let mut cond = self.parse_expression()?;
3153 Self::mark_match_scalar_g_for_boolean_condition(&mut cond);
3154 self.expect(&Token::RParen)?;
3155 let body = self.parse_block()?;
3156 let continue_block = self.parse_optional_continue_block()?;
3157 Ok(Statement {
3158 label: None,
3159 kind: StmtKind::While {
3160 condition: cond,
3161 body,
3162 label: None,
3163 continue_block,
3164 },
3165 line,
3166 })
3167 }
3168
3169 fn parse_while_let(&mut self, line: usize) -> PerlResult<Statement> {
3172 self.advance(); let pattern = self.parse_match_pattern()?;
3174 self.expect(&Token::Assign)?;
3175 self.suppress_scalar_hash_brace = self.suppress_scalar_hash_brace.saturating_add(1);
3176 let rhs = self.parse_assign_expr();
3177 self.suppress_scalar_hash_brace = self.suppress_scalar_hash_brace.saturating_sub(1);
3178 let rhs = rhs?;
3179 let mut user_body = self.parse_block()?;
3180 let continue_block = self.parse_optional_continue_block()?;
3181 user_body.push(Statement::new(
3182 StmtKind::Expression(Expr {
3183 kind: ExprKind::Integer(1),
3184 line,
3185 }),
3186 line,
3187 ));
3188 let tmp = format!("__while_let_{}", self.alloc_desugar_tmp());
3189 let match_expr = Expr {
3190 kind: ExprKind::AlgebraicMatch {
3191 subject: Box::new(rhs),
3192 arms: vec![
3193 MatchArm {
3194 pattern,
3195 guard: None,
3196 body: Self::expr_do_anon_block(user_body, line),
3197 },
3198 MatchArm {
3199 pattern: MatchPattern::Any,
3200 guard: None,
3201 body: Expr {
3202 kind: ExprKind::Integer(0),
3203 line,
3204 },
3205 },
3206 ],
3207 },
3208 line,
3209 };
3210 let my_stmt = Statement::new(
3211 StmtKind::My(vec![VarDecl {
3212 sigil: Sigil::Scalar,
3213 name: tmp.clone(),
3214 initializer: Some(match_expr),
3215 frozen: false,
3216 type_annotation: None,
3217 }]),
3218 line,
3219 );
3220 let unless_last = Statement::new(
3221 StmtKind::Unless {
3222 condition: Expr {
3223 kind: ExprKind::ScalarVar(tmp),
3224 line,
3225 },
3226 body: vec![Statement::new(StmtKind::Last(None), line)],
3227 else_block: None,
3228 },
3229 line,
3230 );
3231 Ok(Statement::new(
3232 StmtKind::While {
3233 condition: Expr {
3234 kind: ExprKind::Integer(1),
3235 line,
3236 },
3237 body: vec![my_stmt, unless_last],
3238 label: None,
3239 continue_block,
3240 },
3241 line,
3242 ))
3243 }
3244
3245 fn parse_until(&mut self) -> PerlResult<Statement> {
3246 let line = self.peek_line();
3247 self.advance(); self.expect(&Token::LParen)?;
3249 let mut cond = self.parse_expression()?;
3250 Self::mark_match_scalar_g_for_boolean_condition(&mut cond);
3251 self.expect(&Token::RParen)?;
3252 let body = self.parse_block()?;
3253 let continue_block = self.parse_optional_continue_block()?;
3254 Ok(Statement {
3255 label: None,
3256 kind: StmtKind::Until {
3257 condition: cond,
3258 body,
3259 label: None,
3260 continue_block,
3261 },
3262 line,
3263 })
3264 }
3265
3266 fn parse_optional_continue_block(&mut self) -> PerlResult<Option<Block>> {
3268 if let Token::Ident(ref kw) = self.peek().clone() {
3269 if kw == "continue" {
3270 self.advance();
3271 return Ok(Some(self.parse_block()?));
3272 }
3273 }
3274 Ok(None)
3275 }
3276
3277 fn parse_for_or_foreach(&mut self) -> PerlResult<Statement> {
3278 let line = self.peek_line();
3279 self.advance(); match self.peek() {
3285 Token::LParen => {
3286 let saved = self.pos;
3291 self.advance(); let mut depth = 1;
3294 let mut has_semi = false;
3295 let mut scan = self.pos;
3296 while scan < self.tokens.len() {
3297 match &self.tokens[scan].0 {
3298 Token::LParen => depth += 1,
3299 Token::RParen => {
3300 depth -= 1;
3301 if depth == 0 {
3302 break;
3303 }
3304 }
3305 Token::Semicolon if depth == 1 => {
3306 has_semi = true;
3307 break;
3308 }
3309 _ => {}
3310 }
3311 scan += 1;
3312 }
3313 self.pos = saved;
3314
3315 if has_semi {
3316 self.parse_c_style_for(line)
3317 } else {
3318 self.expect(&Token::LParen)?;
3320 let list = self.parse_expression()?;
3321 self.expect(&Token::RParen)?;
3322 let body = self.parse_block()?;
3323 let continue_block = self.parse_optional_continue_block()?;
3324 Ok(Statement {
3325 label: None,
3326 kind: StmtKind::Foreach {
3327 var: "_".to_string(),
3328 list,
3329 body,
3330 label: None,
3331 continue_block,
3332 },
3333 line,
3334 })
3335 }
3336 }
3337 Token::Ident(ref kw) if kw == "my" => {
3338 self.advance(); let var = self.parse_scalar_var_name()?;
3340 self.expect(&Token::LParen)?;
3341 let list = self.parse_expression()?;
3342 self.expect(&Token::RParen)?;
3343 let body = self.parse_block()?;
3344 let continue_block = self.parse_optional_continue_block()?;
3345 Ok(Statement {
3346 label: None,
3347 kind: StmtKind::Foreach {
3348 var,
3349 list,
3350 body,
3351 label: None,
3352 continue_block,
3353 },
3354 line,
3355 })
3356 }
3357 Token::ScalarVar(_) => {
3358 let var = self.parse_scalar_var_name()?;
3359 self.expect(&Token::LParen)?;
3360 let list = self.parse_expression()?;
3361 self.expect(&Token::RParen)?;
3362 let body = self.parse_block()?;
3363 let continue_block = self.parse_optional_continue_block()?;
3364 Ok(Statement {
3365 label: None,
3366 kind: StmtKind::Foreach {
3367 var,
3368 list,
3369 body,
3370 label: None,
3371 continue_block,
3372 },
3373 line,
3374 })
3375 }
3376 _ => self.parse_c_style_for(line),
3377 }
3378 }
3379
3380 fn parse_c_style_for(&mut self, line: usize) -> PerlResult<Statement> {
3381 self.expect(&Token::LParen)?;
3382 let init = if self.eat(&Token::Semicolon) {
3383 None
3384 } else {
3385 let s = self.parse_statement()?;
3386 self.eat(&Token::Semicolon);
3387 Some(Box::new(s))
3388 };
3389 let mut condition = if matches!(self.peek(), Token::Semicolon) {
3390 None
3391 } else {
3392 Some(self.parse_expression()?)
3393 };
3394 if let Some(ref mut c) = condition {
3395 Self::mark_match_scalar_g_for_boolean_condition(c);
3396 }
3397 self.expect(&Token::Semicolon)?;
3398 let step = if matches!(self.peek(), Token::RParen) {
3399 None
3400 } else {
3401 Some(self.parse_expression()?)
3402 };
3403 self.expect(&Token::RParen)?;
3404 let body = self.parse_block()?;
3405 let continue_block = self.parse_optional_continue_block()?;
3406 Ok(Statement {
3407 label: None,
3408 kind: StmtKind::For {
3409 init,
3410 condition,
3411 step,
3412 body,
3413 label: None,
3414 continue_block,
3415 },
3416 line,
3417 })
3418 }
3419
3420 fn parse_foreach(&mut self) -> PerlResult<Statement> {
3421 let line = self.peek_line();
3422 self.advance(); let var = match self.peek() {
3424 Token::Ident(ref kw) if kw == "my" => {
3425 self.advance();
3426 self.parse_scalar_var_name()?
3427 }
3428 Token::ScalarVar(_) => self.parse_scalar_var_name()?,
3429 _ => "_".to_string(),
3430 };
3431 self.expect(&Token::LParen)?;
3432 let list = self.parse_expression()?;
3433 self.expect(&Token::RParen)?;
3434 let body = self.parse_block()?;
3435 let continue_block = self.parse_optional_continue_block()?;
3436 Ok(Statement {
3437 label: None,
3438 kind: StmtKind::Foreach {
3439 var,
3440 list,
3441 body,
3442 label: None,
3443 continue_block,
3444 },
3445 line,
3446 })
3447 }
3448
3449 fn parse_scalar_var_name(&mut self) -> PerlResult<String> {
3450 match self.advance() {
3451 (Token::ScalarVar(name), _) => Ok(name),
3452 (tok, line) => {
3453 Err(self.syntax_err(format!("Expected scalar variable, got {:?}", tok), line))
3454 }
3455 }
3456 }
3457
3458 fn parse_legacy_sub_prototype_tail(&mut self) -> PerlResult<String> {
3460 let mut s = String::new();
3461 loop {
3462 match self.peek().clone() {
3463 Token::RParen => {
3464 self.advance();
3465 break;
3466 }
3467 Token::Eof => {
3468 return Err(self.syntax_err(
3469 "Unterminated sub prototype (expected ')' before end of input)",
3470 self.peek_line(),
3471 ));
3472 }
3473 Token::ScalarVar(v) if v == ")" => {
3474 self.advance();
3477 s.push('$');
3478 if matches!(self.peek(), Token::LBrace) {
3479 break;
3480 }
3481 }
3482 Token::Ident(i) => {
3483 let i = i.clone();
3484 self.advance();
3485 s.push_str(&i);
3486 }
3487 Token::Semicolon => {
3488 self.advance();
3489 s.push(';');
3490 }
3491 Token::LParen => {
3492 self.advance();
3493 s.push('(');
3494 }
3495 Token::LBracket => {
3496 self.advance();
3497 s.push('[');
3498 }
3499 Token::RBracket => {
3500 self.advance();
3501 s.push(']');
3502 }
3503 Token::Backslash => {
3504 self.advance();
3505 s.push('\\');
3506 }
3507 Token::Comma => {
3508 self.advance();
3509 s.push(',');
3510 }
3511 Token::ScalarVar(v) => {
3512 let v = v.clone();
3513 self.advance();
3514 s.push('$');
3515 s.push_str(&v);
3516 }
3517 Token::ArrayVar(v) => {
3518 let v = v.clone();
3519 self.advance();
3520 s.push('@');
3521 s.push_str(&v);
3522 }
3523 Token::ArrayAt => {
3525 self.advance();
3526 s.push('@');
3527 }
3528 Token::HashVar(v) => {
3529 let v = v.clone();
3530 self.advance();
3531 s.push('%');
3532 s.push_str(&v);
3533 }
3534 Token::HashPercent => {
3535 self.advance();
3536 s.push('%');
3537 }
3538 Token::Plus => {
3539 self.advance();
3540 s.push('+');
3541 }
3542 Token::Minus => {
3543 self.advance();
3544 s.push('-');
3545 }
3546 Token::BitAnd => {
3547 self.advance();
3548 s.push('&');
3549 }
3550 tok => {
3551 return Err(self.syntax_err(
3552 format!("Unexpected token in sub prototype: {:?}", tok),
3553 self.peek_line(),
3554 ));
3555 }
3556 }
3557 }
3558 Ok(s)
3559 }
3560
3561 fn sub_signature_list_starts_here(&self) -> bool {
3562 match self.peek() {
3563 Token::LBrace | Token::LBracket => true,
3564 Token::ScalarVar(name) if name != "$$" && name != ")" => true,
3565 Token::ArrayVar(_) | Token::HashVar(_) => true,
3566 _ => false,
3567 }
3568 }
3569
3570 fn parse_sub_signature_hash_key(&mut self) -> PerlResult<String> {
3571 let (tok, line) = self.advance();
3572 match tok {
3573 Token::Ident(i) => Ok(i),
3574 Token::SingleString(s) | Token::DoubleString(s) => Ok(s),
3575 tok => Err(self.syntax_err(
3576 format!(
3577 "sub signature: expected hash key (identifier or string), got {:?}",
3578 tok
3579 ),
3580 line,
3581 )),
3582 }
3583 }
3584
3585 fn parse_sub_signature_param_list(&mut self) -> PerlResult<Vec<SubSigParam>> {
3586 let mut params = Vec::new();
3587 loop {
3588 if matches!(self.peek(), Token::RParen) {
3589 break;
3590 }
3591 match self.peek().clone() {
3592 Token::ScalarVar(name) => {
3593 if name == "$$" || name == ")" {
3594 return Err(self.syntax_err(
3595 format!(
3596 "`{name}` cannot start a stryke sub signature (use legacy prototype `($$)` etc.)"
3597 ),
3598 self.peek_line(),
3599 ));
3600 }
3601 self.advance();
3602 let ty = if self.eat(&Token::Colon) {
3603 match self.peek() {
3604 Token::Ident(ref tname) => {
3605 let tname = tname.clone();
3606 self.advance();
3607 Some(match tname.as_str() {
3608 "Int" => PerlTypeName::Int,
3609 "Str" => PerlTypeName::Str,
3610 "Float" => PerlTypeName::Float,
3611 "Bool" => PerlTypeName::Bool,
3612 "Array" => PerlTypeName::Array,
3613 "Hash" => PerlTypeName::Hash,
3614 "Ref" => PerlTypeName::Ref,
3615 "Any" => PerlTypeName::Any,
3616 _ => PerlTypeName::Struct(tname),
3617 })
3618 }
3619 _ => {
3620 return Err(self.syntax_err(
3621 "expected type name after `:` in sub signature",
3622 self.peek_line(),
3623 ));
3624 }
3625 }
3626 } else {
3627 None
3628 };
3629 params.push(SubSigParam::Scalar(name, ty));
3630 }
3631 Token::ArrayVar(name) => {
3632 self.advance();
3633 params.push(SubSigParam::Array(name));
3634 }
3635 Token::HashVar(name) => {
3636 self.advance();
3637 params.push(SubSigParam::Hash(name));
3638 }
3639 Token::LBracket => {
3640 self.advance();
3641 let elems = self.parse_match_array_elems_until_rbracket()?;
3642 params.push(SubSigParam::ArrayDestruct(elems));
3643 }
3644 Token::LBrace => {
3645 self.advance();
3646 let mut pairs = Vec::new();
3647 loop {
3648 if matches!(self.peek(), Token::RBrace | Token::Eof) {
3649 break;
3650 }
3651 if self.eat(&Token::Comma) {
3652 continue;
3653 }
3654 let key = self.parse_sub_signature_hash_key()?;
3655 self.expect(&Token::FatArrow)?;
3656 let bind = self.parse_scalar_var_name()?;
3657 pairs.push((key, bind));
3658 self.eat(&Token::Comma);
3659 }
3660 self.expect(&Token::RBrace)?;
3661 params.push(SubSigParam::HashDestruct(pairs));
3662 }
3663 tok => {
3664 return Err(self.syntax_err(
3665 format!(
3666 "expected `$name`, `[ ... ]`, or `{{ ... }}` in sub signature, got {:?}",
3667 tok
3668 ),
3669 self.peek_line(),
3670 ));
3671 }
3672 }
3673 match self.peek() {
3674 Token::Comma => {
3675 self.advance();
3676 if matches!(self.peek(), Token::RParen) {
3677 return Err(self.syntax_err(
3678 "trailing `,` before `)` in sub signature",
3679 self.peek_line(),
3680 ));
3681 }
3682 }
3683 Token::RParen => break,
3684 _ => {
3685 return Err(self.syntax_err(
3686 format!(
3687 "expected `,` or `)` after sub signature parameter, got {:?}",
3688 self.peek()
3689 ),
3690 self.peek_line(),
3691 ));
3692 }
3693 }
3694 }
3695 Ok(params)
3696 }
3697
3698 fn parse_sub_sig_or_prototype_opt(&mut self) -> PerlResult<(Vec<SubSigParam>, Option<String>)> {
3700 if !matches!(self.peek(), Token::LParen) {
3701 return Ok((vec![], None));
3702 }
3703 self.advance();
3704 if matches!(self.peek(), Token::RParen) {
3705 self.advance();
3706 return Ok((vec![], Some(String::new())));
3707 }
3708 if self.sub_signature_list_starts_here() {
3709 let params = self.parse_sub_signature_param_list()?;
3710 self.expect(&Token::RParen)?;
3711 return Ok((params, None));
3712 }
3713 let proto = self.parse_legacy_sub_prototype_tail()?;
3714 Ok((vec![], Some(proto)))
3715 }
3716
3717 fn parse_sub_attributes(&mut self) -> PerlResult<()> {
3719 while self.eat(&Token::Colon) {
3720 match self.advance() {
3721 (Token::Ident(_), _) => {}
3722 (tok, line) => {
3723 return Err(self.syntax_err(
3724 format!("Expected attribute name after `:`, got {:?}", tok),
3725 line,
3726 ));
3727 }
3728 }
3729 if self.eat(&Token::LParen) {
3730 let mut depth = 1usize;
3731 while depth > 0 {
3732 match self.advance().0 {
3733 Token::LParen => depth += 1,
3734 Token::RParen => {
3735 depth -= 1;
3736 }
3737 Token::Eof => {
3738 return Err(self.syntax_err(
3739 "Unterminated sub attribute argument list",
3740 self.peek_line(),
3741 ));
3742 }
3743 _ => {}
3744 }
3745 }
3746 }
3747 }
3748 Ok(())
3749 }
3750
3751 fn parse_sub_decl(&mut self) -> PerlResult<Statement> {
3752 let line = self.peek_line();
3753 self.advance(); match self.peek().clone() {
3755 Token::Ident(_) => {
3756 let name = self.parse_package_qualified_identifier()?;
3757 self.declared_subs.insert(name.clone());
3758 let (params, prototype) = self.parse_sub_sig_or_prototype_opt()?;
3759 self.parse_sub_attributes()?;
3760 let body = self.parse_block()?;
3761 Ok(Statement {
3762 label: None,
3763 kind: StmtKind::SubDecl {
3764 name,
3765 params,
3766 body,
3767 prototype,
3768 },
3769 line,
3770 })
3771 }
3772 Token::LParen | Token::LBrace | Token::Colon => {
3773 let (params, _prototype) = self.parse_sub_sig_or_prototype_opt()?;
3775 self.parse_sub_attributes()?;
3776 let body = self.parse_block()?;
3777 Ok(Statement {
3778 label: None,
3779 kind: StmtKind::Expression(Expr {
3780 kind: ExprKind::CodeRef { params, body },
3781 line,
3782 }),
3783 line,
3784 })
3785 }
3786 tok => Err(self.syntax_err(
3787 format!("Expected sub name, `(`, `{{`, or `:`, got {:?}", tok),
3788 self.peek_line(),
3789 )),
3790 }
3791 }
3792
3793 fn parse_struct_decl(&mut self) -> PerlResult<Statement> {
3795 let line = self.peek_line();
3796 self.advance(); let name = match self.advance() {
3798 (Token::Ident(n), _) => n,
3799 (tok, err_line) => {
3800 return Err(
3801 self.syntax_err(format!("Expected struct name, got {:?}", tok), err_line)
3802 )
3803 }
3804 };
3805 self.expect(&Token::LBrace)?;
3806 let mut fields = Vec::new();
3807 let mut methods = Vec::new();
3808 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
3809 let is_method = match self.peek() {
3811 Token::Ident(s) => s == "fn" || s == "sub",
3812 _ => false,
3813 };
3814 if is_method {
3815 self.advance(); let method_name = match self.advance() {
3817 (Token::Ident(n), _) => n,
3818 (tok, err_line) => {
3819 return Err(self
3820 .syntax_err(format!("Expected method name, got {:?}", tok), err_line))
3821 }
3822 };
3823 let params = if self.eat(&Token::LParen) {
3825 let p = self.parse_sub_signature_param_list()?;
3826 self.expect(&Token::RParen)?;
3827 p
3828 } else {
3829 Vec::new()
3830 };
3831 let body = self.parse_block()?;
3833 methods.push(crate::ast::StructMethod {
3834 name: method_name,
3835 params,
3836 body,
3837 });
3838 self.eat(&Token::Comma);
3840 self.eat(&Token::Semicolon);
3841 continue;
3842 }
3843
3844 let field_name = match self.advance() {
3845 (Token::Ident(n), _) => n,
3846 (tok, err_line) => {
3847 return Err(
3848 self.syntax_err(format!("Expected field name, got {:?}", tok), err_line)
3849 )
3850 }
3851 };
3852 let ty = if self.eat(&Token::FatArrow) {
3854 self.parse_type_name()?
3855 } else {
3856 crate::ast::PerlTypeName::Any
3857 };
3858 let default = if self.eat(&Token::Assign) {
3859 Some(self.parse_ternary()?)
3861 } else {
3862 None
3863 };
3864 fields.push(StructField {
3865 name: field_name,
3866 ty,
3867 default,
3868 });
3869 if !self.eat(&Token::Comma) {
3870 self.eat(&Token::Semicolon);
3872 }
3873 }
3874 self.expect(&Token::RBrace)?;
3875 self.eat(&Token::Semicolon);
3876 Ok(Statement {
3877 label: None,
3878 kind: StmtKind::StructDecl {
3879 def: StructDef {
3880 name,
3881 fields,
3882 methods,
3883 },
3884 },
3885 line,
3886 })
3887 }
3888
3889 fn parse_enum_decl(&mut self) -> PerlResult<Statement> {
3891 let line = self.peek_line();
3892 self.advance(); let name = match self.advance() {
3894 (Token::Ident(n), _) => n,
3895 (tok, err_line) => {
3896 return Err(self.syntax_err(format!("Expected enum name, got {:?}", tok), err_line))
3897 }
3898 };
3899 self.expect(&Token::LBrace)?;
3900 let mut variants = Vec::new();
3901 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
3902 let variant_name = match self.advance() {
3903 (Token::Ident(n), _) => n,
3904 (tok, err_line) => {
3905 return Err(
3906 self.syntax_err(format!("Expected variant name, got {:?}", tok), err_line)
3907 )
3908 }
3909 };
3910 let ty = if self.eat(&Token::FatArrow) {
3911 Some(self.parse_type_name()?)
3912 } else {
3913 None
3914 };
3915 variants.push(EnumVariant {
3916 name: variant_name,
3917 ty,
3918 });
3919 if !self.eat(&Token::Comma) {
3920 self.eat(&Token::Semicolon);
3921 }
3922 }
3923 self.expect(&Token::RBrace)?;
3924 self.eat(&Token::Semicolon);
3925 Ok(Statement {
3926 label: None,
3927 kind: StmtKind::EnumDecl {
3928 def: EnumDef { name, variants },
3929 },
3930 line,
3931 })
3932 }
3933
3934 fn parse_class_decl(&mut self, is_abstract: bool, is_final: bool) -> PerlResult<Statement> {
3936 use crate::ast::{ClassDef, ClassField, ClassMethod, ClassStaticField, Visibility};
3937 let line = self.peek_line();
3938 self.advance(); let name = match self.advance() {
3940 (Token::Ident(n), _) => n,
3941 (tok, err_line) => {
3942 return Err(self.syntax_err(format!("Expected class name, got {:?}", tok), err_line))
3943 }
3944 };
3945
3946 let mut extends = Vec::new();
3948 if matches!(self.peek(), Token::Ident(ref s) if s == "extends") {
3949 self.advance(); loop {
3951 match self.advance() {
3952 (Token::Ident(parent), _) => extends.push(parent),
3953 (tok, err_line) => {
3954 return Err(self.syntax_err(
3955 format!("Expected parent class name after `extends`, got {:?}", tok),
3956 err_line,
3957 ))
3958 }
3959 }
3960 if !self.eat(&Token::Comma) {
3961 break;
3962 }
3963 }
3964 }
3965
3966 let mut implements = Vec::new();
3968 if matches!(self.peek(), Token::Ident(ref s) if s == "impl") {
3969 self.advance(); loop {
3971 match self.advance() {
3972 (Token::Ident(trait_name), _) => implements.push(trait_name),
3973 (tok, err_line) => {
3974 return Err(self.syntax_err(
3975 format!("Expected trait name after `impl`, got {:?}", tok),
3976 err_line,
3977 ))
3978 }
3979 }
3980 if !self.eat(&Token::Comma) {
3981 break;
3982 }
3983 }
3984 }
3985
3986 self.expect(&Token::LBrace)?;
3987 let mut fields = Vec::new();
3988 let mut methods = Vec::new();
3989 let mut static_fields = Vec::new();
3990
3991 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
3992 let visibility = match self.peek() {
3994 Token::Ident(ref s) if s == "pub" => {
3995 self.advance();
3996 Visibility::Public
3997 }
3998 Token::Ident(ref s) if s == "priv" => {
3999 self.advance();
4000 Visibility::Private
4001 }
4002 Token::Ident(ref s) if s == "prot" => {
4003 self.advance();
4004 Visibility::Protected
4005 }
4006 _ => Visibility::Public, };
4008
4009 if matches!(self.peek(), Token::Ident(ref s) if s == "static") {
4011 self.advance(); if matches!(self.peek(), Token::Ident(ref s) if s == "fn" || s == "sub") {
4015 return Err(self.syntax_err(
4017 "use `fn Self.name` for static methods, not `static fn`",
4018 self.peek_line(),
4019 ));
4020 }
4021
4022 let field_name = match self.advance() {
4023 (Token::Ident(n), _) => n,
4024 (tok, err_line) => {
4025 return Err(self.syntax_err(
4026 format!("Expected static field name, got {:?}", tok),
4027 err_line,
4028 ))
4029 }
4030 };
4031
4032 let ty = if self.eat(&Token::Colon) {
4033 self.parse_type_name()?
4034 } else {
4035 crate::ast::PerlTypeName::Any
4036 };
4037
4038 let default = if self.eat(&Token::Assign) {
4039 Some(self.parse_ternary()?)
4040 } else {
4041 None
4042 };
4043
4044 static_fields.push(ClassStaticField {
4045 name: field_name,
4046 ty,
4047 visibility,
4048 default,
4049 });
4050
4051 if !self.eat(&Token::Comma) {
4052 self.eat(&Token::Semicolon);
4053 }
4054 continue;
4055 }
4056
4057 let method_is_final = matches!(self.peek(), Token::Ident(ref s) if s == "final");
4059 if method_is_final {
4060 self.advance(); }
4062
4063 let is_method = matches!(self.peek(), Token::Ident(ref s) if s == "fn" || s == "sub");
4065 if is_method {
4066 self.advance(); let is_static = matches!(self.peek(), Token::Ident(ref s) if s == "Self");
4070 if is_static {
4071 self.advance(); self.expect(&Token::Dot)?;
4073 }
4074
4075 let method_name = match self.advance() {
4076 (Token::Ident(n), _) => n,
4077 (tok, err_line) => {
4078 return Err(self
4079 .syntax_err(format!("Expected method name, got {:?}", tok), err_line))
4080 }
4081 };
4082
4083 let params = if self.eat(&Token::LParen) {
4085 let p = self.parse_sub_signature_param_list()?;
4086 self.expect(&Token::RParen)?;
4087 p
4088 } else {
4089 Vec::new()
4090 };
4091
4092 let body = if matches!(self.peek(), Token::LBrace) {
4094 Some(self.parse_block()?)
4095 } else {
4096 None
4097 };
4098
4099 methods.push(ClassMethod {
4100 name: method_name,
4101 params,
4102 body,
4103 visibility,
4104 is_static,
4105 is_final: method_is_final,
4106 });
4107 self.eat(&Token::Comma);
4108 self.eat(&Token::Semicolon);
4109 continue;
4110 } else if method_is_final {
4111 return Err(self.syntax_err("`final` must be followed by `fn`", self.peek_line()));
4112 }
4113
4114 let field_name = match self.advance() {
4116 (Token::Ident(n), _) => n,
4117 (tok, err_line) => {
4118 return Err(
4119 self.syntax_err(format!("Expected field name, got {:?}", tok), err_line)
4120 )
4121 }
4122 };
4123
4124 let ty = if self.eat(&Token::Colon) {
4126 self.parse_type_name()?
4127 } else {
4128 crate::ast::PerlTypeName::Any
4129 };
4130
4131 let default = if self.eat(&Token::Assign) {
4133 Some(self.parse_ternary()?)
4134 } else {
4135 None
4136 };
4137
4138 fields.push(ClassField {
4139 name: field_name,
4140 ty,
4141 visibility,
4142 default,
4143 });
4144
4145 if !self.eat(&Token::Comma) {
4146 self.eat(&Token::Semicolon);
4147 }
4148 }
4149
4150 self.expect(&Token::RBrace)?;
4151 self.eat(&Token::Semicolon);
4152
4153 Ok(Statement {
4154 label: None,
4155 kind: StmtKind::ClassDecl {
4156 def: ClassDef {
4157 name,
4158 is_abstract,
4159 is_final,
4160 extends,
4161 implements,
4162 fields,
4163 methods,
4164 static_fields,
4165 },
4166 },
4167 line,
4168 })
4169 }
4170
4171 fn parse_trait_decl(&mut self) -> PerlResult<Statement> {
4173 use crate::ast::{ClassMethod, TraitDef, Visibility};
4174 let line = self.peek_line();
4175 self.advance(); let name = match self.advance() {
4177 (Token::Ident(n), _) => n,
4178 (tok, err_line) => {
4179 return Err(self.syntax_err(format!("Expected trait name, got {:?}", tok), err_line))
4180 }
4181 };
4182
4183 self.expect(&Token::LBrace)?;
4184 let mut methods = Vec::new();
4185
4186 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
4187 let visibility = match self.peek() {
4189 Token::Ident(ref s) if s == "pub" => {
4190 self.advance();
4191 Visibility::Public
4192 }
4193 Token::Ident(ref s) if s == "priv" => {
4194 self.advance();
4195 Visibility::Private
4196 }
4197 Token::Ident(ref s) if s == "prot" => {
4198 self.advance();
4199 Visibility::Protected
4200 }
4201 _ => Visibility::Public,
4202 };
4203
4204 if !matches!(self.peek(), Token::Ident(ref s) if s == "fn" || s == "sub") {
4206 return Err(self.syntax_err("Expected `fn` in trait definition", self.peek_line()));
4207 }
4208 self.advance(); let method_name = match self.advance() {
4211 (Token::Ident(n), _) => n,
4212 (tok, err_line) => {
4213 return Err(
4214 self.syntax_err(format!("Expected method name, got {:?}", tok), err_line)
4215 )
4216 }
4217 };
4218
4219 let params = if self.eat(&Token::LParen) {
4221 let p = self.parse_sub_signature_param_list()?;
4222 self.expect(&Token::RParen)?;
4223 p
4224 } else {
4225 Vec::new()
4226 };
4227
4228 let body = if matches!(self.peek(), Token::LBrace) {
4230 Some(self.parse_block()?)
4231 } else {
4232 None
4233 };
4234
4235 methods.push(ClassMethod {
4236 name: method_name,
4237 params,
4238 body,
4239 visibility,
4240 is_static: false,
4241 is_final: false,
4242 });
4243
4244 self.eat(&Token::Comma);
4245 self.eat(&Token::Semicolon);
4246 }
4247
4248 self.expect(&Token::RBrace)?;
4249 self.eat(&Token::Semicolon);
4250
4251 Ok(Statement {
4252 label: None,
4253 kind: StmtKind::TraitDecl {
4254 def: TraitDef { name, methods },
4255 },
4256 line,
4257 })
4258 }
4259
4260 fn local_simple_target_to_var_decl(target: &Expr) -> Option<VarDecl> {
4261 match &target.kind {
4262 ExprKind::ScalarVar(name) => Some(VarDecl {
4263 sigil: Sigil::Scalar,
4264 name: name.clone(),
4265 initializer: None,
4266 frozen: false,
4267 type_annotation: None,
4268 }),
4269 ExprKind::ArrayVar(name) => Some(VarDecl {
4270 sigil: Sigil::Array,
4271 name: name.clone(),
4272 initializer: None,
4273 frozen: false,
4274 type_annotation: None,
4275 }),
4276 ExprKind::HashVar(name) => Some(VarDecl {
4277 sigil: Sigil::Hash,
4278 name: name.clone(),
4279 initializer: None,
4280 frozen: false,
4281 type_annotation: None,
4282 }),
4283 ExprKind::Typeglob(name) => Some(VarDecl {
4284 sigil: Sigil::Typeglob,
4285 name: name.clone(),
4286 initializer: None,
4287 frozen: false,
4288 type_annotation: None,
4289 }),
4290 _ => None,
4291 }
4292 }
4293
4294 fn parse_decl_array_destructure(
4295 &mut self,
4296 keyword: &str,
4297 line: usize,
4298 ) -> PerlResult<Statement> {
4299 self.expect(&Token::LBracket)?;
4300 let elems = self.parse_match_array_elems_until_rbracket()?;
4301 self.expect(&Token::Assign)?;
4302 self.suppress_scalar_hash_brace += 1;
4303 let rhs = self.parse_expression()?;
4304 self.suppress_scalar_hash_brace -= 1;
4305 let stmt = self.desugar_array_destructure(keyword, line, elems, rhs)?;
4306 self.parse_stmt_postfix_modifier(stmt)
4307 }
4308
4309 fn parse_decl_hash_destructure(&mut self, keyword: &str, line: usize) -> PerlResult<Statement> {
4310 let MatchPattern::Hash(pairs) = self.parse_match_hash_pattern()? else {
4311 unreachable!("parse_match_hash_pattern returns Hash");
4312 };
4313 self.expect(&Token::Assign)?;
4314 self.suppress_scalar_hash_brace += 1;
4315 let rhs = self.parse_expression()?;
4316 self.suppress_scalar_hash_brace -= 1;
4317 let stmt = self.desugar_hash_destructure(keyword, line, pairs, rhs)?;
4318 self.parse_stmt_postfix_modifier(stmt)
4319 }
4320
4321 fn desugar_array_destructure(
4322 &mut self,
4323 keyword: &str,
4324 line: usize,
4325 elems: Vec<MatchArrayElem>,
4326 rhs: Expr,
4327 ) -> PerlResult<Statement> {
4328 let tmp = format!("__stryke_ds_{}", self.alloc_desugar_tmp());
4329 let mut stmts: Vec<Statement> = Vec::new();
4330 stmts.push(destructure_stmt_from_var_decls(
4331 keyword,
4332 vec![VarDecl {
4333 sigil: Sigil::Scalar,
4334 name: tmp.clone(),
4335 initializer: Some(rhs),
4336 frozen: false,
4337 type_annotation: None,
4338 }],
4339 line,
4340 ));
4341
4342 let has_rest = elems
4343 .iter()
4344 .any(|e| matches!(e, MatchArrayElem::Rest | MatchArrayElem::RestBind(_)));
4345 let fixed_slots = elems
4346 .iter()
4347 .filter(|e| {
4348 matches!(
4349 e,
4350 MatchArrayElem::CaptureScalar(_) | MatchArrayElem::Expr(_)
4351 )
4352 })
4353 .count();
4354 if !has_rest {
4355 let cond = Expr {
4356 kind: ExprKind::BinOp {
4357 left: Box::new(destructure_expr_array_len(&tmp, line)),
4358 op: BinOp::NumEq,
4359 right: Box::new(Expr {
4360 kind: ExprKind::Integer(fixed_slots as i64),
4361 line,
4362 }),
4363 },
4364 line,
4365 };
4366 stmts.push(destructure_stmt_unless_die(
4367 line,
4368 cond,
4369 "array destructure: length mismatch",
4370 ));
4371 }
4372
4373 let mut idx: i64 = 0;
4374 for elem in elems {
4375 match elem {
4376 MatchArrayElem::Rest => break,
4377 MatchArrayElem::RestBind(name) => {
4378 let list_source = Expr {
4379 kind: ExprKind::Deref {
4380 expr: Box::new(destructure_expr_scalar_tmp(&tmp, line)),
4381 kind: Sigil::Array,
4382 },
4383 line,
4384 };
4385 let last_ix = Expr {
4386 kind: ExprKind::BinOp {
4387 left: Box::new(destructure_expr_array_len(&tmp, line)),
4388 op: BinOp::Sub,
4389 right: Box::new(Expr {
4390 kind: ExprKind::Integer(1),
4391 line,
4392 }),
4393 },
4394 line,
4395 };
4396 let range = Expr {
4397 kind: ExprKind::Range {
4398 from: Box::new(Expr {
4399 kind: ExprKind::Integer(idx),
4400 line,
4401 }),
4402 to: Box::new(last_ix),
4403 exclusive: false,
4404 },
4405 line,
4406 };
4407 let slice = Expr {
4408 kind: ExprKind::AnonymousListSlice {
4409 source: Box::new(list_source),
4410 indices: vec![range],
4411 },
4412 line,
4413 };
4414 stmts.push(destructure_stmt_from_var_decls(
4415 keyword,
4416 vec![VarDecl {
4417 sigil: Sigil::Array,
4418 name,
4419 initializer: Some(slice),
4420 frozen: false,
4421 type_annotation: None,
4422 }],
4423 line,
4424 ));
4425 break;
4426 }
4427 MatchArrayElem::CaptureScalar(name) => {
4428 let arrow = Expr {
4429 kind: ExprKind::ArrowDeref {
4430 expr: Box::new(destructure_expr_scalar_tmp(&tmp, line)),
4431 index: Box::new(Expr {
4432 kind: ExprKind::Integer(idx),
4433 line,
4434 }),
4435 kind: DerefKind::Array,
4436 },
4437 line,
4438 };
4439 stmts.push(destructure_stmt_from_var_decls(
4440 keyword,
4441 vec![VarDecl {
4442 sigil: Sigil::Scalar,
4443 name,
4444 initializer: Some(arrow),
4445 frozen: false,
4446 type_annotation: None,
4447 }],
4448 line,
4449 ));
4450 idx += 1;
4451 }
4452 MatchArrayElem::Expr(e) => {
4453 let elem_subj = Expr {
4454 kind: ExprKind::ArrowDeref {
4455 expr: Box::new(destructure_expr_scalar_tmp(&tmp, line)),
4456 index: Box::new(Expr {
4457 kind: ExprKind::Integer(idx),
4458 line,
4459 }),
4460 kind: DerefKind::Array,
4461 },
4462 line,
4463 };
4464 let match_expr = Expr {
4465 kind: ExprKind::AlgebraicMatch {
4466 subject: Box::new(elem_subj),
4467 arms: vec![
4468 MatchArm {
4469 pattern: MatchPattern::Value(Box::new(e.clone())),
4470 guard: None,
4471 body: Expr {
4472 kind: ExprKind::Integer(0),
4473 line,
4474 },
4475 },
4476 MatchArm {
4477 pattern: MatchPattern::Any,
4478 guard: None,
4479 body: Expr {
4480 kind: ExprKind::Die(vec![Expr {
4481 kind: ExprKind::String(
4482 "array destructure: element pattern mismatch"
4483 .to_string(),
4484 ),
4485 line,
4486 }]),
4487 line,
4488 },
4489 },
4490 ],
4491 },
4492 line,
4493 };
4494 stmts.push(Statement {
4495 label: None,
4496 kind: StmtKind::Expression(match_expr),
4497 line,
4498 });
4499 idx += 1;
4500 }
4501 }
4502 }
4503
4504 Ok(Statement {
4505 label: None,
4506 kind: StmtKind::StmtGroup(stmts),
4507 line,
4508 })
4509 }
4510
4511 fn desugar_hash_destructure(
4512 &mut self,
4513 keyword: &str,
4514 line: usize,
4515 pairs: Vec<MatchHashPair>,
4516 rhs: Expr,
4517 ) -> PerlResult<Statement> {
4518 let tmp = format!("__stryke_ds_{}", self.alloc_desugar_tmp());
4519 let mut stmts: Vec<Statement> = Vec::new();
4520 stmts.push(destructure_stmt_from_var_decls(
4521 keyword,
4522 vec![VarDecl {
4523 sigil: Sigil::Scalar,
4524 name: tmp.clone(),
4525 initializer: Some(rhs),
4526 frozen: false,
4527 type_annotation: None,
4528 }],
4529 line,
4530 ));
4531
4532 for pair in pairs {
4533 match pair {
4534 MatchHashPair::KeyOnly { key } => {
4535 let exists_op = Expr {
4536 kind: ExprKind::Exists(Box::new(Expr {
4537 kind: ExprKind::ArrowDeref {
4538 expr: Box::new(destructure_expr_scalar_tmp(&tmp, line)),
4539 index: Box::new(key),
4540 kind: DerefKind::Hash,
4541 },
4542 line,
4543 })),
4544 line,
4545 };
4546 stmts.push(destructure_stmt_unless_die(
4547 line,
4548 exists_op,
4549 "hash destructure: missing required key",
4550 ));
4551 }
4552 MatchHashPair::Capture { key, name } => {
4553 let init = Expr {
4554 kind: ExprKind::ArrowDeref {
4555 expr: Box::new(destructure_expr_scalar_tmp(&tmp, line)),
4556 index: Box::new(key),
4557 kind: DerefKind::Hash,
4558 },
4559 line,
4560 };
4561 stmts.push(destructure_stmt_from_var_decls(
4562 keyword,
4563 vec![VarDecl {
4564 sigil: Sigil::Scalar,
4565 name,
4566 initializer: Some(init),
4567 frozen: false,
4568 type_annotation: None,
4569 }],
4570 line,
4571 ));
4572 }
4573 }
4574 }
4575
4576 Ok(Statement {
4577 label: None,
4578 kind: StmtKind::StmtGroup(stmts),
4579 line,
4580 })
4581 }
4582
4583 fn parse_my_our_local(
4584 &mut self,
4585 keyword: &str,
4586 allow_type_annotation: bool,
4587 ) -> PerlResult<Statement> {
4588 let line = self.peek_line();
4589 self.advance(); if keyword == "local"
4592 && !matches!(self.peek(), Token::LParen | Token::LBracket | Token::LBrace)
4593 {
4594 let target = self.parse_postfix()?;
4595 let mut initializer: Option<Expr> = None;
4596 if self.eat(&Token::Assign) {
4597 initializer = Some(self.parse_expression()?);
4598 } else if matches!(
4599 self.peek(),
4600 Token::OrAssign | Token::DefinedOrAssign | Token::AndAssign
4601 ) {
4602 if matches!(&target.kind, ExprKind::Typeglob(_)) {
4603 return Err(self.syntax_err(
4604 "compound assignment on typeglob declaration is not supported",
4605 self.peek_line(),
4606 ));
4607 }
4608 let op = match self.peek().clone() {
4609 Token::OrAssign => BinOp::LogOr,
4610 Token::DefinedOrAssign => BinOp::DefinedOr,
4611 Token::AndAssign => BinOp::LogAnd,
4612 _ => unreachable!(),
4613 };
4614 self.advance();
4615 let rhs = self.parse_assign_expr()?;
4616 let tgt_line = target.line;
4617 initializer = Some(Expr {
4618 kind: ExprKind::CompoundAssign {
4619 target: Box::new(target.clone()),
4620 op,
4621 value: Box::new(rhs),
4622 },
4623 line: tgt_line,
4624 });
4625 }
4626
4627 let kind = if let Some(mut decl) = Self::local_simple_target_to_var_decl(&target) {
4628 decl.initializer = initializer;
4629 StmtKind::Local(vec![decl])
4630 } else {
4631 StmtKind::LocalExpr {
4632 target,
4633 initializer,
4634 }
4635 };
4636 let stmt = Statement {
4637 label: None,
4638 kind,
4639 line,
4640 };
4641 return self.parse_stmt_postfix_modifier(stmt);
4642 }
4643
4644 if matches!(self.peek(), Token::LBracket) {
4645 return self.parse_decl_array_destructure(keyword, line);
4646 }
4647 if matches!(self.peek(), Token::LBrace) {
4648 return self.parse_decl_hash_destructure(keyword, line);
4649 }
4650
4651 let mut decls = Vec::new();
4652
4653 if self.eat(&Token::LParen) {
4654 while !matches!(self.peek(), Token::RParen | Token::Eof) {
4656 let decl = self.parse_var_decl(allow_type_annotation)?;
4657 decls.push(decl);
4658 if !self.eat(&Token::Comma) {
4659 break;
4660 }
4661 }
4662 self.expect(&Token::RParen)?;
4663 } else {
4664 decls.push(self.parse_var_decl(allow_type_annotation)?);
4665 }
4666
4667 if self.eat(&Token::Assign) {
4669 if keyword == "our" && decls.len() == 1 {
4670 while matches!(self.peek(), Token::Ident(ref i) if i == "our") {
4671 self.advance();
4672 decls.push(self.parse_var_decl(allow_type_annotation)?);
4673 if !self.eat(&Token::Assign) {
4674 return Err(self.syntax_err(
4675 "expected `=` after `our` in chained our-declaration",
4676 self.peek_line(),
4677 ));
4678 }
4679 }
4680 }
4681 let val = self.parse_expression()?;
4682 if decls.len() == 1 {
4683 decls[0].initializer = Some(val);
4684 } else {
4685 for decl in &mut decls {
4686 decl.initializer = Some(val.clone());
4687 }
4688 }
4689 } else if decls.len() == 1 {
4690 let op = match self.peek().clone() {
4692 Token::OrAssign => Some(BinOp::LogOr),
4693 Token::DefinedOrAssign => Some(BinOp::DefinedOr),
4694 Token::AndAssign => Some(BinOp::LogAnd),
4695 _ => None,
4696 };
4697 if let Some(op) = op {
4698 let d = &decls[0];
4699 if matches!(d.sigil, Sigil::Typeglob) {
4700 return Err(self.syntax_err(
4701 "compound assignment on typeglob declaration is not supported",
4702 self.peek_line(),
4703 ));
4704 }
4705 self.advance();
4706 let rhs = self.parse_assign_expr()?;
4707 let target = Expr {
4708 kind: match d.sigil {
4709 Sigil::Scalar => ExprKind::ScalarVar(d.name.clone()),
4710 Sigil::Array => ExprKind::ArrayVar(d.name.clone()),
4711 Sigil::Hash => ExprKind::HashVar(d.name.clone()),
4712 Sigil::Typeglob => unreachable!(),
4713 },
4714 line,
4715 };
4716 decls[0].initializer = Some(Expr {
4717 kind: ExprKind::CompoundAssign {
4718 target: Box::new(target),
4719 op,
4720 value: Box::new(rhs),
4721 },
4722 line,
4723 });
4724 }
4725 }
4726
4727 let kind = match keyword {
4728 "my" => StmtKind::My(decls),
4729 "mysync" => StmtKind::MySync(decls),
4730 "our" => StmtKind::Our(decls),
4731 "local" => StmtKind::Local(decls),
4732 "state" => StmtKind::State(decls),
4733 _ => unreachable!(),
4734 };
4735 let stmt = Statement {
4736 label: None,
4737 kind,
4738 line,
4739 };
4740 self.parse_stmt_postfix_modifier(stmt)
4742 }
4743
4744 fn parse_var_decl(&mut self, allow_type_annotation: bool) -> PerlResult<VarDecl> {
4745 let mut decl = match self.advance() {
4746 (Token::ScalarVar(name), _) => VarDecl {
4747 sigil: Sigil::Scalar,
4748 name,
4749 initializer: None,
4750 frozen: false,
4751 type_annotation: None,
4752 },
4753 (Token::ArrayVar(name), _) => VarDecl {
4754 sigil: Sigil::Array,
4755 name,
4756 initializer: None,
4757 frozen: false,
4758 type_annotation: None,
4759 },
4760 (Token::HashVar(name), _) => VarDecl {
4761 sigil: Sigil::Hash,
4762 name,
4763 initializer: None,
4764 frozen: false,
4765 type_annotation: None,
4766 },
4767 (Token::Star, _line) => {
4768 let name = match self.advance() {
4769 (Token::Ident(n), _) => n,
4770 (tok, l) => {
4771 return Err(self
4772 .syntax_err(format!("Expected identifier after *, got {:?}", tok), l));
4773 }
4774 };
4775 VarDecl {
4776 sigil: Sigil::Typeglob,
4777 name,
4778 initializer: None,
4779 frozen: false,
4780 type_annotation: None,
4781 }
4782 }
4783 (Token::Ident(ref kw), _) if kw == "undef" => VarDecl {
4788 sigil: Sigil::Scalar,
4789 name: format!("__undef_sink_{}", self.pos),
4793 initializer: None,
4794 frozen: false,
4795 type_annotation: None,
4796 },
4797 (tok, line) => {
4798 return Err(self.syntax_err(
4799 format!("Expected variable in declaration, got {:?}", tok),
4800 line,
4801 ));
4802 }
4803 };
4804 if allow_type_annotation && self.eat(&Token::Colon) {
4805 let ty = self.parse_type_name()?;
4806 if decl.sigil != Sigil::Scalar {
4807 return Err(self.syntax_err(
4808 "`: Type` is only valid for scalar declarations (typed my $name : Int)",
4809 self.peek_line(),
4810 ));
4811 }
4812 decl.type_annotation = Some(ty);
4813 }
4814 Ok(decl)
4815 }
4816
4817 fn parse_type_name(&mut self) -> PerlResult<PerlTypeName> {
4818 match self.advance() {
4819 (Token::Ident(name), _) => match name.as_str() {
4820 "Int" => Ok(PerlTypeName::Int),
4821 "Str" => Ok(PerlTypeName::Str),
4822 "Float" => Ok(PerlTypeName::Float),
4823 "Bool" => Ok(PerlTypeName::Bool),
4824 "Array" => Ok(PerlTypeName::Array),
4825 "Hash" => Ok(PerlTypeName::Hash),
4826 "Ref" => Ok(PerlTypeName::Ref),
4827 "Any" => Ok(PerlTypeName::Any),
4828 _ => Ok(PerlTypeName::Struct(name)),
4829 },
4830 (tok, err_line) => Err(self.syntax_err(
4831 format!("Expected type name after `:`, got {:?}", tok),
4832 err_line,
4833 )),
4834 }
4835 }
4836
4837 fn parse_package(&mut self) -> PerlResult<Statement> {
4838 let line = self.peek_line();
4839 self.advance(); let name = match self.advance() {
4841 (Token::Ident(n), _) => n,
4842 (tok, line) => {
4843 return Err(self.syntax_err(format!("Expected package name, got {:?}", tok), line))
4844 }
4845 };
4846 let mut full_name = name;
4848 while self.eat(&Token::PackageSep) {
4849 if let (Token::Ident(part), _) = self.advance() {
4850 full_name = format!("{}::{}", full_name, part);
4851 }
4852 }
4853 self.eat(&Token::Semicolon);
4854 Ok(Statement {
4855 label: None,
4856 kind: StmtKind::Package { name: full_name },
4857 line,
4858 })
4859 }
4860
4861 fn parse_use(&mut self) -> PerlResult<Statement> {
4862 let line = self.peek_line();
4863 self.advance(); let (tok, tok_line) = self.advance();
4865 match tok {
4866 Token::Float(v) => {
4867 self.eat(&Token::Semicolon);
4868 Ok(Statement {
4869 label: None,
4870 kind: StmtKind::UsePerlVersion { version: v },
4871 line,
4872 })
4873 }
4874 Token::Integer(n) => {
4875 if matches!(self.peek(), Token::Semicolon | Token::Eof) {
4876 self.eat(&Token::Semicolon);
4877 Ok(Statement {
4878 label: None,
4879 kind: StmtKind::UsePerlVersion { version: n as f64 },
4880 line,
4881 })
4882 } else {
4883 Err(self.syntax_err(
4884 format!("Expected ';' after use VERSION (got {:?})", self.peek()),
4885 line,
4886 ))
4887 }
4888 }
4889 Token::Ident(n) => {
4890 let mut full_name = n;
4891 while self.eat(&Token::PackageSep) {
4892 if let (Token::Ident(part), _) = self.advance() {
4893 full_name = format!("{}::{}", full_name, part);
4894 }
4895 }
4896 if full_name == "overload" {
4897 let mut pairs = Vec::new();
4898 let mut parse_overload_pairs = |this: &mut Self| -> PerlResult<()> {
4899 loop {
4900 if matches!(this.peek(), Token::RParen | Token::Semicolon | Token::Eof)
4901 {
4902 break;
4903 }
4904 let key_e = this.parse_assign_expr()?;
4905 this.expect(&Token::FatArrow)?;
4906 let val_e = this.parse_assign_expr()?;
4907 let key = this.expr_to_overload_key(&key_e)?;
4908 let val = this.expr_to_overload_sub(&val_e)?;
4909 pairs.push((key, val));
4910 if !this.eat(&Token::Comma) {
4911 break;
4912 }
4913 }
4914 Ok(())
4915 };
4916 if self.eat(&Token::LParen) {
4917 parse_overload_pairs(self)?;
4919 self.expect(&Token::RParen)?;
4920 } else if !matches!(self.peek(), Token::Semicolon | Token::Eof) {
4921 parse_overload_pairs(self)?;
4922 }
4923 self.eat(&Token::Semicolon);
4924 return Ok(Statement {
4925 label: None,
4926 kind: StmtKind::UseOverload { pairs },
4927 line,
4928 });
4929 }
4930 let mut imports = Vec::new();
4931 if !matches!(self.peek(), Token::Semicolon | Token::Eof)
4932 && !self.next_is_new_statement_start(tok_line)
4933 {
4934 loop {
4935 if matches!(self.peek(), Token::Semicolon | Token::Eof) {
4936 break;
4937 }
4938 imports.push(self.parse_expression()?);
4939 if !self.eat(&Token::Comma) {
4940 break;
4941 }
4942 }
4943 }
4944 self.eat(&Token::Semicolon);
4945 Ok(Statement {
4946 label: None,
4947 kind: StmtKind::Use {
4948 module: full_name,
4949 imports,
4950 },
4951 line,
4952 })
4953 }
4954 other => Err(self.syntax_err(
4955 format!("Expected module name or version after use, got {:?}", other),
4956 tok_line,
4957 )),
4958 }
4959 }
4960
4961 fn parse_no(&mut self) -> PerlResult<Statement> {
4962 let line = self.peek_line();
4963 self.advance(); let module = match self.advance() {
4965 (Token::Ident(n), tok_line) => (n, tok_line),
4966 (tok, line) => {
4967 return Err(self.syntax_err(
4968 format!("Expected module name after no, got {:?}", tok),
4969 line,
4970 ))
4971 }
4972 };
4973 let (module_name, tok_line) = module;
4974 let mut full_name = module_name;
4975 while self.eat(&Token::PackageSep) {
4976 if let (Token::Ident(part), _) = self.advance() {
4977 full_name = format!("{}::{}", full_name, part);
4978 }
4979 }
4980 let mut imports = Vec::new();
4981 if !matches!(self.peek(), Token::Semicolon | Token::Eof)
4982 && !self.next_is_new_statement_start(tok_line)
4983 {
4984 loop {
4985 if matches!(self.peek(), Token::Semicolon | Token::Eof) {
4986 break;
4987 }
4988 imports.push(self.parse_expression()?);
4989 if !self.eat(&Token::Comma) {
4990 break;
4991 }
4992 }
4993 }
4994 self.eat(&Token::Semicolon);
4995 Ok(Statement {
4996 label: None,
4997 kind: StmtKind::No {
4998 module: full_name,
4999 imports,
5000 },
5001 line,
5002 })
5003 }
5004
5005 fn parse_return(&mut self) -> PerlResult<Statement> {
5006 let line = self.peek_line();
5007 self.advance(); let val = if matches!(self.peek(), Token::Semicolon | Token::RBrace | Token::Eof) {
5009 None
5010 } else {
5011 Some(self.parse_assign_expr()?)
5013 };
5014 let stmt = Statement {
5016 label: None,
5017 kind: StmtKind::Return(val),
5018 line,
5019 };
5020 if let Token::Ident(ref kw) = self.peek().clone() {
5021 match kw.as_str() {
5022 "if" => {
5023 self.advance();
5024 let cond = self.parse_expression()?;
5025 self.eat(&Token::Semicolon);
5026 return Ok(Statement {
5027 label: None,
5028 kind: StmtKind::If {
5029 condition: cond,
5030 body: vec![stmt],
5031 elsifs: vec![],
5032 else_block: None,
5033 },
5034 line,
5035 });
5036 }
5037 "unless" => {
5038 self.advance();
5039 let cond = self.parse_expression()?;
5040 self.eat(&Token::Semicolon);
5041 return Ok(Statement {
5042 label: None,
5043 kind: StmtKind::Unless {
5044 condition: cond,
5045 body: vec![stmt],
5046 else_block: None,
5047 },
5048 line,
5049 });
5050 }
5051 _ => {}
5052 }
5053 }
5054 self.eat(&Token::Semicolon);
5055 Ok(stmt)
5056 }
5057
5058 fn parse_expression(&mut self) -> PerlResult<Expr> {
5061 self.parse_comma_expr()
5062 }
5063
5064 fn parse_comma_expr(&mut self) -> PerlResult<Expr> {
5065 let expr = self.parse_assign_expr()?;
5066 let mut exprs = vec![expr];
5067 while self.eat(&Token::Comma) || self.eat(&Token::FatArrow) {
5068 if matches!(
5069 self.peek(),
5070 Token::RParen | Token::RBracket | Token::RBrace | Token::Semicolon | Token::Eof
5071 ) {
5072 break; }
5074 exprs.push(self.parse_assign_expr()?);
5075 }
5076 if exprs.len() == 1 {
5077 return Ok(exprs.pop().unwrap());
5078 }
5079 let line = exprs[0].line;
5080 Ok(Expr {
5081 kind: ExprKind::List(exprs),
5082 line,
5083 })
5084 }
5085
5086 fn parse_assign_expr(&mut self) -> PerlResult<Expr> {
5087 let expr = self.parse_ternary()?;
5088 let line = expr.line;
5089
5090 match self.peek().clone() {
5091 Token::Assign => {
5092 self.advance();
5093 let right = self.parse_assign_expr()?;
5094 Ok(Expr {
5095 kind: ExprKind::Assign {
5096 target: Box::new(expr),
5097 value: Box::new(right),
5098 },
5099 line,
5100 })
5101 }
5102 Token::PlusAssign => {
5103 self.advance();
5104 let r = self.parse_assign_expr()?;
5105 Ok(Expr {
5106 kind: ExprKind::CompoundAssign {
5107 target: Box::new(expr),
5108 op: BinOp::Add,
5109 value: Box::new(r),
5110 },
5111 line,
5112 })
5113 }
5114 Token::MinusAssign => {
5115 self.advance();
5116 let r = self.parse_assign_expr()?;
5117 Ok(Expr {
5118 kind: ExprKind::CompoundAssign {
5119 target: Box::new(expr),
5120 op: BinOp::Sub,
5121 value: Box::new(r),
5122 },
5123 line,
5124 })
5125 }
5126 Token::MulAssign => {
5127 self.advance();
5128 let r = self.parse_assign_expr()?;
5129 Ok(Expr {
5130 kind: ExprKind::CompoundAssign {
5131 target: Box::new(expr),
5132 op: BinOp::Mul,
5133 value: Box::new(r),
5134 },
5135 line,
5136 })
5137 }
5138 Token::DivAssign => {
5139 self.advance();
5140 let r = self.parse_assign_expr()?;
5141 Ok(Expr {
5142 kind: ExprKind::CompoundAssign {
5143 target: Box::new(expr),
5144 op: BinOp::Div,
5145 value: Box::new(r),
5146 },
5147 line,
5148 })
5149 }
5150 Token::ModAssign => {
5151 self.advance();
5152 let r = self.parse_assign_expr()?;
5153 Ok(Expr {
5154 kind: ExprKind::CompoundAssign {
5155 target: Box::new(expr),
5156 op: BinOp::Mod,
5157 value: Box::new(r),
5158 },
5159 line,
5160 })
5161 }
5162 Token::PowAssign => {
5163 self.advance();
5164 let r = self.parse_assign_expr()?;
5165 Ok(Expr {
5166 kind: ExprKind::CompoundAssign {
5167 target: Box::new(expr),
5168 op: BinOp::Pow,
5169 value: Box::new(r),
5170 },
5171 line,
5172 })
5173 }
5174 Token::DotAssign => {
5175 self.advance();
5176 let r = self.parse_assign_expr()?;
5177 Ok(Expr {
5178 kind: ExprKind::CompoundAssign {
5179 target: Box::new(expr),
5180 op: BinOp::Concat,
5181 value: Box::new(r),
5182 },
5183 line,
5184 })
5185 }
5186 Token::BitAndAssign => {
5187 self.advance();
5188 let r = self.parse_assign_expr()?;
5189 Ok(Expr {
5190 kind: ExprKind::CompoundAssign {
5191 target: Box::new(expr),
5192 op: BinOp::BitAnd,
5193 value: Box::new(r),
5194 },
5195 line,
5196 })
5197 }
5198 Token::BitOrAssign => {
5199 self.advance();
5200 let r = self.parse_assign_expr()?;
5201 Ok(Expr {
5202 kind: ExprKind::CompoundAssign {
5203 target: Box::new(expr),
5204 op: BinOp::BitOr,
5205 value: Box::new(r),
5206 },
5207 line,
5208 })
5209 }
5210 Token::XorAssign => {
5211 self.advance();
5212 let r = self.parse_assign_expr()?;
5213 Ok(Expr {
5214 kind: ExprKind::CompoundAssign {
5215 target: Box::new(expr),
5216 op: BinOp::BitXor,
5217 value: Box::new(r),
5218 },
5219 line,
5220 })
5221 }
5222 Token::ShiftLeftAssign => {
5223 self.advance();
5224 let r = self.parse_assign_expr()?;
5225 Ok(Expr {
5226 kind: ExprKind::CompoundAssign {
5227 target: Box::new(expr),
5228 op: BinOp::ShiftLeft,
5229 value: Box::new(r),
5230 },
5231 line,
5232 })
5233 }
5234 Token::ShiftRightAssign => {
5235 self.advance();
5236 let r = self.parse_assign_expr()?;
5237 Ok(Expr {
5238 kind: ExprKind::CompoundAssign {
5239 target: Box::new(expr),
5240 op: BinOp::ShiftRight,
5241 value: Box::new(r),
5242 },
5243 line,
5244 })
5245 }
5246 Token::OrAssign => {
5247 self.advance();
5248 let r = self.parse_assign_expr()?;
5249 Ok(Expr {
5250 kind: ExprKind::CompoundAssign {
5251 target: Box::new(expr),
5252 op: BinOp::LogOr,
5253 value: Box::new(r),
5254 },
5255 line,
5256 })
5257 }
5258 Token::DefinedOrAssign => {
5259 self.advance();
5260 let r = self.parse_assign_expr()?;
5261 Ok(Expr {
5262 kind: ExprKind::CompoundAssign {
5263 target: Box::new(expr),
5264 op: BinOp::DefinedOr,
5265 value: Box::new(r),
5266 },
5267 line,
5268 })
5269 }
5270 Token::AndAssign => {
5271 self.advance();
5272 let r = self.parse_assign_expr()?;
5273 Ok(Expr {
5274 kind: ExprKind::CompoundAssign {
5275 target: Box::new(expr),
5276 op: BinOp::LogAnd,
5277 value: Box::new(r),
5278 },
5279 line,
5280 })
5281 }
5282 _ => Ok(expr),
5283 }
5284 }
5285
5286 fn parse_ternary(&mut self) -> PerlResult<Expr> {
5287 let expr = self.parse_pipe_forward()?;
5288 if self.eat(&Token::Question) {
5289 let line = expr.line;
5290 let then_expr = self.parse_assign_expr()?;
5291 self.expect(&Token::Colon)?;
5292 let else_expr = self.parse_assign_expr()?;
5293 return Ok(Expr {
5294 kind: ExprKind::Ternary {
5295 condition: Box::new(expr),
5296 then_expr: Box::new(then_expr),
5297 else_expr: Box::new(else_expr),
5298 },
5299 line,
5300 });
5301 }
5302 Ok(expr)
5303 }
5304
5305 fn parse_pipe_forward(&mut self) -> PerlResult<Expr> {
5311 let mut left = self.parse_or_word()?;
5312 if self.no_pipe_forward_depth > 0 {
5318 return Ok(left);
5319 }
5320 while matches!(self.peek(), Token::PipeForward) {
5321 if crate::compat_mode() {
5322 return Err(self.syntax_err(
5323 "pipe-forward operator `|>` is a stryke extension (disabled by --compat)",
5324 left.line,
5325 ));
5326 }
5327 let line = left.line;
5328 self.advance();
5329 self.pipe_rhs_depth = self.pipe_rhs_depth.saturating_add(1);
5332 let right_result = self.parse_or_word();
5333 self.pipe_rhs_depth = self.pipe_rhs_depth.saturating_sub(1);
5334 let right = right_result?;
5335 left = self.pipe_forward_apply(left, right, line)?;
5336 }
5337 Ok(left)
5338 }
5339
5340 fn pipe_forward_apply(&self, lhs: Expr, rhs: Expr, line: usize) -> PerlResult<Expr> {
5362 let Expr { kind, line: rline } = rhs;
5363 let new_kind = match kind {
5364 ExprKind::FuncCall { name, mut args } => {
5366 match name.as_str() {
5367 "puniq" | "uniq" | "distinct" | "flatten" | "set" | "list_count"
5368 | "list_size" | "count" | "size" | "cnt" | "len" | "with_index" | "shuffle"
5369 | "shuffled" | "frequencies" | "freq" | "interleave" | "ddump"
5370 | "stringify" | "str" | "lines" | "words" | "chars" | "digits" | "letters"
5371 | "letters_uc" | "letters_lc" | "punctuation" | "numbers" | "graphemes"
5372 | "columns" | "sentences" | "paragraphs" | "sections" | "trim" | "avg"
5373 | "to_json" | "to_csv" | "to_toml" | "to_yaml" | "to_xml" | "to_html"
5374 | "to_markdown" | "to_table" | "xopen" | "clip" | "sparkline" | "bar_chart"
5375 | "flame" | "stddev" | "squared" | "sq" | "square" | "cubed" | "cb"
5376 | "cube" | "normalize" | "snake_case" | "camel_case" | "kebab_case" => {
5377 if args.is_empty() {
5378 args.push(lhs);
5379 } else {
5380 args[0] = lhs;
5381 }
5382 }
5383 "chunked" | "windowed" => {
5384 if args.is_empty() {
5385 return Err(self.syntax_err(
5386 "|>: chunked(N) / windowed(N) needs size — e.g. `@a |> windowed(2)`",
5387 line,
5388 ));
5389 }
5390 args.insert(0, lhs);
5391 }
5392 "List::Util::reduce" | "List::Util::fold" => {
5393 args.push(lhs);
5394 }
5395 "grep_v" | "pluck" | "tee" | "nth" | "chunk" => {
5396 args.push(lhs);
5402 }
5403 "enumerate" | "dedup" => {
5404 args.insert(0, lhs);
5407 }
5408 "clamp" => {
5409 args.push(lhs);
5411 }
5412 "pfirst" | "pany" | "any" | "all" | "none" | "first" | "take_while"
5413 | "drop_while" | "skip_while" | "reject" | "tap" | "peek" | "group_by"
5414 | "chunk_by" | "partition" | "min_by" | "max_by" | "zip_with" | "count_by" => {
5415 if args.len() < 2 {
5416 return Err(self.syntax_err(
5417 format!(
5418 "|>: `{name}` needs {{ BLOCK }}, LIST so the list can receive the pipe"
5419 ),
5420 line,
5421 ));
5422 }
5423 args[1] = lhs;
5424 }
5425 "take" | "head" | "tail" | "drop" | "List::Util::head" | "List::Util::tail" => {
5426 if args.is_empty() {
5427 return Err(self.syntax_err(
5428 "|>: `{name}` needs N last — e.g. `@a |> take(3)` for `take(@a, 3)`",
5429 line,
5430 ));
5431 }
5432 args.insert(0, lhs);
5434 }
5435 _ => {
5436 args.insert(0, lhs);
5437 }
5438 }
5439 ExprKind::FuncCall { name, args }
5440 }
5441 ExprKind::MethodCall {
5442 object,
5443 method,
5444 mut args,
5445 super_call,
5446 } => {
5447 args.insert(0, lhs);
5448 ExprKind::MethodCall {
5449 object,
5450 method,
5451 args,
5452 super_call,
5453 }
5454 }
5455 ExprKind::IndirectCall {
5456 target,
5457 mut args,
5458 ampersand,
5459 pass_caller_arglist: _,
5460 } => {
5461 args.insert(0, lhs);
5462 ExprKind::IndirectCall {
5463 target,
5464 args,
5465 ampersand,
5466 pass_caller_arglist: false,
5469 }
5470 }
5471
5472 ExprKind::Print { handle, mut args } => {
5474 args.insert(0, lhs);
5475 ExprKind::Print { handle, args }
5476 }
5477 ExprKind::Say { handle, mut args } => {
5478 args.insert(0, lhs);
5479 ExprKind::Say { handle, args }
5480 }
5481 ExprKind::Printf { handle, mut args } => {
5482 args.insert(0, lhs);
5483 ExprKind::Printf { handle, args }
5484 }
5485 ExprKind::Die(mut args) => {
5486 args.insert(0, lhs);
5487 ExprKind::Die(args)
5488 }
5489 ExprKind::Warn(mut args) => {
5490 args.insert(0, lhs);
5491 ExprKind::Warn(args)
5492 }
5493
5494 ExprKind::Sprintf { format, mut args } => {
5500 args.insert(0, lhs);
5501 ExprKind::Sprintf { format, args }
5502 }
5503
5504 ExprKind::System(mut args) => {
5506 args.insert(0, lhs);
5507 ExprKind::System(args)
5508 }
5509 ExprKind::Exec(mut args) => {
5510 args.insert(0, lhs);
5511 ExprKind::Exec(args)
5512 }
5513 ExprKind::Unlink(mut args) => {
5514 args.insert(0, lhs);
5515 ExprKind::Unlink(args)
5516 }
5517 ExprKind::Chmod(mut args) => {
5518 args.insert(0, lhs);
5519 ExprKind::Chmod(args)
5520 }
5521 ExprKind::Chown(mut args) => {
5522 args.insert(0, lhs);
5523 ExprKind::Chown(args)
5524 }
5525 ExprKind::Glob(mut args) => {
5526 args.insert(0, lhs);
5527 ExprKind::Glob(args)
5528 }
5529 ExprKind::Files(mut args) => {
5530 args.insert(0, lhs);
5531 ExprKind::Files(args)
5532 }
5533 ExprKind::Filesf(mut args) => {
5534 args.insert(0, lhs);
5535 ExprKind::Filesf(args)
5536 }
5537 ExprKind::FilesfRecursive(mut args) => {
5538 args.insert(0, lhs);
5539 ExprKind::FilesfRecursive(args)
5540 }
5541 ExprKind::Dirs(mut args) => {
5542 args.insert(0, lhs);
5543 ExprKind::Dirs(args)
5544 }
5545 ExprKind::DirsRecursive(mut args) => {
5546 args.insert(0, lhs);
5547 ExprKind::DirsRecursive(args)
5548 }
5549 ExprKind::SymLinks(mut args) => {
5550 args.insert(0, lhs);
5551 ExprKind::SymLinks(args)
5552 }
5553 ExprKind::Sockets(mut args) => {
5554 args.insert(0, lhs);
5555 ExprKind::Sockets(args)
5556 }
5557 ExprKind::Pipes(mut args) => {
5558 args.insert(0, lhs);
5559 ExprKind::Pipes(args)
5560 }
5561 ExprKind::BlockDevices(mut args) => {
5562 args.insert(0, lhs);
5563 ExprKind::BlockDevices(args)
5564 }
5565 ExprKind::CharDevices(mut args) => {
5566 args.insert(0, lhs);
5567 ExprKind::CharDevices(args)
5568 }
5569 ExprKind::GlobPar { mut args, progress } => {
5570 args.insert(0, lhs);
5571 ExprKind::GlobPar { args, progress }
5572 }
5573 ExprKind::ParSed { mut args, progress } => {
5574 args.insert(0, lhs);
5575 ExprKind::ParSed { args, progress }
5576 }
5577
5578 ExprKind::Length(_) => ExprKind::Length(Box::new(lhs)),
5580 ExprKind::Abs(_) => ExprKind::Abs(Box::new(lhs)),
5581 ExprKind::Int(_) => ExprKind::Int(Box::new(lhs)),
5582 ExprKind::Sqrt(_) => ExprKind::Sqrt(Box::new(lhs)),
5583 ExprKind::Sin(_) => ExprKind::Sin(Box::new(lhs)),
5584 ExprKind::Cos(_) => ExprKind::Cos(Box::new(lhs)),
5585 ExprKind::Exp(_) => ExprKind::Exp(Box::new(lhs)),
5586 ExprKind::Log(_) => ExprKind::Log(Box::new(lhs)),
5587 ExprKind::Hex(_) => ExprKind::Hex(Box::new(lhs)),
5588 ExprKind::Oct(_) => ExprKind::Oct(Box::new(lhs)),
5589 ExprKind::Lc(_) => ExprKind::Lc(Box::new(lhs)),
5590 ExprKind::Uc(_) => ExprKind::Uc(Box::new(lhs)),
5591 ExprKind::Lcfirst(_) => ExprKind::Lcfirst(Box::new(lhs)),
5592 ExprKind::Ucfirst(_) => ExprKind::Ucfirst(Box::new(lhs)),
5593 ExprKind::Fc(_) => ExprKind::Fc(Box::new(lhs)),
5594 ExprKind::Chr(_) => ExprKind::Chr(Box::new(lhs)),
5595 ExprKind::Ord(_) => ExprKind::Ord(Box::new(lhs)),
5596 ExprKind::Chomp(_) => ExprKind::Chomp(Box::new(lhs)),
5597 ExprKind::Chop(_) => ExprKind::Chop(Box::new(lhs)),
5598 ExprKind::Defined(_) => ExprKind::Defined(Box::new(lhs)),
5599 ExprKind::Ref(_) => ExprKind::Ref(Box::new(lhs)),
5600 ExprKind::ScalarContext(_) => ExprKind::ScalarContext(Box::new(lhs)),
5601 ExprKind::Keys(_) => ExprKind::Keys(Box::new(lhs)),
5602 ExprKind::Values(_) => ExprKind::Values(Box::new(lhs)),
5603 ExprKind::Each(_) => ExprKind::Each(Box::new(lhs)),
5604 ExprKind::Pop(_) => ExprKind::Pop(Box::new(lhs)),
5605 ExprKind::Shift(_) => ExprKind::Shift(Box::new(lhs)),
5606 ExprKind::Delete(_) => ExprKind::Delete(Box::new(lhs)),
5607 ExprKind::Exists(_) => ExprKind::Exists(Box::new(lhs)),
5608 ExprKind::ReverseExpr(_) => ExprKind::ReverseExpr(Box::new(lhs)),
5609 ExprKind::ScalarReverse(_) => ExprKind::ScalarReverse(Box::new(lhs)),
5610 ExprKind::Slurp(_) => ExprKind::Slurp(Box::new(lhs)),
5611 ExprKind::Capture(_) => ExprKind::Capture(Box::new(lhs)),
5612 ExprKind::Qx(_) => ExprKind::Qx(Box::new(lhs)),
5613 ExprKind::FetchUrl(_) => ExprKind::FetchUrl(Box::new(lhs)),
5614 ExprKind::Close(_) => ExprKind::Close(Box::new(lhs)),
5615 ExprKind::Chdir(_) => ExprKind::Chdir(Box::new(lhs)),
5616 ExprKind::Readdir(_) => ExprKind::Readdir(Box::new(lhs)),
5617 ExprKind::Closedir(_) => ExprKind::Closedir(Box::new(lhs)),
5618 ExprKind::Rewinddir(_) => ExprKind::Rewinddir(Box::new(lhs)),
5619 ExprKind::Telldir(_) => ExprKind::Telldir(Box::new(lhs)),
5620 ExprKind::Stat(_) => ExprKind::Stat(Box::new(lhs)),
5621 ExprKind::Lstat(_) => ExprKind::Lstat(Box::new(lhs)),
5622 ExprKind::Readlink(_) => ExprKind::Readlink(Box::new(lhs)),
5623 ExprKind::Study(_) => ExprKind::Study(Box::new(lhs)),
5624 ExprKind::Await(_) => ExprKind::Await(Box::new(lhs)),
5625 ExprKind::Eval(_) => ExprKind::Eval(Box::new(lhs)),
5626 ExprKind::Rand(_) => ExprKind::Rand(Some(Box::new(lhs))),
5627 ExprKind::Srand(_) => ExprKind::Srand(Some(Box::new(lhs))),
5628 ExprKind::Pos(_) => ExprKind::Pos(Some(Box::new(lhs))),
5629 ExprKind::Exit(_) => ExprKind::Exit(Some(Box::new(lhs))),
5630
5631 ExprKind::MapExpr {
5633 block,
5634 list: _,
5635 flatten_array_refs,
5636 stream,
5637 } => ExprKind::MapExpr {
5638 block,
5639 list: Box::new(lhs),
5640 flatten_array_refs,
5641 stream,
5642 },
5643 ExprKind::MapExprComma {
5644 expr,
5645 list: _,
5646 flatten_array_refs,
5647 stream,
5648 } => ExprKind::MapExprComma {
5649 expr,
5650 list: Box::new(lhs),
5651 flatten_array_refs,
5652 stream,
5653 },
5654 ExprKind::GrepExpr {
5655 block,
5656 list: _,
5657 keyword,
5658 } => ExprKind::GrepExpr {
5659 block,
5660 list: Box::new(lhs),
5661 keyword,
5662 },
5663 ExprKind::GrepExprComma {
5664 expr,
5665 list: _,
5666 keyword,
5667 } => ExprKind::GrepExprComma {
5668 expr,
5669 list: Box::new(lhs),
5670 keyword,
5671 },
5672 ExprKind::ForEachExpr { block, list: _ } => ExprKind::ForEachExpr {
5673 block,
5674 list: Box::new(lhs),
5675 },
5676 ExprKind::SortExpr { cmp, list: _ } => ExprKind::SortExpr {
5677 cmp,
5678 list: Box::new(lhs),
5679 },
5680 ExprKind::JoinExpr { separator, list: _ } => ExprKind::JoinExpr {
5681 separator,
5682 list: Box::new(lhs),
5683 },
5684 ExprKind::ReduceExpr { block, list: _ } => ExprKind::ReduceExpr {
5685 block,
5686 list: Box::new(lhs),
5687 },
5688 ExprKind::PMapExpr {
5689 block,
5690 list: _,
5691 progress,
5692 flat_outputs,
5693 on_cluster,
5694 stream,
5695 } => ExprKind::PMapExpr {
5696 block,
5697 list: Box::new(lhs),
5698 progress,
5699 flat_outputs,
5700 on_cluster,
5701 stream,
5702 },
5703 ExprKind::PMapChunkedExpr {
5704 chunk_size,
5705 block,
5706 list: _,
5707 progress,
5708 } => ExprKind::PMapChunkedExpr {
5709 chunk_size,
5710 block,
5711 list: Box::new(lhs),
5712 progress,
5713 },
5714 ExprKind::PGrepExpr {
5715 block,
5716 list: _,
5717 progress,
5718 stream,
5719 } => ExprKind::PGrepExpr {
5720 block,
5721 list: Box::new(lhs),
5722 progress,
5723 stream,
5724 },
5725 ExprKind::PForExpr {
5726 block,
5727 list: _,
5728 progress,
5729 } => ExprKind::PForExpr {
5730 block,
5731 list: Box::new(lhs),
5732 progress,
5733 },
5734 ExprKind::PSortExpr {
5735 cmp,
5736 list: _,
5737 progress,
5738 } => ExprKind::PSortExpr {
5739 cmp,
5740 list: Box::new(lhs),
5741 progress,
5742 },
5743 ExprKind::PReduceExpr {
5744 block,
5745 list: _,
5746 progress,
5747 } => ExprKind::PReduceExpr {
5748 block,
5749 list: Box::new(lhs),
5750 progress,
5751 },
5752 ExprKind::PcacheExpr {
5753 block,
5754 list: _,
5755 progress,
5756 } => ExprKind::PcacheExpr {
5757 block,
5758 list: Box::new(lhs),
5759 progress,
5760 },
5761 ExprKind::PReduceInitExpr {
5762 init,
5763 block,
5764 list: _,
5765 progress,
5766 } => ExprKind::PReduceInitExpr {
5767 init,
5768 block,
5769 list: Box::new(lhs),
5770 progress,
5771 },
5772 ExprKind::PMapReduceExpr {
5773 map_block,
5774 reduce_block,
5775 list: _,
5776 progress,
5777 } => ExprKind::PMapReduceExpr {
5778 map_block,
5779 reduce_block,
5780 list: Box::new(lhs),
5781 progress,
5782 },
5783
5784 ExprKind::Push { array, mut values } => {
5789 values.insert(0, lhs);
5790 ExprKind::Push { array, values }
5791 }
5792 ExprKind::Unshift { array, mut values } => {
5793 values.insert(0, lhs);
5794 ExprKind::Unshift { array, values }
5795 }
5796
5797 ExprKind::SplitExpr {
5799 pattern,
5800 string: _,
5801 limit,
5802 } => ExprKind::SplitExpr {
5803 pattern,
5804 string: Box::new(lhs),
5805 limit,
5806 },
5807
5808 ExprKind::Substitution {
5812 pattern,
5813 replacement,
5814 mut flags,
5815 expr: _,
5816 delim,
5817 } => {
5818 if !flags.contains('r') {
5819 flags.push('r');
5820 }
5821 ExprKind::Substitution {
5822 expr: Box::new(lhs),
5823 pattern,
5824 replacement,
5825 flags,
5826 delim,
5827 }
5828 }
5829 ExprKind::Transliterate {
5830 from,
5831 to,
5832 mut flags,
5833 expr: _,
5834 delim,
5835 } => {
5836 if !flags.contains('r') {
5837 flags.push('r');
5838 }
5839 ExprKind::Transliterate {
5840 expr: Box::new(lhs),
5841 from,
5842 to,
5843 flags,
5844 delim,
5845 }
5846 }
5847 ExprKind::Match {
5848 pattern,
5849 flags,
5850 scalar_g,
5851 expr: _,
5852 delim,
5853 } => ExprKind::Match {
5854 expr: Box::new(lhs),
5855 pattern,
5856 flags,
5857 scalar_g,
5858 delim,
5859 },
5860 ExprKind::Regex(pattern, flags) => ExprKind::Match {
5862 expr: Box::new(lhs),
5863 pattern,
5864 flags,
5865 scalar_g: false,
5866 delim: '/',
5867 },
5868
5869 ExprKind::Bareword(name) => match name.as_str() {
5871 "rv" | "reverse" | "reversed" => ExprKind::ReverseExpr(Box::new(lhs)),
5872 "rev" => ExprKind::ScalarReverse(Box::new(lhs)),
5873 "uq" | "uniq" | "distinct" => ExprKind::FuncCall {
5874 name: "uniq".to_string(),
5875 args: vec![lhs],
5876 },
5877 "fl" | "flatten" => ExprKind::FuncCall {
5878 name: "flatten".to_string(),
5879 args: vec![lhs],
5880 },
5881 _ => ExprKind::FuncCall {
5882 name,
5883 args: vec![lhs],
5884 },
5885 },
5886
5887 kind @ (ExprKind::ScalarVar(_)
5889 | ExprKind::ArrayElement { .. }
5890 | ExprKind::HashElement { .. }
5891 | ExprKind::Deref { .. }
5892 | ExprKind::ArrowDeref { .. }
5893 | ExprKind::CodeRef { .. }
5894 | ExprKind::SubroutineRef(_)
5895 | ExprKind::SubroutineCodeRef(_)
5896 | ExprKind::DynamicSubCodeRef(_)) => ExprKind::IndirectCall {
5897 target: Box::new(Expr { kind, line: rline }),
5898 args: vec![lhs],
5899 ampersand: false,
5900 pass_caller_arglist: false,
5901 },
5902
5903 ExprKind::Do(inner) if matches!(inner.kind, ExprKind::CodeRef { .. }) => {
5907 ExprKind::IndirectCall {
5908 target: inner,
5909 args: vec![lhs],
5910 ampersand: false,
5911 pass_caller_arglist: false,
5912 }
5913 }
5914
5915 other => {
5916 return Err(self.syntax_err(
5917 format!(
5918 "right-hand side of `|>` must be a call, builtin, or coderef \
5919 expression (got {})",
5920 Self::expr_kind_name(&other)
5921 ),
5922 line,
5923 ));
5924 }
5925 };
5926 Ok(Expr {
5927 kind: new_kind,
5928 line,
5929 })
5930 }
5931
5932 fn expr_kind_name(kind: &ExprKind) -> &'static str {
5934 match kind {
5935 ExprKind::Integer(_) | ExprKind::Float(_) => "numeric literal",
5936 ExprKind::String(_) | ExprKind::InterpolatedString(_) => "string literal",
5937 ExprKind::BinOp { .. } => "binary expression",
5938 ExprKind::UnaryOp { .. } => "unary expression",
5939 ExprKind::Ternary { .. } => "ternary expression",
5940 ExprKind::Assign { .. } | ExprKind::CompoundAssign { .. } => "assignment",
5941 ExprKind::List(_) => "list expression",
5942 ExprKind::Range { .. } => "range expression",
5943 _ => "expression",
5944 }
5945 }
5946
5947 fn parse_or_word(&mut self) -> PerlResult<Expr> {
5949 let mut left = self.parse_and_word()?;
5950 while matches!(self.peek(), Token::LogOrWord) {
5951 let line = left.line;
5952 self.advance();
5953 let right = self.parse_and_word()?;
5954 left = Expr {
5955 kind: ExprKind::BinOp {
5956 left: Box::new(left),
5957 op: BinOp::LogOrWord,
5958 right: Box::new(right),
5959 },
5960 line,
5961 };
5962 }
5963 Ok(left)
5964 }
5965
5966 fn parse_and_word(&mut self) -> PerlResult<Expr> {
5967 let mut left = self.parse_not_word()?;
5968 while matches!(self.peek(), Token::LogAndWord) {
5969 let line = left.line;
5970 self.advance();
5971 let right = self.parse_not_word()?;
5972 left = Expr {
5973 kind: ExprKind::BinOp {
5974 left: Box::new(left),
5975 op: BinOp::LogAndWord,
5976 right: Box::new(right),
5977 },
5978 line,
5979 };
5980 }
5981 Ok(left)
5982 }
5983
5984 fn parse_not_word(&mut self) -> PerlResult<Expr> {
5985 if matches!(self.peek(), Token::LogNotWord) {
5986 let line = self.peek_line();
5987 self.advance();
5988 let expr = self.parse_not_word()?;
5989 return Ok(Expr {
5990 kind: ExprKind::UnaryOp {
5991 op: UnaryOp::LogNotWord,
5992 expr: Box::new(expr),
5993 },
5994 line,
5995 });
5996 }
5997 self.parse_range()
5998 }
5999
6000 fn parse_log_or(&mut self) -> PerlResult<Expr> {
6001 let mut left = self.parse_log_and()?;
6002 loop {
6003 let op = match self.peek() {
6004 Token::LogOr => BinOp::LogOr,
6005 Token::DefinedOr => BinOp::DefinedOr,
6006 _ => break,
6007 };
6008 let line = left.line;
6009 self.advance();
6010 let right = self.parse_log_and()?;
6011 left = Expr {
6012 kind: ExprKind::BinOp {
6013 left: Box::new(left),
6014 op,
6015 right: Box::new(right),
6016 },
6017 line,
6018 };
6019 }
6020 Ok(left)
6021 }
6022
6023 fn parse_log_and(&mut self) -> PerlResult<Expr> {
6024 let mut left = self.parse_bit_or()?;
6025 while matches!(self.peek(), Token::LogAnd) {
6026 let line = left.line;
6027 self.advance();
6028 let right = self.parse_bit_or()?;
6029 left = Expr {
6030 kind: ExprKind::BinOp {
6031 left: Box::new(left),
6032 op: BinOp::LogAnd,
6033 right: Box::new(right),
6034 },
6035 line,
6036 };
6037 }
6038 Ok(left)
6039 }
6040
6041 fn parse_bit_or(&mut self) -> PerlResult<Expr> {
6042 let mut left = self.parse_bit_xor()?;
6043 while matches!(self.peek(), Token::BitOr) {
6044 let line = left.line;
6045 self.advance();
6046 let right = self.parse_bit_xor()?;
6047 left = Expr {
6048 kind: ExprKind::BinOp {
6049 left: Box::new(left),
6050 op: BinOp::BitOr,
6051 right: Box::new(right),
6052 },
6053 line,
6054 };
6055 }
6056 Ok(left)
6057 }
6058
6059 fn parse_bit_xor(&mut self) -> PerlResult<Expr> {
6060 let mut left = self.parse_bit_and()?;
6061 while matches!(self.peek(), Token::BitXor) {
6062 let line = left.line;
6063 self.advance();
6064 let right = self.parse_bit_and()?;
6065 left = Expr {
6066 kind: ExprKind::BinOp {
6067 left: Box::new(left),
6068 op: BinOp::BitXor,
6069 right: Box::new(right),
6070 },
6071 line,
6072 };
6073 }
6074 Ok(left)
6075 }
6076
6077 fn parse_bit_and(&mut self) -> PerlResult<Expr> {
6078 let mut left = self.parse_equality()?;
6079 while matches!(self.peek(), Token::BitAnd) {
6080 let line = left.line;
6081 self.advance();
6082 let right = self.parse_equality()?;
6083 left = Expr {
6084 kind: ExprKind::BinOp {
6085 left: Box::new(left),
6086 op: BinOp::BitAnd,
6087 right: Box::new(right),
6088 },
6089 line,
6090 };
6091 }
6092 Ok(left)
6093 }
6094
6095 fn parse_equality(&mut self) -> PerlResult<Expr> {
6096 let mut left = self.parse_comparison()?;
6097 loop {
6098 let op = match self.peek() {
6099 Token::NumEq => BinOp::NumEq,
6100 Token::NumNe => BinOp::NumNe,
6101 Token::StrEq => BinOp::StrEq,
6102 Token::StrNe => BinOp::StrNe,
6103 Token::Spaceship => BinOp::Spaceship,
6104 Token::StrCmp => BinOp::StrCmp,
6105 _ => break,
6106 };
6107 let line = left.line;
6108 self.advance();
6109 let right = self.parse_comparison()?;
6110 left = Expr {
6111 kind: ExprKind::BinOp {
6112 left: Box::new(left),
6113 op,
6114 right: Box::new(right),
6115 },
6116 line,
6117 };
6118 }
6119 Ok(left)
6120 }
6121
6122 fn parse_comparison(&mut self) -> PerlResult<Expr> {
6123 let mut left = self.parse_shift()?;
6124 loop {
6125 let op = match self.peek() {
6126 Token::NumLt => BinOp::NumLt,
6127 Token::NumGt => BinOp::NumGt,
6128 Token::NumLe => BinOp::NumLe,
6129 Token::NumGe => BinOp::NumGe,
6130 Token::StrLt => BinOp::StrLt,
6131 Token::StrGt => BinOp::StrGt,
6132 Token::StrLe => BinOp::StrLe,
6133 Token::StrGe => BinOp::StrGe,
6134 _ => break,
6135 };
6136 let line = left.line;
6137 self.advance();
6138 let right = self.parse_shift()?;
6139 left = Expr {
6140 kind: ExprKind::BinOp {
6141 left: Box::new(left),
6142 op,
6143 right: Box::new(right),
6144 },
6145 line,
6146 };
6147 }
6148 Ok(left)
6149 }
6150
6151 fn parse_shift(&mut self) -> PerlResult<Expr> {
6152 let mut left = self.parse_addition()?;
6153 loop {
6154 let op = match self.peek() {
6155 Token::ShiftLeft => BinOp::ShiftLeft,
6156 Token::ShiftRight => BinOp::ShiftRight,
6157 _ => break,
6158 };
6159 let line = left.line;
6160 self.advance();
6161 let right = self.parse_addition()?;
6162 left = Expr {
6163 kind: ExprKind::BinOp {
6164 left: Box::new(left),
6165 op,
6166 right: Box::new(right),
6167 },
6168 line,
6169 };
6170 }
6171 Ok(left)
6172 }
6173
6174 fn parse_addition(&mut self) -> PerlResult<Expr> {
6175 let mut left = self.parse_multiplication()?;
6176 loop {
6177 let op = match self.peek() {
6180 Token::Plus if self.peek_line() == self.prev_line() => BinOp::Add,
6181 Token::Minus if self.peek_line() == self.prev_line() => BinOp::Sub,
6182 Token::Dot => BinOp::Concat,
6183 _ => break,
6184 };
6185 let line = left.line;
6186 self.advance();
6187 let right = self.parse_multiplication()?;
6188 left = Expr {
6189 kind: ExprKind::BinOp {
6190 left: Box::new(left),
6191 op,
6192 right: Box::new(right),
6193 },
6194 line,
6195 };
6196 }
6197 Ok(left)
6198 }
6199
6200 fn parse_multiplication(&mut self) -> PerlResult<Expr> {
6201 let mut left = self.parse_regex_bind()?;
6202 loop {
6203 let op = match self.peek() {
6204 Token::Star => BinOp::Mul,
6205 Token::Slash if self.suppress_slash_as_div == 0 => BinOp::Div,
6206 Token::Percent if self.peek_line() == self.prev_line() => BinOp::Mod,
6209 Token::X => {
6210 let line = left.line;
6211 self.advance();
6212 let right = self.parse_regex_bind()?;
6213 left = Expr {
6214 kind: ExprKind::Repeat {
6215 expr: Box::new(left),
6216 count: Box::new(right),
6217 },
6218 line,
6219 };
6220 continue;
6221 }
6222 _ => break,
6223 };
6224 let line = left.line;
6225 self.advance();
6226 let right = self.parse_regex_bind()?;
6227 left = Expr {
6228 kind: ExprKind::BinOp {
6229 left: Box::new(left),
6230 op,
6231 right: Box::new(right),
6232 },
6233 line,
6234 };
6235 }
6236 Ok(left)
6237 }
6238
6239 fn parse_regex_bind(&mut self) -> PerlResult<Expr> {
6240 let left = self.parse_unary()?;
6241 match self.peek() {
6242 Token::BindMatch => {
6243 let line = left.line;
6244 self.advance();
6245 match self.peek().clone() {
6246 Token::Regex(pattern, flags, delim) => {
6247 self.advance();
6248 Ok(Expr {
6249 kind: ExprKind::Match {
6250 expr: Box::new(left),
6251 pattern,
6252 flags,
6253 scalar_g: false,
6254 delim,
6255 },
6256 line,
6257 })
6258 }
6259 Token::Ident(ref s) if s.starts_with('\x00') => {
6260 let (Token::Ident(encoded), _) = self.advance() else {
6261 unreachable!()
6262 };
6263 let parts: Vec<&str> = encoded.split('\x00').collect();
6264 if parts.len() >= 4 && parts[1] == "s" {
6265 let delim = parts.get(5).and_then(|s| s.chars().next()).unwrap_or('/');
6266 Ok(Expr {
6267 kind: ExprKind::Substitution {
6268 expr: Box::new(left),
6269 pattern: parts[2].to_string(),
6270 replacement: parts[3].to_string(),
6271 flags: parts.get(4).unwrap_or(&"").to_string(),
6272 delim,
6273 },
6274 line,
6275 })
6276 } else if parts.len() >= 4 && parts[1] == "tr" {
6277 let delim = parts.get(5).and_then(|s| s.chars().next()).unwrap_or('/');
6278 Ok(Expr {
6279 kind: ExprKind::Transliterate {
6280 expr: Box::new(left),
6281 from: parts[2].to_string(),
6282 to: parts[3].to_string(),
6283 flags: parts.get(4).unwrap_or(&"").to_string(),
6284 delim,
6285 },
6286 line,
6287 })
6288 } else {
6289 Err(self.syntax_err("Invalid regex binding", line))
6290 }
6291 }
6292 _ => {
6293 let rhs = self.parse_unary()?;
6294 Ok(Expr {
6295 kind: ExprKind::BinOp {
6296 left: Box::new(left),
6297 op: BinOp::BindMatch,
6298 right: Box::new(rhs),
6299 },
6300 line,
6301 })
6302 }
6303 }
6304 }
6305 Token::BindNotMatch => {
6306 let line = left.line;
6307 self.advance();
6308 match self.peek().clone() {
6309 Token::Regex(pattern, flags, delim) => {
6310 self.advance();
6311 Ok(Expr {
6312 kind: ExprKind::UnaryOp {
6313 op: UnaryOp::LogNot,
6314 expr: Box::new(Expr {
6315 kind: ExprKind::Match {
6316 expr: Box::new(left),
6317 pattern,
6318 flags,
6319 scalar_g: false,
6320 delim,
6321 },
6322 line,
6323 }),
6324 },
6325 line,
6326 })
6327 }
6328 Token::Ident(ref s) if s.starts_with('\x00') => {
6329 let (Token::Ident(encoded), _) = self.advance() else {
6330 unreachable!()
6331 };
6332 let parts: Vec<&str> = encoded.split('\x00').collect();
6333 if parts.len() >= 4 && parts[1] == "s" {
6334 let delim = parts.get(5).and_then(|s| s.chars().next()).unwrap_or('/');
6335 Ok(Expr {
6336 kind: ExprKind::UnaryOp {
6337 op: UnaryOp::LogNot,
6338 expr: Box::new(Expr {
6339 kind: ExprKind::Substitution {
6340 expr: Box::new(left),
6341 pattern: parts[2].to_string(),
6342 replacement: parts[3].to_string(),
6343 flags: parts.get(4).unwrap_or(&"").to_string(),
6344 delim,
6345 },
6346 line,
6347 }),
6348 },
6349 line,
6350 })
6351 } else if parts.len() >= 4 && parts[1] == "tr" {
6352 let delim = parts.get(5).and_then(|s| s.chars().next()).unwrap_or('/');
6353 Ok(Expr {
6354 kind: ExprKind::UnaryOp {
6355 op: UnaryOp::LogNot,
6356 expr: Box::new(Expr {
6357 kind: ExprKind::Transliterate {
6358 expr: Box::new(left),
6359 from: parts[2].to_string(),
6360 to: parts[3].to_string(),
6361 flags: parts.get(4).unwrap_or(&"").to_string(),
6362 delim,
6363 },
6364 line,
6365 }),
6366 },
6367 line,
6368 })
6369 } else {
6370 Err(self.syntax_err("Invalid regex binding after !~", line))
6371 }
6372 }
6373 _ => {
6374 let rhs = self.parse_unary()?;
6375 Ok(Expr {
6376 kind: ExprKind::BinOp {
6377 left: Box::new(left),
6378 op: BinOp::BindNotMatch,
6379 right: Box::new(rhs),
6380 },
6381 line,
6382 })
6383 }
6384 }
6385 }
6386 _ => Ok(left),
6387 }
6388 }
6389
6390 fn parse_thread_input(&mut self) -> PerlResult<Expr> {
6393 self.suppress_slash_as_div = self.suppress_slash_as_div.saturating_add(1);
6394 let result = self.parse_range();
6395 self.suppress_slash_as_div = self.suppress_slash_as_div.saturating_sub(1);
6396 result
6397 }
6398
6399 fn parse_range(&mut self) -> PerlResult<Expr> {
6406 let left = self.parse_log_or()?;
6407 let line = left.line;
6408 let exclusive = if self.eat(&Token::RangeExclusive) {
6409 true
6410 } else if self.eat(&Token::Range) {
6411 false
6412 } else {
6413 return Ok(left);
6414 };
6415 let right = self.parse_log_or()?;
6416 Ok(Expr {
6417 kind: ExprKind::Range {
6418 from: Box::new(left),
6419 to: Box::new(right),
6420 exclusive,
6421 },
6422 line,
6423 })
6424 }
6425
6426 fn parse_package_qualified_identifier(&mut self) -> PerlResult<String> {
6428 let mut name = match self.advance() {
6429 (Token::Ident(n), _) => n,
6430 (tok, l) => {
6431 return Err(self.syntax_err(format!("Expected identifier, got {:?}", tok), l));
6432 }
6433 };
6434 while self.eat(&Token::PackageSep) {
6435 match self.advance() {
6436 (Token::Ident(part), _) => {
6437 name.push_str("::");
6438 name.push_str(&part);
6439 }
6440 (tok, l) => {
6441 return Err(self
6442 .syntax_err(format!("Expected identifier after `::`, got {:?}", tok), l));
6443 }
6444 }
6445 }
6446 Ok(name)
6447 }
6448
6449 fn parse_qualified_subroutine_name(&mut self) -> PerlResult<String> {
6451 self.parse_package_qualified_identifier()
6452 }
6453
6454 fn parse_unary(&mut self) -> PerlResult<Expr> {
6455 let line = self.peek_line();
6456 match self.peek().clone() {
6457 Token::Minus => {
6458 self.advance();
6459 let expr = self.parse_power()?;
6460 Ok(Expr {
6461 kind: ExprKind::UnaryOp {
6462 op: UnaryOp::Negate,
6463 expr: Box::new(expr),
6464 },
6465 line,
6466 })
6467 }
6468 Token::Plus => {
6471 self.advance();
6472 self.parse_unary()
6473 }
6474 Token::LogNot => {
6475 self.advance();
6476 let expr = self.parse_unary()?;
6477 Ok(Expr {
6478 kind: ExprKind::UnaryOp {
6479 op: UnaryOp::LogNot,
6480 expr: Box::new(expr),
6481 },
6482 line,
6483 })
6484 }
6485 Token::BitNot => {
6486 self.advance();
6487 let expr = self.parse_unary()?;
6488 Ok(Expr {
6489 kind: ExprKind::UnaryOp {
6490 op: UnaryOp::BitNot,
6491 expr: Box::new(expr),
6492 },
6493 line,
6494 })
6495 }
6496 Token::Increment => {
6497 self.advance();
6498 let expr = self.parse_postfix()?;
6499 Ok(Expr {
6500 kind: ExprKind::UnaryOp {
6501 op: UnaryOp::PreIncrement,
6502 expr: Box::new(expr),
6503 },
6504 line,
6505 })
6506 }
6507 Token::Decrement => {
6508 self.advance();
6509 let expr = self.parse_postfix()?;
6510 Ok(Expr {
6511 kind: ExprKind::UnaryOp {
6512 op: UnaryOp::PreDecrement,
6513 expr: Box::new(expr),
6514 },
6515 line,
6516 })
6517 }
6518 Token::BitAnd => {
6519 self.advance();
6522 if matches!(self.peek(), Token::LBrace) {
6523 self.advance();
6524 let inner = self.parse_expression()?;
6525 self.expect(&Token::RBrace)?;
6526 return Ok(Expr {
6527 kind: ExprKind::DynamicSubCodeRef(Box::new(inner)),
6528 line,
6529 });
6530 }
6531 if matches!(self.peek(), Token::Ident(_)) {
6532 let name = self.parse_qualified_subroutine_name()?;
6533 return Ok(Expr {
6534 kind: ExprKind::SubroutineRef(name),
6535 line,
6536 });
6537 }
6538 let target = self.parse_primary()?;
6539 if matches!(self.peek(), Token::LParen) {
6540 self.advance();
6541 let args = self.parse_arg_list()?;
6542 self.expect(&Token::RParen)?;
6543 return Ok(Expr {
6544 kind: ExprKind::IndirectCall {
6545 target: Box::new(target),
6546 args,
6547 ampersand: true,
6548 pass_caller_arglist: false,
6549 },
6550 line,
6551 });
6552 }
6553 Ok(Expr {
6555 kind: ExprKind::IndirectCall {
6556 target: Box::new(target),
6557 args: vec![],
6558 ampersand: true,
6559 pass_caller_arglist: true,
6560 },
6561 line,
6562 })
6563 }
6564 Token::Backslash => {
6565 self.advance();
6566 let expr = self.parse_unary()?;
6567 if let ExprKind::SubroutineRef(name) = expr.kind {
6568 return Ok(Expr {
6569 kind: ExprKind::SubroutineCodeRef(name),
6570 line,
6571 });
6572 }
6573 if matches!(expr.kind, ExprKind::DynamicSubCodeRef(_)) {
6574 return Ok(expr);
6575 }
6576 Ok(Expr {
6578 kind: ExprKind::ScalarRef(Box::new(expr)),
6579 line,
6580 })
6581 }
6582 Token::FileTest(op) => {
6583 self.advance();
6584 let expr = if Self::filetest_allows_implicit_topic(self.peek()) {
6586 Expr {
6587 kind: ExprKind::ScalarVar("_".into()),
6588 line: self.peek_line(),
6589 }
6590 } else {
6591 self.parse_unary()?
6592 };
6593 Ok(Expr {
6594 kind: ExprKind::FileTest {
6595 op,
6596 expr: Box::new(expr),
6597 },
6598 line,
6599 })
6600 }
6601 _ => self.parse_power(),
6602 }
6603 }
6604
6605 fn parse_power(&mut self) -> PerlResult<Expr> {
6606 let left = self.parse_postfix()?;
6607 if matches!(self.peek(), Token::Power) {
6608 let line = left.line;
6609 self.advance();
6610 let right = self.parse_unary()?; return Ok(Expr {
6612 kind: ExprKind::BinOp {
6613 left: Box::new(left),
6614 op: BinOp::Pow,
6615 right: Box::new(right),
6616 },
6617 line,
6618 });
6619 }
6620 Ok(left)
6621 }
6622
6623 fn parse_postfix(&mut self) -> PerlResult<Expr> {
6624 let mut expr = self.parse_primary()?;
6625 loop {
6626 match self.peek().clone() {
6627 Token::Increment => {
6628 if self.peek_line() > self.prev_line() {
6631 break;
6632 }
6633 let line = expr.line;
6634 self.advance();
6635 expr = Expr {
6636 kind: ExprKind::PostfixOp {
6637 expr: Box::new(expr),
6638 op: PostfixOp::Increment,
6639 },
6640 line,
6641 };
6642 }
6643 Token::Decrement => {
6644 if self.peek_line() > self.prev_line() {
6647 break;
6648 }
6649 let line = expr.line;
6650 self.advance();
6651 expr = Expr {
6652 kind: ExprKind::PostfixOp {
6653 expr: Box::new(expr),
6654 op: PostfixOp::Decrement,
6655 },
6656 line,
6657 };
6658 }
6659 Token::LParen => {
6660 if self.suppress_indirect_paren_call > 0 {
6661 break;
6662 }
6663 if self.peek_line() > self.prev_line() {
6667 break;
6668 }
6669 let line = expr.line;
6670 self.advance();
6671 let args = self.parse_arg_list()?;
6672 self.expect(&Token::RParen)?;
6673 expr = Expr {
6674 kind: ExprKind::IndirectCall {
6675 target: Box::new(expr),
6676 args,
6677 ampersand: false,
6678 pass_caller_arglist: false,
6679 },
6680 line,
6681 };
6682 }
6683 Token::Arrow => {
6684 let line = expr.line;
6685 self.advance();
6686 match self.peek().clone() {
6687 Token::LBracket => {
6688 self.advance();
6689 let index = self.parse_expression()?;
6690 self.expect(&Token::RBracket)?;
6691 expr = Expr {
6692 kind: ExprKind::ArrowDeref {
6693 expr: Box::new(expr),
6694 index: Box::new(index),
6695 kind: DerefKind::Array,
6696 },
6697 line,
6698 };
6699 }
6700 Token::LBrace => {
6701 self.advance();
6702 let key = self.parse_hash_subscript_key()?;
6703 self.expect(&Token::RBrace)?;
6704 expr = Expr {
6705 kind: ExprKind::ArrowDeref {
6706 expr: Box::new(expr),
6707 index: Box::new(key),
6708 kind: DerefKind::Hash,
6709 },
6710 line,
6711 };
6712 }
6713 Token::LParen => {
6714 self.advance();
6715 let args = self.parse_arg_list()?;
6716 self.expect(&Token::RParen)?;
6717 expr = Expr {
6718 kind: ExprKind::ArrowDeref {
6719 expr: Box::new(expr),
6720 index: Box::new(Expr {
6721 kind: ExprKind::List(args),
6722 line,
6723 }),
6724 kind: DerefKind::Call,
6725 },
6726 line,
6727 };
6728 }
6729 Token::Ident(method) => {
6730 self.advance();
6731 if method == "SUPER" {
6732 self.expect(&Token::PackageSep)?;
6733 let real_method = match self.advance() {
6734 (Token::Ident(n), _) => n,
6735 (tok, l) => {
6736 return Err(self.syntax_err(
6737 format!(
6738 "Expected method name after SUPER::, got {:?}",
6739 tok
6740 ),
6741 l,
6742 ));
6743 }
6744 };
6745 let args = if self.eat(&Token::LParen) {
6746 let a = self.parse_arg_list()?;
6747 self.expect(&Token::RParen)?;
6748 a
6749 } else {
6750 self.parse_method_arg_list_no_paren()?
6751 };
6752 expr = Expr {
6753 kind: ExprKind::MethodCall {
6754 object: Box::new(expr),
6755 method: real_method,
6756 args,
6757 super_call: true,
6758 },
6759 line,
6760 };
6761 } else {
6762 let mut method_name = method;
6763 while self.eat(&Token::PackageSep) {
6764 match self.advance() {
6765 (Token::Ident(part), _) => {
6766 method_name.push_str("::");
6767 method_name.push_str(&part);
6768 }
6769 (tok, l) => {
6770 return Err(self.syntax_err(
6771 format!(
6772 "Expected identifier after :: in method name, got {:?}",
6773 tok
6774 ),
6775 l,
6776 ));
6777 }
6778 }
6779 }
6780 let args = if self.eat(&Token::LParen) {
6781 let a = self.parse_arg_list()?;
6782 self.expect(&Token::RParen)?;
6783 a
6784 } else {
6785 self.parse_method_arg_list_no_paren()?
6786 };
6787 expr = Expr {
6788 kind: ExprKind::MethodCall {
6789 object: Box::new(expr),
6790 method: method_name,
6791 args,
6792 super_call: false,
6793 },
6794 line,
6795 };
6796 }
6797 }
6798 Token::ArrayAt => {
6804 self.advance(); match self.peek().clone() {
6806 Token::Star => {
6807 self.advance();
6808 expr = Expr {
6809 kind: ExprKind::Deref {
6810 expr: Box::new(expr),
6811 kind: Sigil::Array,
6812 },
6813 line,
6814 };
6815 }
6816 Token::LBracket => {
6817 self.advance();
6818 let mut indices = Vec::new();
6819 while !matches!(self.peek(), Token::RBracket | Token::Eof) {
6820 indices.push(self.parse_assign_expr()?);
6821 if !self.eat(&Token::Comma) {
6822 break;
6823 }
6824 }
6825 self.expect(&Token::RBracket)?;
6826 let source = Expr {
6827 kind: ExprKind::Deref {
6828 expr: Box::new(expr),
6829 kind: Sigil::Array,
6830 },
6831 line,
6832 };
6833 expr = Expr {
6834 kind: ExprKind::AnonymousListSlice {
6835 source: Box::new(source),
6836 indices,
6837 },
6838 line,
6839 };
6840 }
6841 Token::LBrace => {
6842 self.advance();
6843 let mut keys = Vec::new();
6844 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
6845 keys.push(self.parse_assign_expr()?);
6846 if !self.eat(&Token::Comma) {
6847 break;
6848 }
6849 }
6850 self.expect(&Token::RBrace)?;
6851 expr = Expr {
6852 kind: ExprKind::HashSliceDeref {
6853 container: Box::new(expr),
6854 keys,
6855 },
6856 line,
6857 };
6858 }
6859 tok => {
6860 return Err(self.syntax_err(
6861 format!(
6862 "Expected `*`, `[…]`, or `{{…}}` after `->@`, got {:?}",
6863 tok
6864 ),
6865 line,
6866 ));
6867 }
6868 }
6869 }
6870 Token::HashPercent => {
6871 self.advance(); match self.peek().clone() {
6873 Token::Star => {
6874 self.advance();
6875 expr = Expr {
6876 kind: ExprKind::Deref {
6877 expr: Box::new(expr),
6878 kind: Sigil::Hash,
6879 },
6880 line,
6881 };
6882 }
6883 tok => {
6884 return Err(self.syntax_err(
6885 format!("Expected `*` after `->%`, got {:?}", tok),
6886 line,
6887 ));
6888 }
6889 }
6890 }
6891 Token::X => {
6893 self.advance();
6894 let args = if self.eat(&Token::LParen) {
6895 let a = self.parse_arg_list()?;
6896 self.expect(&Token::RParen)?;
6897 a
6898 } else {
6899 self.parse_method_arg_list_no_paren()?
6900 };
6901 expr = Expr {
6902 kind: ExprKind::MethodCall {
6903 object: Box::new(expr),
6904 method: "x".to_string(),
6905 args,
6906 super_call: false,
6907 },
6908 line,
6909 };
6910 }
6911 _ => break,
6912 }
6913 }
6914 Token::LBracket => {
6915 let line = expr.line;
6917 if matches!(expr.kind, ExprKind::ScalarVar(_)) {
6918 if let ExprKind::ScalarVar(ref name) = expr.kind {
6919 let name = name.clone();
6920 self.advance();
6921 let index = self.parse_expression()?;
6922 self.expect(&Token::RBracket)?;
6923 expr = Expr {
6924 kind: ExprKind::ArrayElement {
6925 array: name,
6926 index: Box::new(index),
6927 },
6928 line,
6929 };
6930 }
6931 } else if postfix_lbracket_is_arrow_container(&expr) {
6932 self.advance();
6933 let indices = self.parse_arg_list()?;
6934 self.expect(&Token::RBracket)?;
6935 expr = Expr {
6936 kind: ExprKind::ArrowDeref {
6937 expr: Box::new(expr),
6938 index: Box::new(Expr {
6939 kind: ExprKind::List(indices),
6940 line,
6941 }),
6942 kind: DerefKind::Array,
6943 },
6944 line,
6945 };
6946 } else {
6947 self.advance();
6948 let indices = self.parse_arg_list()?;
6949 self.expect(&Token::RBracket)?;
6950 expr = Expr {
6951 kind: ExprKind::AnonymousListSlice {
6952 source: Box::new(expr),
6953 indices,
6954 },
6955 line,
6956 };
6957 }
6958 }
6959 Token::LBrace => {
6960 if self.suppress_scalar_hash_brace > 0 {
6961 break;
6962 }
6963 let line = expr.line;
6966 let is_scalar_named_hash = matches!(expr.kind, ExprKind::ScalarVar(_));
6967 let is_chainable_hash_subscript = is_scalar_named_hash
6968 || matches!(
6969 expr.kind,
6970 ExprKind::HashElement { .. }
6971 | ExprKind::ArrayElement { .. }
6972 | ExprKind::ArrowDeref { .. }
6973 | ExprKind::Deref {
6974 kind: Sigil::Scalar,
6975 ..
6976 }
6977 );
6978 if !is_chainable_hash_subscript {
6979 break;
6980 }
6981 self.advance();
6982 let key = self.parse_hash_subscript_key()?;
6983 self.expect(&Token::RBrace)?;
6984 expr = if is_scalar_named_hash {
6985 if let ExprKind::ScalarVar(ref name) = expr.kind {
6986 let name = name.clone();
6987 if name == "_" {
6989 Expr {
6990 kind: ExprKind::ArrowDeref {
6991 expr: Box::new(Expr {
6992 kind: ExprKind::ScalarVar("_".into()),
6993 line,
6994 }),
6995 index: Box::new(key),
6996 kind: DerefKind::Hash,
6997 },
6998 line,
6999 }
7000 } else {
7001 Expr {
7002 kind: ExprKind::HashElement {
7003 hash: name,
7004 key: Box::new(key),
7005 },
7006 line,
7007 }
7008 }
7009 } else {
7010 unreachable!("is_scalar_named_hash implies ScalarVar");
7011 }
7012 } else {
7013 Expr {
7014 kind: ExprKind::ArrowDeref {
7015 expr: Box::new(expr),
7016 index: Box::new(key),
7017 kind: DerefKind::Hash,
7018 },
7019 line,
7020 }
7021 };
7022 }
7023 _ => break,
7024 }
7025 }
7026 Ok(expr)
7027 }
7028
7029 fn parse_primary(&mut self) -> PerlResult<Expr> {
7030 let line = self.peek_line();
7031 if let Token::Ident(ref kw) = self.peek().clone() {
7036 if matches!(kw.as_str(), "my" | "our" | "state" | "local") {
7037 let kw_owned = kw.clone();
7038 let saved_pos = self.pos;
7043 let stmt = self.parse_my_our_local(&kw_owned, false)?;
7044 let decls = match stmt.kind {
7045 StmtKind::My(d)
7046 | StmtKind::Our(d)
7047 | StmtKind::State(d)
7048 | StmtKind::Local(d) => d,
7049 _ => {
7050 self.pos = saved_pos;
7055 return Err(self.syntax_err(
7056 "`my`/`our`/`local` in expression must declare variables",
7057 line,
7058 ));
7059 }
7060 };
7061 return Ok(Expr {
7062 kind: ExprKind::MyExpr {
7063 keyword: kw_owned,
7064 decls,
7065 },
7066 line,
7067 });
7068 }
7069 }
7070 match self.peek().clone() {
7071 Token::Integer(n) => {
7072 self.advance();
7073 Ok(Expr {
7074 kind: ExprKind::Integer(n),
7075 line,
7076 })
7077 }
7078 Token::Float(f) => {
7079 self.advance();
7080 Ok(Expr {
7081 kind: ExprKind::Float(f),
7082 line,
7083 })
7084 }
7085 Token::ArrowBrace => {
7091 self.advance();
7092 let mut stmts = Vec::new();
7093 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
7094 if self.eat(&Token::Semicolon) {
7095 continue;
7096 }
7097 stmts.push(self.parse_statement()?);
7098 }
7099 self.expect(&Token::RBrace)?;
7100 let inner_line = stmts.first().map(|s| s.line).unwrap_or(line);
7101 let inner = Expr {
7102 kind: ExprKind::CodeRef {
7103 params: vec![],
7104 body: stmts,
7105 },
7106 line: inner_line,
7107 };
7108 Ok(Expr {
7109 kind: ExprKind::Do(Box::new(inner)),
7110 line,
7111 })
7112 }
7113 Token::Star => {
7114 self.advance();
7115 if matches!(self.peek(), Token::LBrace) {
7116 self.advance();
7117 let inner = self.parse_expression()?;
7118 self.expect(&Token::RBrace)?;
7119 return Ok(Expr {
7120 kind: ExprKind::Deref {
7121 expr: Box::new(inner),
7122 kind: Sigil::Typeglob,
7123 },
7124 line,
7125 });
7126 }
7127 if matches!(
7129 self.peek(),
7130 Token::ScalarVar(_)
7131 | Token::ArrayVar(_)
7132 | Token::HashVar(_)
7133 | Token::DerefScalarVar(_)
7134 | Token::HashPercent
7135 ) {
7136 let inner = self.parse_postfix()?;
7137 return Ok(Expr {
7138 kind: ExprKind::TypeglobExpr(Box::new(inner)),
7139 line,
7140 });
7141 }
7142 let mut full_name = match self.advance() {
7144 (Token::Ident(n), _) => n,
7145 (Token::X, _) => "x".to_string(),
7146 (tok, l) => {
7147 return Err(self
7148 .syntax_err(format!("Expected identifier after *, got {:?}", tok), l));
7149 }
7150 };
7151 while self.eat(&Token::PackageSep) {
7152 match self.advance() {
7153 (Token::Ident(part), _) => {
7154 full_name = format!("{}::{}", full_name, part);
7155 }
7156 (Token::X, _) => {
7157 full_name = format!("{}::x", full_name);
7158 }
7159 (tok, l) => {
7160 return Err(self.syntax_err(
7161 format!("Expected identifier after :: in typeglob, got {:?}", tok),
7162 l,
7163 ));
7164 }
7165 }
7166 }
7167 Ok(Expr {
7168 kind: ExprKind::Typeglob(full_name),
7169 line,
7170 })
7171 }
7172 Token::SingleString(s) => {
7173 self.advance();
7174 Ok(Expr {
7175 kind: ExprKind::String(s),
7176 line,
7177 })
7178 }
7179 Token::DoubleString(s) => {
7180 self.advance();
7181 self.parse_interpolated_string(&s, line)
7182 }
7183 Token::BacktickString(s) => {
7184 self.advance();
7185 let inner = self.parse_interpolated_string(&s, line)?;
7186 Ok(Expr {
7187 kind: ExprKind::Qx(Box::new(inner)),
7188 line,
7189 })
7190 }
7191 Token::HereDoc(_, body, interpolate) => {
7192 self.advance();
7193 if interpolate {
7194 self.parse_interpolated_string(&body, line)
7195 } else {
7196 Ok(Expr {
7197 kind: ExprKind::String(body),
7198 line,
7199 })
7200 }
7201 }
7202 Token::Regex(pattern, flags, _delim) => {
7203 self.advance();
7204 Ok(Expr {
7205 kind: ExprKind::Regex(pattern, flags),
7206 line,
7207 })
7208 }
7209 Token::QW(words) => {
7210 self.advance();
7211 Ok(Expr {
7212 kind: ExprKind::QW(words),
7213 line,
7214 })
7215 }
7216 Token::DerefScalarVar(name) => {
7217 self.advance();
7218 Ok(Expr {
7219 kind: ExprKind::Deref {
7220 expr: Box::new(Expr {
7221 kind: ExprKind::ScalarVar(name),
7222 line,
7223 }),
7224 kind: Sigil::Scalar,
7225 },
7226 line,
7227 })
7228 }
7229 Token::ScalarVar(name) => {
7230 self.advance();
7231 Ok(Expr {
7232 kind: ExprKind::ScalarVar(name),
7233 line,
7234 })
7235 }
7236 Token::ArrayVar(name) => {
7237 self.advance();
7238 match self.peek() {
7240 Token::LBracket => {
7241 self.advance();
7242 let indices = self.parse_arg_list()?;
7243 self.expect(&Token::RBracket)?;
7244 Ok(Expr {
7245 kind: ExprKind::ArraySlice {
7246 array: name,
7247 indices,
7248 },
7249 line,
7250 })
7251 }
7252 Token::LBrace if self.suppress_scalar_hash_brace == 0 => {
7253 self.advance();
7254 let keys = self.parse_arg_list()?;
7255 self.expect(&Token::RBrace)?;
7256 Ok(Expr {
7257 kind: ExprKind::HashSlice { hash: name, keys },
7258 line,
7259 })
7260 }
7261 _ => Ok(Expr {
7262 kind: ExprKind::ArrayVar(name),
7263 line,
7264 }),
7265 }
7266 }
7267 Token::HashVar(name) => {
7268 self.advance();
7269 Ok(Expr {
7270 kind: ExprKind::HashVar(name),
7271 line,
7272 })
7273 }
7274 Token::HashPercent => {
7275 self.advance();
7277 if matches!(self.peek(), Token::ScalarVar(_)) {
7278 let n = match self.advance() {
7279 (Token::ScalarVar(n), _) => n,
7280 (tok, l) => {
7281 return Err(self.syntax_err(
7282 format!("Expected scalar variable after %%, got {:?}", tok),
7283 l,
7284 ));
7285 }
7286 };
7287 return Ok(Expr {
7288 kind: ExprKind::Deref {
7289 expr: Box::new(Expr {
7290 kind: ExprKind::ScalarVar(n),
7291 line,
7292 }),
7293 kind: Sigil::Hash,
7294 },
7295 line,
7296 });
7297 }
7298 if matches!(self.peek(), Token::LBracket) {
7303 self.advance();
7304 let pairs = self.parse_hashref_pairs_until(&Token::RBracket)?;
7305 self.expect(&Token::RBracket)?;
7306 let href = Expr {
7307 kind: ExprKind::HashRef(pairs),
7308 line,
7309 };
7310 return Ok(Expr {
7311 kind: ExprKind::Deref {
7312 expr: Box::new(href),
7313 kind: Sigil::Hash,
7314 },
7315 line,
7316 });
7317 }
7318 self.expect(&Token::LBrace)?;
7319 let looks_like_pair = matches!(
7325 self.peek(),
7326 Token::Ident(_) | Token::SingleString(_) | Token::DoubleString(_)
7327 ) && matches!(self.peek_at(1), Token::FatArrow);
7328 let inner = if looks_like_pair {
7329 let pairs = self.parse_hashref_pairs_until(&Token::RBrace)?;
7330 Expr {
7331 kind: ExprKind::HashRef(pairs),
7332 line,
7333 }
7334 } else {
7335 self.parse_expression()?
7336 };
7337 self.expect(&Token::RBrace)?;
7338 Ok(Expr {
7339 kind: ExprKind::Deref {
7340 expr: Box::new(inner),
7341 kind: Sigil::Hash,
7342 },
7343 line,
7344 })
7345 }
7346 Token::ArrayAt => {
7347 self.advance();
7348 if matches!(self.peek(), Token::LBrace) {
7350 self.advance();
7351 let inner = self.parse_expression()?;
7352 self.expect(&Token::RBrace)?;
7353 return Ok(Expr {
7354 kind: ExprKind::Deref {
7355 expr: Box::new(inner),
7356 kind: Sigil::Array,
7357 },
7358 line,
7359 });
7360 }
7361 if matches!(self.peek(), Token::LBracket) {
7365 self.advance();
7366 let mut elems = Vec::new();
7367 if !matches!(self.peek(), Token::RBracket) {
7368 elems.push(self.parse_assign_expr()?);
7369 while self.eat(&Token::Comma) {
7370 if matches!(self.peek(), Token::RBracket) {
7371 break;
7372 }
7373 elems.push(self.parse_assign_expr()?);
7374 }
7375 }
7376 self.expect(&Token::RBracket)?;
7377 let aref = Expr {
7378 kind: ExprKind::ArrayRef(elems),
7379 line,
7380 };
7381 return Ok(Expr {
7382 kind: ExprKind::Deref {
7383 expr: Box::new(aref),
7384 kind: Sigil::Array,
7385 },
7386 line,
7387 });
7388 }
7389 let container = match self.peek().clone() {
7391 Token::ScalarVar(n) => {
7392 self.advance();
7393 Expr {
7394 kind: ExprKind::ScalarVar(n),
7395 line,
7396 }
7397 }
7398 _ => {
7399 return Err(self.syntax_err(
7400 "Expected `$name`, `{`, or `[` after `@` (e.g. `@$aref`, `@{expr}`, `@[1,2,3]`, or `@$href{keys}`)",
7401 line,
7402 ));
7403 }
7404 };
7405 if matches!(self.peek(), Token::LBrace) {
7406 self.advance();
7407 let keys = self.parse_arg_list()?;
7408 self.expect(&Token::RBrace)?;
7409 return Ok(Expr {
7410 kind: ExprKind::HashSliceDeref {
7411 container: Box::new(container),
7412 keys,
7413 },
7414 line,
7415 });
7416 }
7417 Ok(Expr {
7418 kind: ExprKind::Deref {
7419 expr: Box::new(container),
7420 kind: Sigil::Array,
7421 },
7422 line,
7423 })
7424 }
7425 Token::LParen => {
7426 self.advance();
7427 if matches!(self.peek(), Token::RParen) {
7428 self.advance();
7429 return Ok(Expr {
7430 kind: ExprKind::List(vec![]),
7431 line,
7432 });
7433 }
7434 let expr = self.parse_expression()?;
7435 self.expect(&Token::RParen)?;
7436 Ok(expr)
7437 }
7438 Token::LBracket => {
7439 self.advance();
7440 let elems = self.parse_arg_list()?;
7441 self.expect(&Token::RBracket)?;
7442 Ok(Expr {
7443 kind: ExprKind::ArrayRef(elems),
7444 line,
7445 })
7446 }
7447 Token::LBrace => {
7448 self.advance();
7450 let saved = self.pos;
7452 match self.try_parse_hash_ref() {
7453 Ok(pairs) => Ok(Expr {
7454 kind: ExprKind::HashRef(pairs),
7455 line,
7456 }),
7457 Err(_) => {
7458 self.pos = saved;
7459 let mut stmts = Vec::new();
7461 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
7462 if self.eat(&Token::Semicolon) {
7463 continue;
7464 }
7465 stmts.push(self.parse_statement()?);
7466 }
7467 self.expect(&Token::RBrace)?;
7468 Ok(Expr {
7469 kind: ExprKind::CodeRef {
7470 params: vec![],
7471 body: stmts,
7472 },
7473 line,
7474 })
7475 }
7476 }
7477 }
7478 Token::Diamond => {
7479 self.advance();
7480 Ok(Expr {
7481 kind: ExprKind::ReadLine(None),
7482 line,
7483 })
7484 }
7485 Token::ReadLine(handle) => {
7486 self.advance();
7487 Ok(Expr {
7488 kind: ExprKind::ReadLine(Some(handle)),
7489 line,
7490 })
7491 }
7492
7493 Token::ThreadArrow => {
7495 self.advance();
7496 self.parse_thread_macro(line)
7497 }
7498 Token::Ident(ref name) => {
7499 let name = name.clone();
7500 if name.starts_with('\x00') {
7502 self.advance();
7503 let parts: Vec<&str> = name.split('\x00').collect();
7504 if parts.len() >= 4 && parts[1] == "s" {
7505 let delim = parts.get(5).and_then(|s| s.chars().next()).unwrap_or('/');
7506 return Ok(Expr {
7507 kind: ExprKind::Substitution {
7508 expr: Box::new(Expr {
7509 kind: ExprKind::ScalarVar("_".into()),
7510 line,
7511 }),
7512 pattern: parts[2].to_string(),
7513 replacement: parts[3].to_string(),
7514 flags: parts.get(4).unwrap_or(&"").to_string(),
7515 delim,
7516 },
7517 line,
7518 });
7519 }
7520 if parts.len() >= 4 && parts[1] == "tr" {
7521 let delim = parts.get(5).and_then(|s| s.chars().next()).unwrap_or('/');
7522 return Ok(Expr {
7523 kind: ExprKind::Transliterate {
7524 expr: Box::new(Expr {
7525 kind: ExprKind::ScalarVar("_".into()),
7526 line,
7527 }),
7528 from: parts[2].to_string(),
7529 to: parts[3].to_string(),
7530 flags: parts.get(4).unwrap_or(&"").to_string(),
7531 delim,
7532 },
7533 line,
7534 });
7535 }
7536 return Err(self.syntax_err("Unexpected encoded token", line));
7537 }
7538 self.parse_named_expr(name)
7539 }
7540
7541 Token::Percent => {
7544 self.advance();
7545 match self.peek().clone() {
7546 Token::Ident(name) => {
7547 self.advance();
7548 Ok(Expr {
7549 kind: ExprKind::HashVar(name),
7550 line,
7551 })
7552 }
7553 Token::ScalarVar(n) => {
7554 self.advance();
7555 Ok(Expr {
7556 kind: ExprKind::Deref {
7557 expr: Box::new(Expr {
7558 kind: ExprKind::ScalarVar(n),
7559 line,
7560 }),
7561 kind: Sigil::Hash,
7562 },
7563 line,
7564 })
7565 }
7566 Token::LBrace => {
7567 self.advance();
7568 let looks_like_pair = matches!(
7569 self.peek(),
7570 Token::Ident(_) | Token::SingleString(_) | Token::DoubleString(_)
7571 ) && matches!(self.peek_at(1), Token::FatArrow);
7572 let inner = if looks_like_pair {
7573 let pairs = self.parse_hashref_pairs_until(&Token::RBrace)?;
7574 Expr {
7575 kind: ExprKind::HashRef(pairs),
7576 line,
7577 }
7578 } else {
7579 self.parse_expression()?
7580 };
7581 self.expect(&Token::RBrace)?;
7582 Ok(Expr {
7583 kind: ExprKind::Deref {
7584 expr: Box::new(inner),
7585 kind: Sigil::Hash,
7586 },
7587 line,
7588 })
7589 }
7590 Token::LBracket => {
7591 self.advance();
7592 let pairs = self.parse_hashref_pairs_until(&Token::RBracket)?;
7593 self.expect(&Token::RBracket)?;
7594 let href = Expr {
7595 kind: ExprKind::HashRef(pairs),
7596 line,
7597 };
7598 Ok(Expr {
7599 kind: ExprKind::Deref {
7600 expr: Box::new(href),
7601 kind: Sigil::Hash,
7602 },
7603 line,
7604 })
7605 }
7606 tok => Err(self.syntax_err(
7607 format!(
7608 "Expected identifier, `$`, `{{`, or `[` after `%`, got {:?}",
7609 tok
7610 ),
7611 line,
7612 )),
7613 }
7614 }
7615
7616 tok => Err(self.syntax_err(format!("Unexpected token {:?}", tok), line)),
7617 }
7618 }
7619
7620 fn parse_named_expr(&mut self, mut name: String) -> PerlResult<Expr> {
7621 let line = self.peek_line();
7622 self.advance(); while self.eat(&Token::PackageSep) {
7624 match self.advance() {
7625 (Token::Ident(part), _) => {
7626 name = format!("{}::{}", name, part);
7627 }
7628 (tok, err_line) => {
7629 return Err(self.syntax_err(
7630 format!("Expected identifier after `::`, got {:?}", tok),
7631 err_line,
7632 ));
7633 }
7634 }
7635 }
7636
7637 if matches!(self.peek(), Token::FatArrow) {
7641 return Ok(Expr {
7642 kind: ExprKind::String(name),
7643 line,
7644 });
7645 }
7646
7647 if crate::compat_mode() {
7648 if let Some(ext) = Self::stryke_extension_name(&name) {
7649 if !self.declared_subs.contains(&name) {
7650 return Err(self.syntax_err(
7651 format!("`{ext}` is a stryke extension (disabled by --compat)"),
7652 line,
7653 ));
7654 }
7655 }
7656 }
7657
7658 match name.as_str() {
7659 "__FILE__" => Ok(Expr {
7660 kind: ExprKind::MagicConst(MagicConstKind::File),
7661 line,
7662 }),
7663 "__LINE__" => Ok(Expr {
7664 kind: ExprKind::MagicConst(MagicConstKind::Line),
7665 line,
7666 }),
7667 "__SUB__" => Ok(Expr {
7668 kind: ExprKind::MagicConst(MagicConstKind::Sub),
7669 line,
7670 }),
7671 "stdin" => Ok(Expr {
7672 kind: ExprKind::FuncCall {
7673 name: "stdin".into(),
7674 args: vec![],
7675 },
7676 line,
7677 }),
7678 "range" => {
7679 let args = self.parse_builtin_args()?;
7680 Ok(Expr {
7681 kind: ExprKind::FuncCall {
7682 name: "range".into(),
7683 args,
7684 },
7685 line,
7686 })
7687 }
7688 "print" | "pr" => self.parse_print_like(|h, a| ExprKind::Print { handle: h, args: a }),
7689 "say" | "p" => self.parse_print_like(|h, a| ExprKind::Say { handle: h, args: a }),
7690 "printf" => self.parse_print_like(|h, a| ExprKind::Printf { handle: h, args: a }),
7691 "die" => {
7692 let args = self.parse_list_until_terminator()?;
7693 Ok(Expr {
7694 kind: ExprKind::Die(args),
7695 line,
7696 })
7697 }
7698 "warn" => {
7699 let args = self.parse_list_until_terminator()?;
7700 Ok(Expr {
7701 kind: ExprKind::Warn(args),
7702 line,
7703 })
7704 }
7705 "croak" | "confess" => {
7710 let args = self.parse_list_until_terminator()?;
7711 Ok(Expr {
7712 kind: ExprKind::Die(args),
7713 line,
7714 })
7715 }
7716 "carp" | "cluck" => {
7718 let args = self.parse_list_until_terminator()?;
7719 Ok(Expr {
7720 kind: ExprKind::Warn(args),
7721 line,
7722 })
7723 }
7724 "chomp" => {
7725 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
7726 return Ok(e);
7727 }
7728 let a = self.parse_one_arg_or_default()?;
7729 Ok(Expr {
7730 kind: ExprKind::Chomp(Box::new(a)),
7731 line,
7732 })
7733 }
7734 "chop" => {
7735 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
7736 return Ok(e);
7737 }
7738 let a = self.parse_one_arg_or_default()?;
7739 Ok(Expr {
7740 kind: ExprKind::Chop(Box::new(a)),
7741 line,
7742 })
7743 }
7744 "length" => {
7745 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
7746 return Ok(e);
7747 }
7748 let a = self.parse_one_arg_or_default()?;
7749 Ok(Expr {
7750 kind: ExprKind::Length(Box::new(a)),
7751 line,
7752 })
7753 }
7754 "defined" => {
7755 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
7756 return Ok(e);
7757 }
7758 let a = self.parse_one_arg_or_default()?;
7759 Ok(Expr {
7760 kind: ExprKind::Defined(Box::new(a)),
7761 line,
7762 })
7763 }
7764 "ref" => {
7765 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
7766 return Ok(e);
7767 }
7768 let a = self.parse_one_arg_or_default()?;
7769 Ok(Expr {
7770 kind: ExprKind::Ref(Box::new(a)),
7771 line,
7772 })
7773 }
7774 "undef" => {
7775 if self.peek_line() == self.prev_line()
7778 && matches!(
7779 self.peek(),
7780 Token::ScalarVar(_) | Token::ArrayVar(_) | Token::HashVar(_)
7781 )
7782 {
7783 let target = self.parse_primary()?;
7784 return Ok(Expr {
7785 kind: ExprKind::Assign {
7786 target: Box::new(target),
7787 value: Box::new(Expr {
7788 kind: ExprKind::Undef,
7789 line,
7790 }),
7791 },
7792 line,
7793 });
7794 }
7795 Ok(Expr {
7796 kind: ExprKind::Undef,
7797 line,
7798 })
7799 }
7800 "scalar" => {
7801 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
7802 return Ok(e);
7803 }
7804 let a = self.parse_one_arg_or_default()?;
7805 Ok(Expr {
7806 kind: ExprKind::ScalarContext(Box::new(a)),
7807 line,
7808 })
7809 }
7810 "abs" => {
7811 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
7812 return Ok(e);
7813 }
7814 let a = self.parse_one_arg_or_default()?;
7815 Ok(Expr {
7816 kind: ExprKind::Abs(Box::new(a)),
7817 line,
7818 })
7819 }
7820 "inc" | "dec" => {
7825 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
7826 return Ok(e);
7827 }
7828 let a = self.parse_one_arg_or_default()?;
7829 Ok(Expr {
7830 kind: ExprKind::FuncCall {
7831 name,
7832 args: vec![a],
7833 },
7834 line,
7835 })
7836 }
7837 "int" => {
7838 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
7839 return Ok(e);
7840 }
7841 let a = self.parse_one_arg_or_default()?;
7842 Ok(Expr {
7843 kind: ExprKind::Int(Box::new(a)),
7844 line,
7845 })
7846 }
7847 "sqrt" => {
7848 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
7849 return Ok(e);
7850 }
7851 let a = self.parse_one_arg_or_default()?;
7852 Ok(Expr {
7853 kind: ExprKind::Sqrt(Box::new(a)),
7854 line,
7855 })
7856 }
7857 "sin" => {
7858 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
7859 return Ok(e);
7860 }
7861 let a = self.parse_one_arg_or_default()?;
7862 Ok(Expr {
7863 kind: ExprKind::Sin(Box::new(a)),
7864 line,
7865 })
7866 }
7867 "cos" => {
7868 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
7869 return Ok(e);
7870 }
7871 let a = self.parse_one_arg_or_default()?;
7872 Ok(Expr {
7873 kind: ExprKind::Cos(Box::new(a)),
7874 line,
7875 })
7876 }
7877 "atan2" => {
7878 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
7879 return Ok(e);
7880 }
7881 let args = self.parse_builtin_args()?;
7882 if args.len() != 2 {
7883 return Err(self.syntax_err("atan2 requires two arguments", line));
7884 }
7885 Ok(Expr {
7886 kind: ExprKind::Atan2 {
7887 y: Box::new(args[0].clone()),
7888 x: Box::new(args[1].clone()),
7889 },
7890 line,
7891 })
7892 }
7893 "exp" => {
7894 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
7895 return Ok(e);
7896 }
7897 let a = self.parse_one_arg_or_default()?;
7898 Ok(Expr {
7899 kind: ExprKind::Exp(Box::new(a)),
7900 line,
7901 })
7902 }
7903 "log" => {
7904 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
7905 return Ok(e);
7906 }
7907 let a = self.parse_one_arg_or_default()?;
7908 Ok(Expr {
7909 kind: ExprKind::Log(Box::new(a)),
7910 line,
7911 })
7912 }
7913 "input" => {
7914 let args = if matches!(
7915 self.peek(),
7916 Token::Semicolon
7917 | Token::RBrace
7918 | Token::RParen
7919 | Token::Eof
7920 | Token::Comma
7921 | Token::PipeForward
7922 ) {
7923 vec![]
7924 } else if matches!(self.peek(), Token::LParen) {
7925 self.advance();
7926 if matches!(self.peek(), Token::RParen) {
7927 self.advance();
7928 vec![]
7929 } else {
7930 let a = self.parse_expression()?;
7931 self.expect(&Token::RParen)?;
7932 vec![a]
7933 }
7934 } else {
7935 let a = self.parse_one_arg()?;
7936 vec![a]
7937 };
7938 Ok(Expr {
7939 kind: ExprKind::FuncCall {
7940 name: "input".to_string(),
7941 args,
7942 },
7943 line,
7944 })
7945 }
7946 "rand" => {
7947 if matches!(
7948 self.peek(),
7949 Token::Semicolon
7950 | Token::RBrace
7951 | Token::RParen
7952 | Token::Eof
7953 | Token::Comma
7954 | Token::PipeForward
7955 ) {
7956 Ok(Expr {
7957 kind: ExprKind::Rand(None),
7958 line,
7959 })
7960 } else if matches!(self.peek(), Token::LParen) {
7961 self.advance();
7962 if matches!(self.peek(), Token::RParen) {
7963 self.advance();
7964 Ok(Expr {
7965 kind: ExprKind::Rand(None),
7966 line,
7967 })
7968 } else {
7969 let a = self.parse_expression()?;
7970 self.expect(&Token::RParen)?;
7971 Ok(Expr {
7972 kind: ExprKind::Rand(Some(Box::new(a))),
7973 line,
7974 })
7975 }
7976 } else {
7977 let a = self.parse_one_arg()?;
7978 Ok(Expr {
7979 kind: ExprKind::Rand(Some(Box::new(a))),
7980 line,
7981 })
7982 }
7983 }
7984 "srand" => {
7985 if matches!(
7986 self.peek(),
7987 Token::Semicolon
7988 | Token::RBrace
7989 | Token::RParen
7990 | Token::Eof
7991 | Token::Comma
7992 | Token::PipeForward
7993 ) {
7994 Ok(Expr {
7995 kind: ExprKind::Srand(None),
7996 line,
7997 })
7998 } else if matches!(self.peek(), Token::LParen) {
7999 self.advance();
8000 if matches!(self.peek(), Token::RParen) {
8001 self.advance();
8002 Ok(Expr {
8003 kind: ExprKind::Srand(None),
8004 line,
8005 })
8006 } else {
8007 let a = self.parse_expression()?;
8008 self.expect(&Token::RParen)?;
8009 Ok(Expr {
8010 kind: ExprKind::Srand(Some(Box::new(a))),
8011 line,
8012 })
8013 }
8014 } else {
8015 let a = self.parse_one_arg()?;
8016 Ok(Expr {
8017 kind: ExprKind::Srand(Some(Box::new(a))),
8018 line,
8019 })
8020 }
8021 }
8022 "hex" => {
8023 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8024 return Ok(e);
8025 }
8026 let a = self.parse_one_arg_or_default()?;
8027 Ok(Expr {
8028 kind: ExprKind::Hex(Box::new(a)),
8029 line,
8030 })
8031 }
8032 "oct" => {
8033 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8034 return Ok(e);
8035 }
8036 let a = self.parse_one_arg_or_default()?;
8037 Ok(Expr {
8038 kind: ExprKind::Oct(Box::new(a)),
8039 line,
8040 })
8041 }
8042 "chr" => {
8043 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8044 return Ok(e);
8045 }
8046 let a = self.parse_one_arg_or_default()?;
8047 Ok(Expr {
8048 kind: ExprKind::Chr(Box::new(a)),
8049 line,
8050 })
8051 }
8052 "ord" => {
8053 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8054 return Ok(e);
8055 }
8056 let a = self.parse_one_arg_or_default()?;
8057 Ok(Expr {
8058 kind: ExprKind::Ord(Box::new(a)),
8059 line,
8060 })
8061 }
8062 "lc" => {
8063 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8064 return Ok(e);
8065 }
8066 let a = self.parse_one_arg_or_default()?;
8067 Ok(Expr {
8068 kind: ExprKind::Lc(Box::new(a)),
8069 line,
8070 })
8071 }
8072 "uc" => {
8073 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8074 return Ok(e);
8075 }
8076 let a = self.parse_one_arg_or_default()?;
8077 Ok(Expr {
8078 kind: ExprKind::Uc(Box::new(a)),
8079 line,
8080 })
8081 }
8082 "lcfirst" => {
8083 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8084 return Ok(e);
8085 }
8086 let a = self.parse_one_arg_or_default()?;
8087 Ok(Expr {
8088 kind: ExprKind::Lcfirst(Box::new(a)),
8089 line,
8090 })
8091 }
8092 "ucfirst" => {
8093 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8094 return Ok(e);
8095 }
8096 let a = self.parse_one_arg_or_default()?;
8097 Ok(Expr {
8098 kind: ExprKind::Ucfirst(Box::new(a)),
8099 line,
8100 })
8101 }
8102 "fc" => {
8103 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8104 return Ok(e);
8105 }
8106 let a = self.parse_one_arg_or_default()?;
8107 Ok(Expr {
8108 kind: ExprKind::Fc(Box::new(a)),
8109 line,
8110 })
8111 }
8112 "crypt" => {
8113 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8114 return Ok(e);
8115 }
8116 let args = self.parse_builtin_args()?;
8117 if args.len() != 2 {
8118 return Err(self.syntax_err("crypt requires two arguments", line));
8119 }
8120 Ok(Expr {
8121 kind: ExprKind::Crypt {
8122 plaintext: Box::new(args[0].clone()),
8123 salt: Box::new(args[1].clone()),
8124 },
8125 line,
8126 })
8127 }
8128 "pos" => {
8129 if matches!(
8130 self.peek(),
8131 Token::Semicolon
8132 | Token::RBrace
8133 | Token::RParen
8134 | Token::Eof
8135 | Token::Comma
8136 | Token::PipeForward
8137 ) {
8138 Ok(Expr {
8139 kind: ExprKind::Pos(None),
8140 line,
8141 })
8142 } else if matches!(self.peek(), Token::Assign) {
8143 self.advance();
8145 let rhs = self.parse_assign_expr()?;
8146 Ok(Expr {
8147 kind: ExprKind::Assign {
8148 target: Box::new(Expr {
8149 kind: ExprKind::Pos(Some(Box::new(Expr {
8150 kind: ExprKind::ScalarVar("_".into()),
8151 line,
8152 }))),
8153 line,
8154 }),
8155 value: Box::new(rhs),
8156 },
8157 line,
8158 })
8159 } else if matches!(self.peek(), Token::LParen) {
8160 self.advance();
8161 if matches!(self.peek(), Token::RParen) {
8162 self.advance();
8163 Ok(Expr {
8164 kind: ExprKind::Pos(None),
8165 line,
8166 })
8167 } else {
8168 let a = self.parse_expression()?;
8169 self.expect(&Token::RParen)?;
8170 Ok(Expr {
8171 kind: ExprKind::Pos(Some(Box::new(a))),
8172 line,
8173 })
8174 }
8175 } else {
8176 let saved = self.pos;
8177 let subj = self.parse_unary()?;
8178 if matches!(self.peek(), Token::Assign) {
8179 self.advance();
8180 let rhs = self.parse_assign_expr()?;
8181 Ok(Expr {
8182 kind: ExprKind::Assign {
8183 target: Box::new(Expr {
8184 kind: ExprKind::Pos(Some(Box::new(subj))),
8185 line,
8186 }),
8187 value: Box::new(rhs),
8188 },
8189 line,
8190 })
8191 } else {
8192 self.pos = saved;
8193 let a = self.parse_one_arg()?;
8194 Ok(Expr {
8195 kind: ExprKind::Pos(Some(Box::new(a))),
8196 line,
8197 })
8198 }
8199 }
8200 }
8201 "study" => {
8202 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8203 return Ok(e);
8204 }
8205 let a = self.parse_one_arg_or_default()?;
8206 Ok(Expr {
8207 kind: ExprKind::Study(Box::new(a)),
8208 line,
8209 })
8210 }
8211 "push" => {
8212 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8213 return Ok(e);
8214 }
8215 let args = self.parse_builtin_args()?;
8216 let (first, rest) = args
8217 .split_first()
8218 .ok_or_else(|| self.syntax_err("push requires arguments", line))?;
8219 Ok(Expr {
8220 kind: ExprKind::Push {
8221 array: Box::new(first.clone()),
8222 values: rest.to_vec(),
8223 },
8224 line,
8225 })
8226 }
8227 "pop" => {
8228 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8229 return Ok(e);
8230 }
8231 let a = self.parse_one_arg_or_argv()?;
8232 Ok(Expr {
8233 kind: ExprKind::Pop(Box::new(a)),
8234 line,
8235 })
8236 }
8237 "shift" => {
8238 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8239 return Ok(e);
8240 }
8241 let a = self.parse_one_arg_or_argv()?;
8242 Ok(Expr {
8243 kind: ExprKind::Shift(Box::new(a)),
8244 line,
8245 })
8246 }
8247 "unshift" => {
8248 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8249 return Ok(e);
8250 }
8251 let args = self.parse_builtin_args()?;
8252 let (first, rest) = args
8253 .split_first()
8254 .ok_or_else(|| self.syntax_err("unshift requires arguments", line))?;
8255 Ok(Expr {
8256 kind: ExprKind::Unshift {
8257 array: Box::new(first.clone()),
8258 values: rest.to_vec(),
8259 },
8260 line,
8261 })
8262 }
8263 "splice" => {
8264 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8265 return Ok(e);
8266 }
8267 let args = self.parse_builtin_args()?;
8268 let mut iter = args.into_iter();
8269 let array = Box::new(
8270 iter.next()
8271 .ok_or_else(|| self.syntax_err("splice requires arguments", line))?,
8272 );
8273 let offset = iter.next().map(Box::new);
8274 let length = iter.next().map(Box::new);
8275 let replacement: Vec<Expr> = iter.collect();
8276 Ok(Expr {
8277 kind: ExprKind::Splice {
8278 array,
8279 offset,
8280 length,
8281 replacement,
8282 },
8283 line,
8284 })
8285 }
8286 "delete" => {
8287 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8288 return Ok(e);
8289 }
8290 let a = self.parse_postfix()?;
8291 Ok(Expr {
8292 kind: ExprKind::Delete(Box::new(a)),
8293 line,
8294 })
8295 }
8296 "exists" => {
8297 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8298 return Ok(e);
8299 }
8300 let a = self.parse_postfix()?;
8301 Ok(Expr {
8302 kind: ExprKind::Exists(Box::new(a)),
8303 line,
8304 })
8305 }
8306 "keys" => {
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::Keys(Box::new(a)),
8313 line,
8314 })
8315 }
8316 "values" => {
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::Values(Box::new(a)),
8323 line,
8324 })
8325 }
8326 "each" => {
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::Each(Box::new(a)),
8333 line,
8334 })
8335 }
8336 "fore" | "e" | "ep" => {
8337 if matches!(self.peek(), Token::LBrace) {
8339 let (block, list) = self.parse_block_list()?;
8340 Ok(Expr {
8341 kind: ExprKind::ForEachExpr {
8342 block,
8343 list: Box::new(list),
8344 },
8345 line,
8346 })
8347 } else if self.in_pipe_rhs() {
8348 let is_terminal = matches!(
8351 self.peek(),
8352 Token::Semicolon
8353 | Token::RParen
8354 | Token::Eof
8355 | Token::PipeForward
8356 | Token::RBrace
8357 );
8358 let block = if name == "ep" && is_terminal {
8359 vec![Statement {
8360 label: None,
8361 kind: StmtKind::Expression(Expr {
8362 kind: ExprKind::Say {
8363 handle: None,
8364 args: vec![Expr {
8365 kind: ExprKind::ScalarVar("_".into()),
8366 line,
8367 }],
8368 },
8369 line,
8370 }),
8371 line,
8372 }]
8373 } else {
8374 let expr = self.parse_assign_expr_stop_at_pipe()?;
8375 let expr = Self::lift_bareword_to_topic_call(expr);
8376 vec![Statement {
8377 label: None,
8378 kind: StmtKind::Expression(expr),
8379 line,
8380 }]
8381 };
8382 let list = self.pipe_placeholder_list(line);
8383 Ok(Expr {
8384 kind: ExprKind::ForEachExpr {
8385 block,
8386 list: Box::new(list),
8387 },
8388 line,
8389 })
8390 } else {
8391 let expr = self.parse_assign_expr()?;
8393 let expr = Self::lift_bareword_to_topic_call(expr);
8394 self.expect(&Token::Comma)?;
8395 let list_parts = self.parse_list_until_terminator()?;
8396 let list_expr = if list_parts.len() == 1 {
8397 list_parts.into_iter().next().unwrap()
8398 } else {
8399 Expr {
8400 kind: ExprKind::List(list_parts),
8401 line,
8402 }
8403 };
8404 let block = vec![Statement {
8405 label: None,
8406 kind: StmtKind::Expression(expr),
8407 line,
8408 }];
8409 Ok(Expr {
8410 kind: ExprKind::ForEachExpr {
8411 block,
8412 list: Box::new(list_expr),
8413 },
8414 line,
8415 })
8416 }
8417 }
8418 "rev" => {
8419 let a = if self.in_pipe_rhs()
8424 && matches!(
8425 self.peek(),
8426 Token::Semicolon | Token::RParen | Token::Eof | Token::PipeForward
8427 ) {
8428 self.pipe_placeholder_list(line)
8429 } else {
8430 self.parse_one_arg_or_default()?
8431 };
8432 Ok(Expr {
8433 kind: ExprKind::ScalarReverse(Box::new(a)),
8434 line,
8435 })
8436 }
8437 "reverse" | "reversed" => {
8438 let a = if self.in_pipe_rhs()
8440 && matches!(
8441 self.peek(),
8442 Token::Semicolon
8443 | Token::RBrace
8444 | Token::RParen
8445 | Token::Eof
8446 | Token::PipeForward
8447 ) {
8448 self.pipe_placeholder_list(line)
8449 } else {
8450 self.parse_one_arg()?
8451 };
8452 Ok(Expr {
8453 kind: ExprKind::ReverseExpr(Box::new(a)),
8454 line,
8455 })
8456 }
8457 "join" => {
8458 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8459 return Ok(e);
8460 }
8461 let args = self.parse_builtin_args()?;
8462 if args.is_empty() {
8463 return Err(self.syntax_err("join requires separator and list", line));
8464 }
8465 if args.len() < 2 && !self.in_pipe_rhs() {
8467 return Err(self.syntax_err("join requires separator and list", line));
8468 }
8469 Ok(Expr {
8470 kind: ExprKind::JoinExpr {
8471 separator: Box::new(args[0].clone()),
8472 list: Box::new(Expr {
8473 kind: ExprKind::List(args[1..].to_vec()),
8474 line,
8475 }),
8476 },
8477 line,
8478 })
8479 }
8480 "split" => {
8481 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8482 return Ok(e);
8483 }
8484 let args = self.parse_builtin_args()?;
8485 let pattern = args.first().cloned().unwrap_or(Expr {
8486 kind: ExprKind::String(" ".into()),
8487 line,
8488 });
8489 let string = args.get(1).cloned().unwrap_or(Expr {
8490 kind: ExprKind::ScalarVar("_".into()),
8491 line,
8492 });
8493 let limit = args.get(2).cloned().map(Box::new);
8494 Ok(Expr {
8495 kind: ExprKind::SplitExpr {
8496 pattern: Box::new(pattern),
8497 string: Box::new(string),
8498 limit,
8499 },
8500 line,
8501 })
8502 }
8503 "substr" => {
8504 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8505 return Ok(e);
8506 }
8507 let args = self.parse_builtin_args()?;
8508 Ok(Expr {
8509 kind: ExprKind::Substr {
8510 string: Box::new(args[0].clone()),
8511 offset: Box::new(args[1].clone()),
8512 length: args.get(2).cloned().map(Box::new),
8513 replacement: args.get(3).cloned().map(Box::new),
8514 },
8515 line,
8516 })
8517 }
8518 "index" => {
8519 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8520 return Ok(e);
8521 }
8522 let args = self.parse_builtin_args()?;
8523 Ok(Expr {
8524 kind: ExprKind::Index {
8525 string: Box::new(args[0].clone()),
8526 substr: Box::new(args[1].clone()),
8527 position: args.get(2).cloned().map(Box::new),
8528 },
8529 line,
8530 })
8531 }
8532 "rindex" => {
8533 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8534 return Ok(e);
8535 }
8536 let args = self.parse_builtin_args()?;
8537 Ok(Expr {
8538 kind: ExprKind::Rindex {
8539 string: Box::new(args[0].clone()),
8540 substr: Box::new(args[1].clone()),
8541 position: args.get(2).cloned().map(Box::new),
8542 },
8543 line,
8544 })
8545 }
8546 "sprintf" => {
8547 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8548 return Ok(e);
8549 }
8550 let args = self.parse_builtin_args()?;
8551 let (first, rest) = args
8552 .split_first()
8553 .ok_or_else(|| self.syntax_err("sprintf requires format", line))?;
8554 Ok(Expr {
8555 kind: ExprKind::Sprintf {
8556 format: Box::new(first.clone()),
8557 args: rest.to_vec(),
8558 },
8559 line,
8560 })
8561 }
8562 "map" | "flat_map" | "maps" | "flat_maps" => {
8563 let flatten_array_refs = matches!(name.as_str(), "flat_map" | "flat_maps");
8564 let stream = matches!(name.as_str(), "maps" | "flat_maps");
8565 if matches!(self.peek(), Token::LBrace) {
8566 let (block, list) = self.parse_block_list()?;
8567 Ok(Expr {
8568 kind: ExprKind::MapExpr {
8569 block,
8570 list: Box::new(list),
8571 flatten_array_refs,
8572 stream,
8573 },
8574 line,
8575 })
8576 } else {
8577 let expr = self.parse_assign_expr_stop_at_pipe()?;
8578 let expr = Self::lift_bareword_to_topic_call(expr);
8581 let list_expr = if self.in_pipe_rhs()
8582 && matches!(
8583 self.peek(),
8584 Token::Semicolon
8585 | Token::RBrace
8586 | Token::RParen
8587 | Token::Eof
8588 | Token::PipeForward
8589 ) {
8590 self.pipe_placeholder_list(line)
8591 } else {
8592 self.expect(&Token::Comma)?;
8593 let list_parts = self.parse_list_until_terminator()?;
8594 if list_parts.len() == 1 {
8595 list_parts.into_iter().next().unwrap()
8596 } else {
8597 Expr {
8598 kind: ExprKind::List(list_parts),
8599 line,
8600 }
8601 }
8602 };
8603 Ok(Expr {
8604 kind: ExprKind::MapExprComma {
8605 expr: Box::new(expr),
8606 list: Box::new(list_expr),
8607 flatten_array_refs,
8608 stream,
8609 },
8610 line,
8611 })
8612 }
8613 }
8614 "match" => {
8615 if crate::compat_mode() {
8616 return Err(self.syntax_err(
8617 "algebraic `match` is a stryke extension (disabled by --compat)",
8618 line,
8619 ));
8620 }
8621 self.parse_algebraic_match_expr(line)
8622 }
8623 "grep" | "greps" | "filter" | "f" | "find_all" => {
8624 let keyword = match name.as_str() {
8625 "grep" => crate::ast::GrepBuiltinKeyword::Grep,
8626 "greps" => crate::ast::GrepBuiltinKeyword::Greps,
8627 "filter" | "f" => crate::ast::GrepBuiltinKeyword::Filter,
8628 "find_all" => crate::ast::GrepBuiltinKeyword::FindAll,
8629 _ => unreachable!(),
8630 };
8631 if matches!(self.peek(), Token::LBrace) {
8632 let (block, list) = self.parse_block_list()?;
8633 Ok(Expr {
8634 kind: ExprKind::GrepExpr {
8635 block,
8636 list: Box::new(list),
8637 keyword,
8638 },
8639 line,
8640 })
8641 } else {
8642 let expr = self.parse_assign_expr_stop_at_pipe()?;
8643 if self.in_pipe_rhs()
8644 && matches!(
8645 self.peek(),
8646 Token::Semicolon
8647 | Token::RBrace
8648 | Token::RParen
8649 | Token::Eof
8650 | Token::PipeForward
8651 )
8652 {
8653 let list = self.pipe_placeholder_list(line);
8658 let topic = Expr {
8659 kind: ExprKind::ScalarVar("_".into()),
8660 line,
8661 };
8662 let test = match &expr.kind {
8663 ExprKind::Integer(_) | ExprKind::Float(_) => Expr {
8664 kind: ExprKind::BinOp {
8665 op: BinOp::NumEq,
8666 left: Box::new(topic),
8667 right: Box::new(expr),
8668 },
8669 line,
8670 },
8671 ExprKind::String(_) | ExprKind::InterpolatedString(_) => Expr {
8672 kind: ExprKind::BinOp {
8673 op: BinOp::StrEq,
8674 left: Box::new(topic),
8675 right: Box::new(expr),
8676 },
8677 line,
8678 },
8679 ExprKind::Regex { .. } => Expr {
8680 kind: ExprKind::BinOp {
8681 op: BinOp::BindMatch,
8682 left: Box::new(topic),
8683 right: Box::new(expr),
8684 },
8685 line,
8686 },
8687 _ => {
8688 Self::lift_bareword_to_topic_call(expr)
8690 }
8691 };
8692 let block = vec![Statement {
8693 label: None,
8694 kind: StmtKind::Expression(test),
8695 line,
8696 }];
8697 Ok(Expr {
8698 kind: ExprKind::GrepExpr {
8699 block,
8700 list: Box::new(list),
8701 keyword,
8702 },
8703 line,
8704 })
8705 } else {
8706 let expr = Self::lift_bareword_to_topic_call(expr);
8707 self.expect(&Token::Comma)?;
8708 let list_parts = self.parse_list_until_terminator()?;
8709 let list_expr = if list_parts.len() == 1 {
8710 list_parts.into_iter().next().unwrap()
8711 } else {
8712 Expr {
8713 kind: ExprKind::List(list_parts),
8714 line,
8715 }
8716 };
8717 Ok(Expr {
8718 kind: ExprKind::GrepExprComma {
8719 expr: Box::new(expr),
8720 list: Box::new(list_expr),
8721 keyword,
8722 },
8723 line,
8724 })
8725 }
8726 }
8727 }
8728 "sort" => {
8729 use crate::ast::SortComparator;
8730 if matches!(self.peek(), Token::LBrace) {
8731 let block = self.parse_block()?;
8732 let _ = self.eat(&Token::Comma);
8733 let list = if self.in_pipe_rhs()
8734 && matches!(
8735 self.peek(),
8736 Token::Semicolon
8737 | Token::RBrace
8738 | Token::RParen
8739 | Token::Eof
8740 | Token::PipeForward
8741 ) {
8742 self.pipe_placeholder_list(line)
8743 } else {
8744 self.parse_expression()?
8745 };
8746 Ok(Expr {
8747 kind: ExprKind::SortExpr {
8748 cmp: Some(SortComparator::Block(block)),
8749 list: Box::new(list),
8750 },
8751 line,
8752 })
8753 } else if matches!(self.peek(), Token::ScalarVar(ref v) if v == "a" || v == "b") {
8754 let block = self.parse_block_or_bareword_cmp_block()?;
8756 let _ = self.eat(&Token::Comma);
8757 let list = if self.in_pipe_rhs()
8758 && matches!(
8759 self.peek(),
8760 Token::Semicolon
8761 | Token::RBrace
8762 | Token::RParen
8763 | Token::Eof
8764 | Token::PipeForward
8765 ) {
8766 self.pipe_placeholder_list(line)
8767 } else {
8768 self.parse_expression()?
8769 };
8770 Ok(Expr {
8771 kind: ExprKind::SortExpr {
8772 cmp: Some(SortComparator::Block(block)),
8773 list: Box::new(list),
8774 },
8775 line,
8776 })
8777 } else if matches!(self.peek(), Token::ScalarVar(_)) {
8778 self.suppress_indirect_paren_call =
8780 self.suppress_indirect_paren_call.saturating_add(1);
8781 let code = self.parse_assign_expr()?;
8782 self.suppress_indirect_paren_call =
8783 self.suppress_indirect_paren_call.saturating_sub(1);
8784 let list = if matches!(self.peek(), Token::LParen) {
8785 self.advance();
8786 let e = self.parse_expression()?;
8787 self.expect(&Token::RParen)?;
8788 e
8789 } else {
8790 self.parse_expression()?
8791 };
8792 Ok(Expr {
8793 kind: ExprKind::SortExpr {
8794 cmp: Some(SortComparator::Code(Box::new(code))),
8795 list: Box::new(list),
8796 },
8797 line,
8798 })
8799 } else if matches!(self.peek(), Token::Ident(ref name) if !Self::is_known_bareword(name))
8800 {
8801 let block = self.parse_block_or_bareword_cmp_block()?;
8803 let _ = self.eat(&Token::Comma);
8804 let list = if self.in_pipe_rhs()
8805 && matches!(
8806 self.peek(),
8807 Token::Semicolon
8808 | Token::RBrace
8809 | Token::RParen
8810 | Token::Eof
8811 | Token::PipeForward
8812 ) {
8813 self.pipe_placeholder_list(line)
8814 } else {
8815 self.parse_expression()?
8816 };
8817 Ok(Expr {
8818 kind: ExprKind::SortExpr {
8819 cmp: Some(SortComparator::Block(block)),
8820 list: Box::new(list),
8821 },
8822 line,
8823 })
8824 } else {
8825 let list = if self.in_pipe_rhs()
8828 && matches!(
8829 self.peek(),
8830 Token::Semicolon
8831 | Token::RBrace
8832 | Token::RParen
8833 | Token::Eof
8834 | Token::PipeForward
8835 ) {
8836 self.pipe_placeholder_list(line)
8837 } else {
8838 self.parse_expression()?
8839 };
8840 Ok(Expr {
8841 kind: ExprKind::SortExpr {
8842 cmp: None,
8843 list: Box::new(list),
8844 },
8845 line,
8846 })
8847 }
8848 }
8849 "reduce" | "fold" | "inject" => {
8850 let (block, list) = self.parse_block_list()?;
8851 Ok(Expr {
8852 kind: ExprKind::ReduceExpr {
8853 block,
8854 list: Box::new(list),
8855 },
8856 line,
8857 })
8858 }
8859 "pmap" => {
8861 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
8862 Ok(Expr {
8863 kind: ExprKind::PMapExpr {
8864 block,
8865 list: Box::new(list),
8866 progress: progress.map(Box::new),
8867 flat_outputs: false,
8868 on_cluster: None,
8869 stream: false,
8870 },
8871 line,
8872 })
8873 }
8874 "pmap_on" => {
8875 let (cluster, block, list, progress) =
8876 self.parse_cluster_block_then_list_optional_progress()?;
8877 Ok(Expr {
8878 kind: ExprKind::PMapExpr {
8879 block,
8880 list: Box::new(list),
8881 progress: progress.map(Box::new),
8882 flat_outputs: false,
8883 on_cluster: Some(Box::new(cluster)),
8884 stream: false,
8885 },
8886 line,
8887 })
8888 }
8889 "pflat_map" => {
8890 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
8891 Ok(Expr {
8892 kind: ExprKind::PMapExpr {
8893 block,
8894 list: Box::new(list),
8895 progress: progress.map(Box::new),
8896 flat_outputs: true,
8897 on_cluster: None,
8898 stream: false,
8899 },
8900 line,
8901 })
8902 }
8903 "pflat_map_on" => {
8904 let (cluster, block, list, progress) =
8905 self.parse_cluster_block_then_list_optional_progress()?;
8906 Ok(Expr {
8907 kind: ExprKind::PMapExpr {
8908 block,
8909 list: Box::new(list),
8910 progress: progress.map(Box::new),
8911 flat_outputs: true,
8912 on_cluster: Some(Box::new(cluster)),
8913 stream: false,
8914 },
8915 line,
8916 })
8917 }
8918 "pmaps" => {
8919 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
8920 Ok(Expr {
8921 kind: ExprKind::PMapExpr {
8922 block,
8923 list: Box::new(list),
8924 progress: progress.map(Box::new),
8925 flat_outputs: false,
8926 on_cluster: None,
8927 stream: true,
8928 },
8929 line,
8930 })
8931 }
8932 "pflat_maps" => {
8933 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
8934 Ok(Expr {
8935 kind: ExprKind::PMapExpr {
8936 block,
8937 list: Box::new(list),
8938 progress: progress.map(Box::new),
8939 flat_outputs: true,
8940 on_cluster: None,
8941 stream: true,
8942 },
8943 line,
8944 })
8945 }
8946 "pgreps" => {
8947 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
8948 Ok(Expr {
8949 kind: ExprKind::PGrepExpr {
8950 block,
8951 list: Box::new(list),
8952 progress: progress.map(Box::new),
8953 stream: true,
8954 },
8955 line,
8956 })
8957 }
8958 "pmap_chunked" => {
8959 let chunk_size = self.parse_assign_expr()?;
8960 let block = self.parse_block_or_bareword_block()?;
8961 self.eat(&Token::Comma);
8962 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
8963 Ok(Expr {
8964 kind: ExprKind::PMapChunkedExpr {
8965 chunk_size: Box::new(chunk_size),
8966 block,
8967 list: Box::new(list),
8968 progress: progress.map(Box::new),
8969 },
8970 line,
8971 })
8972 }
8973 "pgrep" => {
8974 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
8975 Ok(Expr {
8976 kind: ExprKind::PGrepExpr {
8977 block,
8978 list: Box::new(list),
8979 progress: progress.map(Box::new),
8980 stream: false,
8981 },
8982 line,
8983 })
8984 }
8985 "pfor" => {
8986 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
8987 Ok(Expr {
8988 kind: ExprKind::PForExpr {
8989 block,
8990 list: Box::new(list),
8991 progress: progress.map(Box::new),
8992 },
8993 line,
8994 })
8995 }
8996 "par_lines" | "par_walk" => {
8997 let args = self.parse_builtin_args()?;
8998 if args.len() < 2 {
8999 return Err(
9000 self.syntax_err(format!("{} requires at least two arguments", name), line)
9001 );
9002 }
9003
9004 if name == "par_lines" {
9005 Ok(Expr {
9006 kind: ExprKind::ParLinesExpr {
9007 path: Box::new(args[0].clone()),
9008 callback: Box::new(args[1].clone()),
9009 progress: None,
9010 },
9011 line,
9012 })
9013 } else {
9014 Ok(Expr {
9015 kind: ExprKind::ParWalkExpr {
9016 path: Box::new(args[0].clone()),
9017 callback: Box::new(args[1].clone()),
9018 progress: None,
9019 },
9020 line,
9021 })
9022 }
9023 }
9024 "pwatch" | "watch" => {
9025 let args = self.parse_builtin_args()?;
9026 if args.len() < 2 {
9027 return Err(
9028 self.syntax_err(format!("{} requires at least two arguments", name), line)
9029 );
9030 }
9031 Ok(Expr {
9032 kind: ExprKind::PwatchExpr {
9033 path: Box::new(args[0].clone()),
9034 callback: Box::new(args[1].clone()),
9035 },
9036 line,
9037 })
9038 }
9039 "fan" => {
9040 let (count, block) = self.parse_fan_count_and_block(line)?;
9046 let progress = self.parse_fan_optional_progress("fan")?;
9047 Ok(Expr {
9048 kind: ExprKind::FanExpr {
9049 count,
9050 block,
9051 progress,
9052 capture: false,
9053 },
9054 line,
9055 })
9056 }
9057 "fan_cap" => {
9058 let (count, block) = self.parse_fan_count_and_block(line)?;
9059 let progress = self.parse_fan_optional_progress("fan_cap")?;
9060 Ok(Expr {
9061 kind: ExprKind::FanExpr {
9062 count,
9063 block,
9064 progress,
9065 capture: true,
9066 },
9067 line,
9068 })
9069 }
9070 "async" => {
9071 if !matches!(self.peek(), Token::LBrace) {
9072 return Err(self.syntax_err("async must be followed by { BLOCK }", line));
9073 }
9074 let block = self.parse_block()?;
9075 Ok(Expr {
9076 kind: ExprKind::AsyncBlock { body: block },
9077 line,
9078 })
9079 }
9080 "spawn" => {
9081 if !matches!(self.peek(), Token::LBrace) {
9082 return Err(self.syntax_err("spawn must be followed by { BLOCK }", line));
9083 }
9084 let block = self.parse_block()?;
9085 Ok(Expr {
9086 kind: ExprKind::SpawnBlock { body: block },
9087 line,
9088 })
9089 }
9090 "trace" => {
9091 if !matches!(self.peek(), Token::LBrace) {
9092 return Err(self.syntax_err("trace must be followed by { BLOCK }", line));
9093 }
9094 let block = self.parse_block()?;
9095 Ok(Expr {
9096 kind: ExprKind::Trace { body: block },
9097 line,
9098 })
9099 }
9100 "timer" => {
9101 let block = self.parse_block_or_bareword_block_no_args()?;
9102 Ok(Expr {
9103 kind: ExprKind::Timer { body: block },
9104 line,
9105 })
9106 }
9107 "bench" => {
9108 let block = self.parse_block_or_bareword_block_no_args()?;
9109 let times = Box::new(self.parse_expression()?);
9110 Ok(Expr {
9111 kind: ExprKind::Bench { body: block, times },
9112 line,
9113 })
9114 }
9115 "spinner" => {
9116 let (message, body) = if matches!(self.peek(), Token::LBrace) {
9118 let body = self.parse_block()?;
9119 (
9120 Box::new(Expr {
9121 kind: ExprKind::String("working".to_string()),
9122 line,
9123 }),
9124 body,
9125 )
9126 } else {
9127 let msg = self.parse_assign_expr()?;
9128 let body = self.parse_block()?;
9129 (Box::new(msg), body)
9130 };
9131 Ok(Expr {
9132 kind: ExprKind::Spinner { message, body },
9133 line,
9134 })
9135 }
9136 "thread" | "t" => {
9137 self.parse_thread_macro(line)
9147 }
9148 "retry" => {
9149 let body = if matches!(self.peek(), Token::LBrace) {
9152 self.parse_block()?
9153 } else {
9154 let bw_line = self.peek_line();
9155 let Token::Ident(ref name) = self.peek().clone() else {
9156 return Err(self
9157 .syntax_err("retry: expected block or bareword function name", line));
9158 };
9159 let name = name.clone();
9160 self.advance();
9161 vec![Statement::new(
9162 StmtKind::Expression(Expr {
9163 kind: ExprKind::FuncCall { name, args: vec![] },
9164 line: bw_line,
9165 }),
9166 bw_line,
9167 )]
9168 };
9169 self.eat(&Token::Comma);
9170 match self.peek() {
9171 Token::Ident(ref s) if s == "times" => {
9172 self.advance();
9173 }
9174 _ => {
9175 return Err(self.syntax_err("retry: expected `times =>` after block", line));
9176 }
9177 }
9178 self.expect(&Token::FatArrow)?;
9179 let times = Box::new(self.parse_assign_expr()?);
9180 let mut backoff = RetryBackoff::None;
9181 if self.eat(&Token::Comma) {
9182 match self.peek() {
9183 Token::Ident(ref s) if s == "backoff" => {
9184 self.advance();
9185 }
9186 _ => {
9187 return Err(
9188 self.syntax_err("retry: expected `backoff =>` after comma", line)
9189 );
9190 }
9191 }
9192 self.expect(&Token::FatArrow)?;
9193 let Token::Ident(mode) = self.peek().clone() else {
9194 return Err(self.syntax_err(
9195 "retry: expected backoff mode (none, linear, exponential)",
9196 line,
9197 ));
9198 };
9199 backoff = match mode.as_str() {
9200 "none" => RetryBackoff::None,
9201 "linear" => RetryBackoff::Linear,
9202 "exponential" => RetryBackoff::Exponential,
9203 _ => {
9204 return Err(
9205 self.syntax_err(format!("retry: invalid backoff `{mode}`"), line)
9206 );
9207 }
9208 };
9209 self.advance();
9210 }
9211 Ok(Expr {
9212 kind: ExprKind::RetryBlock {
9213 body,
9214 times,
9215 backoff,
9216 },
9217 line,
9218 })
9219 }
9220 "rate_limit" => {
9221 self.expect(&Token::LParen)?;
9222 let max = Box::new(self.parse_assign_expr()?);
9223 self.expect(&Token::Comma)?;
9224 let window = Box::new(self.parse_assign_expr()?);
9225 self.expect(&Token::RParen)?;
9226 let body = self.parse_block_or_bareword_block_no_args()?;
9227 let slot = self.alloc_rate_limit_slot();
9228 Ok(Expr {
9229 kind: ExprKind::RateLimitBlock {
9230 slot,
9231 max,
9232 window,
9233 body,
9234 },
9235 line,
9236 })
9237 }
9238 "every" => {
9239 let has_paren = self.eat(&Token::LParen);
9242 let interval = Box::new(self.parse_assign_expr()?);
9243 if has_paren {
9244 self.expect(&Token::RParen)?;
9245 }
9246 let body = if matches!(self.peek(), Token::LBrace) {
9247 self.parse_block()?
9248 } else {
9249 let bline = self.peek_line();
9250 let expr = self.parse_assign_expr()?;
9251 vec![Statement::new(StmtKind::Expression(expr), bline)]
9252 };
9253 Ok(Expr {
9254 kind: ExprKind::EveryBlock { interval, body },
9255 line,
9256 })
9257 }
9258 "gen" => {
9259 if !matches!(self.peek(), Token::LBrace) {
9260 return Err(self.syntax_err("gen must be followed by { BLOCK }", line));
9261 }
9262 let body = self.parse_block()?;
9263 Ok(Expr {
9264 kind: ExprKind::GenBlock { body },
9265 line,
9266 })
9267 }
9268 "yield" => {
9269 let e = self.parse_assign_expr()?;
9270 Ok(Expr {
9271 kind: ExprKind::Yield(Box::new(e)),
9272 line,
9273 })
9274 }
9275 "await" => {
9276 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9277 return Ok(e);
9278 }
9279 let a = self.parse_one_arg_or_default()?;
9282 Ok(Expr {
9283 kind: ExprKind::Await(Box::new(a)),
9284 line,
9285 })
9286 }
9287 "slurp" | "cat" | "c" => {
9288 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9289 return Ok(e);
9290 }
9291 let a = self.parse_one_arg_or_default()?;
9292 Ok(Expr {
9293 kind: ExprKind::Slurp(Box::new(a)),
9294 line,
9295 })
9296 }
9297 "capture" => {
9298 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9299 return Ok(e);
9300 }
9301 let a = self.parse_one_arg()?;
9302 Ok(Expr {
9303 kind: ExprKind::Capture(Box::new(a)),
9304 line,
9305 })
9306 }
9307 "fetch_url" => {
9308 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9309 return Ok(e);
9310 }
9311 let a = self.parse_one_arg()?;
9312 Ok(Expr {
9313 kind: ExprKind::FetchUrl(Box::new(a)),
9314 line,
9315 })
9316 }
9317 "pchannel" => {
9318 let capacity = if self.eat(&Token::LParen) {
9319 if matches!(self.peek(), Token::RParen) {
9320 self.advance();
9321 None
9322 } else {
9323 let e = self.parse_expression()?;
9324 self.expect(&Token::RParen)?;
9325 Some(Box::new(e))
9326 }
9327 } else {
9328 None
9329 };
9330 Ok(Expr {
9331 kind: ExprKind::Pchannel { capacity },
9332 line,
9333 })
9334 }
9335 "psort" => {
9336 if matches!(self.peek(), Token::LBrace)
9337 || matches!(self.peek(), Token::ScalarVar(ref v) if v == "a" || v == "b")
9338 || matches!(self.peek(), Token::Ident(ref name) if !Self::is_known_bareword(name))
9339 {
9340 let block = self.parse_block_or_bareword_cmp_block()?;
9341 self.eat(&Token::Comma);
9342 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
9343 Ok(Expr {
9344 kind: ExprKind::PSortExpr {
9345 cmp: Some(block),
9346 list: Box::new(list),
9347 progress: progress.map(Box::new),
9348 },
9349 line,
9350 })
9351 } else {
9352 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
9353 Ok(Expr {
9354 kind: ExprKind::PSortExpr {
9355 cmp: None,
9356 list: Box::new(list),
9357 progress: progress.map(Box::new),
9358 },
9359 line,
9360 })
9361 }
9362 }
9363 "preduce" => {
9364 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
9365 Ok(Expr {
9366 kind: ExprKind::PReduceExpr {
9367 block,
9368 list: Box::new(list),
9369 progress: progress.map(Box::new),
9370 },
9371 line,
9372 })
9373 }
9374 "preduce_init" => {
9375 let (init, block, list, progress) =
9376 self.parse_init_block_then_list_optional_progress()?;
9377 Ok(Expr {
9378 kind: ExprKind::PReduceInitExpr {
9379 init: Box::new(init),
9380 block,
9381 list: Box::new(list),
9382 progress: progress.map(Box::new),
9383 },
9384 line,
9385 })
9386 }
9387 "pmap_reduce" => {
9388 let map_block = self.parse_block_or_bareword_block()?;
9389 let reduce_block = if matches!(self.peek(), Token::LBrace) {
9392 self.parse_block()?
9393 } else {
9394 self.expect(&Token::Comma)?;
9396 self.parse_block_or_bareword_cmp_block()?
9397 };
9398 self.eat(&Token::Comma);
9399 let line = self.peek_line();
9400 if let Token::Ident(ref kw) = self.peek().clone() {
9401 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
9402 self.advance();
9403 self.expect(&Token::FatArrow)?;
9404 let prog = self.parse_assign_expr()?;
9405 return Ok(Expr {
9406 kind: ExprKind::PMapReduceExpr {
9407 map_block,
9408 reduce_block,
9409 list: Box::new(Expr {
9410 kind: ExprKind::List(vec![]),
9411 line,
9412 }),
9413 progress: Some(Box::new(prog)),
9414 },
9415 line,
9416 });
9417 }
9418 }
9419 if matches!(
9420 self.peek(),
9421 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof
9422 ) {
9423 return Ok(Expr {
9424 kind: ExprKind::PMapReduceExpr {
9425 map_block,
9426 reduce_block,
9427 list: Box::new(Expr {
9428 kind: ExprKind::List(vec![]),
9429 line,
9430 }),
9431 progress: None,
9432 },
9433 line,
9434 });
9435 }
9436 let mut parts = vec![self.parse_assign_expr()?];
9437 loop {
9438 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
9439 break;
9440 }
9441 if matches!(
9442 self.peek(),
9443 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof
9444 ) {
9445 break;
9446 }
9447 if let Token::Ident(ref kw) = self.peek().clone() {
9448 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
9449 self.advance();
9450 self.expect(&Token::FatArrow)?;
9451 let prog = self.parse_assign_expr()?;
9452 return Ok(Expr {
9453 kind: ExprKind::PMapReduceExpr {
9454 map_block,
9455 reduce_block,
9456 list: Box::new(merge_expr_list(parts)),
9457 progress: Some(Box::new(prog)),
9458 },
9459 line,
9460 });
9461 }
9462 }
9463 parts.push(self.parse_assign_expr()?);
9464 }
9465 Ok(Expr {
9466 kind: ExprKind::PMapReduceExpr {
9467 map_block,
9468 reduce_block,
9469 list: Box::new(merge_expr_list(parts)),
9470 progress: None,
9471 },
9472 line,
9473 })
9474 }
9475 "puniq" => {
9476 if self.pipe_supplies_slurped_list_operand() {
9477 return Ok(Expr {
9478 kind: ExprKind::FuncCall {
9479 name: "puniq".to_string(),
9480 args: vec![],
9481 },
9482 line,
9483 });
9484 }
9485 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
9486 let mut args = vec![list];
9487 if let Some(p) = progress {
9488 args.push(p);
9489 }
9490 Ok(Expr {
9491 kind: ExprKind::FuncCall {
9492 name: "puniq".to_string(),
9493 args,
9494 },
9495 line,
9496 })
9497 }
9498 "pfirst" => {
9499 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
9500 let cr = Expr {
9501 kind: ExprKind::CodeRef {
9502 params: vec![],
9503 body: block,
9504 },
9505 line,
9506 };
9507 let mut args = vec![cr, list];
9508 if let Some(p) = progress {
9509 args.push(p);
9510 }
9511 Ok(Expr {
9512 kind: ExprKind::FuncCall {
9513 name: "pfirst".to_string(),
9514 args,
9515 },
9516 line,
9517 })
9518 }
9519 "pany" => {
9520 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
9521 let cr = Expr {
9522 kind: ExprKind::CodeRef {
9523 params: vec![],
9524 body: block,
9525 },
9526 line,
9527 };
9528 let mut args = vec![cr, list];
9529 if let Some(p) = progress {
9530 args.push(p);
9531 }
9532 Ok(Expr {
9533 kind: ExprKind::FuncCall {
9534 name: "pany".to_string(),
9535 args,
9536 },
9537 line,
9538 })
9539 }
9540 "uniq" | "distinct" => {
9541 if self.pipe_supplies_slurped_list_operand() {
9542 return Ok(Expr {
9543 kind: ExprKind::FuncCall {
9544 name: name.clone(),
9545 args: vec![],
9546 },
9547 line,
9548 });
9549 }
9550 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
9551 if progress.is_some() {
9552 return Err(self.syntax_err(
9553 "`progress =>` is not supported for uniq (use puniq for parallel + progress)",
9554 line,
9555 ));
9556 }
9557 Ok(Expr {
9558 kind: ExprKind::FuncCall {
9559 name: name.clone(),
9560 args: vec![list],
9561 },
9562 line,
9563 })
9564 }
9565 "flatten" => {
9566 if self.pipe_supplies_slurped_list_operand() {
9567 return Ok(Expr {
9568 kind: ExprKind::FuncCall {
9569 name: "flatten".to_string(),
9570 args: vec![],
9571 },
9572 line,
9573 });
9574 }
9575 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
9576 if progress.is_some() {
9577 return Err(self.syntax_err("`progress =>` is not supported for flatten", line));
9578 }
9579 Ok(Expr {
9580 kind: ExprKind::FuncCall {
9581 name: "flatten".to_string(),
9582 args: vec![list],
9583 },
9584 line,
9585 })
9586 }
9587 "set" => {
9588 if self.pipe_supplies_slurped_list_operand() {
9589 return Ok(Expr {
9590 kind: ExprKind::FuncCall {
9591 name: "set".to_string(),
9592 args: vec![],
9593 },
9594 line,
9595 });
9596 }
9597 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
9598 if progress.is_some() {
9599 return Err(self.syntax_err("`progress =>` is not supported for set", line));
9600 }
9601 Ok(Expr {
9602 kind: ExprKind::FuncCall {
9603 name: "set".to_string(),
9604 args: vec![list],
9605 },
9606 line,
9607 })
9608 }
9609 "size" => {
9613 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9614 return Ok(e);
9615 }
9616 if self.pipe_supplies_slurped_list_operand() {
9617 return Ok(Expr {
9618 kind: ExprKind::FuncCall {
9619 name: "size".to_string(),
9620 args: vec![],
9621 },
9622 line,
9623 });
9624 }
9625 let a = self.parse_one_arg_or_default()?;
9626 Ok(Expr {
9627 kind: ExprKind::FuncCall {
9628 name: "size".to_string(),
9629 args: vec![a],
9630 },
9631 line,
9632 })
9633 }
9634 "list_count" | "list_size" | "count" | "len" | "cnt" => {
9635 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9636 return Ok(e);
9637 }
9638 if self.pipe_supplies_slurped_list_operand() {
9639 return Ok(Expr {
9640 kind: ExprKind::FuncCall {
9641 name: name.clone(),
9642 args: vec![],
9643 },
9644 line,
9645 });
9646 }
9647 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
9648 if progress.is_some() {
9649 return Err(self.syntax_err(
9650 "`progress =>` is not supported for list_count / list_size / count / cnt",
9651 line,
9652 ));
9653 }
9654 Ok(Expr {
9655 kind: ExprKind::FuncCall {
9656 name: name.clone(),
9657 args: vec![list],
9658 },
9659 line,
9660 })
9661 }
9662 "shuffle" | "shuffled" => {
9663 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9664 return Ok(e);
9665 }
9666 if self.pipe_supplies_slurped_list_operand() {
9667 return Ok(Expr {
9668 kind: ExprKind::FuncCall {
9669 name: "shuffle".to_string(),
9670 args: vec![],
9671 },
9672 line,
9673 });
9674 }
9675 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
9676 if progress.is_some() {
9677 return Err(self.syntax_err("`progress =>` is not supported for shuffle", line));
9678 }
9679 Ok(Expr {
9680 kind: ExprKind::FuncCall {
9681 name: "shuffle".to_string(),
9682 args: vec![list],
9683 },
9684 line,
9685 })
9686 }
9687 "chunked" => {
9688 let mut parts = Vec::new();
9689 if self.eat(&Token::LParen) {
9690 if !matches!(self.peek(), Token::RParen) {
9691 parts.push(self.parse_assign_expr()?);
9692 while self.eat(&Token::Comma) {
9693 if matches!(self.peek(), Token::RParen) {
9694 break;
9695 }
9696 parts.push(self.parse_assign_expr()?);
9697 }
9698 }
9699 self.expect(&Token::RParen)?;
9700 } else {
9701 parts.push(self.parse_assign_expr_stop_at_pipe()?);
9705 loop {
9706 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
9707 break;
9708 }
9709 if matches!(
9710 self.peek(),
9711 Token::Semicolon
9712 | Token::RBrace
9713 | Token::RParen
9714 | Token::Eof
9715 | Token::PipeForward
9716 ) {
9717 break;
9718 }
9719 if self.peek_is_postfix_stmt_modifier_keyword() {
9720 break;
9721 }
9722 parts.push(self.parse_assign_expr_stop_at_pipe()?);
9723 }
9724 }
9725 if parts.len() == 1 {
9726 let n = parts.pop().unwrap();
9727 return Ok(Expr {
9728 kind: ExprKind::FuncCall {
9729 name: "chunked".to_string(),
9730 args: vec![n],
9731 },
9732 line,
9733 });
9734 }
9735 if parts.is_empty() {
9736 return Ok(Expr {
9737 kind: ExprKind::FuncCall {
9738 name: "chunked".to_string(),
9739 args: parts,
9740 },
9741 line,
9742 });
9743 }
9744 if parts.len() == 2 {
9745 let n = parts.pop().unwrap();
9746 let list = parts.pop().unwrap();
9747 return Ok(Expr {
9748 kind: ExprKind::FuncCall {
9749 name: "chunked".to_string(),
9750 args: vec![list, n],
9751 },
9752 line,
9753 });
9754 }
9755 Err(self.syntax_err(
9756 "chunked: use LIST |> chunked(N) or chunked((1,2,3), 2)",
9757 line,
9758 ))
9759 }
9760 "windowed" => {
9761 let mut parts = Vec::new();
9762 if self.eat(&Token::LParen) {
9763 if !matches!(self.peek(), Token::RParen) {
9764 parts.push(self.parse_assign_expr()?);
9765 while self.eat(&Token::Comma) {
9766 if matches!(self.peek(), Token::RParen) {
9767 break;
9768 }
9769 parts.push(self.parse_assign_expr()?);
9770 }
9771 }
9772 self.expect(&Token::RParen)?;
9773 } else {
9774 parts.push(self.parse_assign_expr_stop_at_pipe()?);
9777 loop {
9778 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
9779 break;
9780 }
9781 if matches!(
9782 self.peek(),
9783 Token::Semicolon
9784 | Token::RBrace
9785 | Token::RParen
9786 | Token::Eof
9787 | Token::PipeForward
9788 ) {
9789 break;
9790 }
9791 if self.peek_is_postfix_stmt_modifier_keyword() {
9792 break;
9793 }
9794 parts.push(self.parse_assign_expr_stop_at_pipe()?);
9795 }
9796 }
9797 if parts.len() == 1 {
9798 let n = parts.pop().unwrap();
9799 return Ok(Expr {
9800 kind: ExprKind::FuncCall {
9801 name: "windowed".to_string(),
9802 args: vec![n],
9803 },
9804 line,
9805 });
9806 }
9807 if parts.is_empty() {
9808 return Ok(Expr {
9809 kind: ExprKind::FuncCall {
9810 name: "windowed".to_string(),
9811 args: parts,
9812 },
9813 line,
9814 });
9815 }
9816 if parts.len() == 2 {
9817 let n = parts.pop().unwrap();
9818 let list = parts.pop().unwrap();
9819 return Ok(Expr {
9820 kind: ExprKind::FuncCall {
9821 name: "windowed".to_string(),
9822 args: vec![list, n],
9823 },
9824 line,
9825 });
9826 }
9827 Err(self.syntax_err(
9828 "windowed: use LIST |> windowed(N) or windowed((1,2,3), 2)",
9829 line,
9830 ))
9831 }
9832 "any" | "all" | "none" => {
9833 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
9834 if progress.is_some() {
9835 return Err(self.syntax_err(
9836 "`progress =>` is not supported for any/all/none (use pany for parallel + progress)",
9837 line,
9838 ));
9839 }
9840 let cr = Expr {
9841 kind: ExprKind::CodeRef {
9842 params: vec![],
9843 body: block,
9844 },
9845 line,
9846 };
9847 Ok(Expr {
9848 kind: ExprKind::FuncCall {
9849 name: name.clone(),
9850 args: vec![cr, list],
9851 },
9852 line,
9853 })
9854 }
9855 "first" | "detect" | "find" => {
9857 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
9858 if progress.is_some() {
9859 return Err(self.syntax_err(
9860 "`progress =>` is not supported for first/detect/find (use pfirst for parallel + progress)",
9861 line,
9862 ));
9863 }
9864 let cr = Expr {
9865 kind: ExprKind::CodeRef {
9866 params: vec![],
9867 body: block,
9868 },
9869 line,
9870 };
9871 Ok(Expr {
9872 kind: ExprKind::FuncCall {
9873 name: "first".to_string(),
9874 args: vec![cr, list],
9875 },
9876 line,
9877 })
9878 }
9879 "take_while" | "drop_while" | "skip_while" | "reject" | "tap" | "peek"
9880 | "partition" | "min_by" | "max_by" | "zip_with" | "count_by" => {
9881 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
9882 if progress.is_some() {
9883 return Err(
9884 self.syntax_err(format!("`progress =>` is not supported for {name}"), line)
9885 );
9886 }
9887 let cr = Expr {
9888 kind: ExprKind::CodeRef {
9889 params: vec![],
9890 body: block,
9891 },
9892 line,
9893 };
9894 Ok(Expr {
9895 kind: ExprKind::FuncCall {
9896 name: name.to_string(),
9897 args: vec![cr, list],
9898 },
9899 line,
9900 })
9901 }
9902 "group_by" | "chunk_by" => {
9903 if matches!(self.peek(), Token::LBrace) {
9904 let (block, list) = self.parse_block_list()?;
9905 let cr = Expr {
9906 kind: ExprKind::CodeRef {
9907 params: vec![],
9908 body: block,
9909 },
9910 line,
9911 };
9912 Ok(Expr {
9913 kind: ExprKind::FuncCall {
9914 name: name.to_string(),
9915 args: vec![cr, list],
9916 },
9917 line,
9918 })
9919 } else {
9920 let key_expr = self.parse_assign_expr()?;
9921 self.expect(&Token::Comma)?;
9922 let list_parts = self.parse_list_until_terminator()?;
9923 let list_expr = if list_parts.len() == 1 {
9924 list_parts.into_iter().next().unwrap()
9925 } else {
9926 Expr {
9927 kind: ExprKind::List(list_parts),
9928 line,
9929 }
9930 };
9931 Ok(Expr {
9932 kind: ExprKind::FuncCall {
9933 name: name.to_string(),
9934 args: vec![key_expr, list_expr],
9935 },
9936 line,
9937 })
9938 }
9939 }
9940 "with_index" => {
9941 if self.pipe_supplies_slurped_list_operand() {
9942 return Ok(Expr {
9943 kind: ExprKind::FuncCall {
9944 name: "with_index".to_string(),
9945 args: vec![],
9946 },
9947 line,
9948 });
9949 }
9950 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
9951 if progress.is_some() {
9952 return Err(
9953 self.syntax_err("`progress =>` is not supported for with_index", line)
9954 );
9955 }
9956 Ok(Expr {
9957 kind: ExprKind::FuncCall {
9958 name: "with_index".to_string(),
9959 args: vec![list],
9960 },
9961 line,
9962 })
9963 }
9964 "pcache" => {
9965 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
9966 Ok(Expr {
9967 kind: ExprKind::PcacheExpr {
9968 block,
9969 list: Box::new(list),
9970 progress: progress.map(Box::new),
9971 },
9972 line,
9973 })
9974 }
9975 "pselect" => {
9976 let paren = self.eat(&Token::LParen);
9977 let (receivers, timeout) = self.parse_comma_expr_list_with_timeout_tail(paren)?;
9978 if paren {
9979 self.expect(&Token::RParen)?;
9980 }
9981 if receivers.is_empty() {
9982 return Err(self.syntax_err("pselect needs at least one receiver", line));
9983 }
9984 Ok(Expr {
9985 kind: ExprKind::PselectExpr {
9986 receivers,
9987 timeout: timeout.map(Box::new),
9988 },
9989 line,
9990 })
9991 }
9992 "open" => {
9993 let paren = matches!(self.peek(), Token::LParen);
9994 if paren {
9995 self.advance();
9996 }
9997 if matches!(self.peek(), Token::Ident(ref s) if s == "my") {
9998 self.advance();
9999 let name = self.parse_scalar_var_name()?;
10000 self.expect(&Token::Comma)?;
10001 let mode = self.parse_assign_expr()?;
10002 let file = if self.eat(&Token::Comma) {
10003 Some(self.parse_assign_expr()?)
10004 } else {
10005 None
10006 };
10007 if paren {
10008 self.expect(&Token::RParen)?;
10009 }
10010 Ok(Expr {
10011 kind: ExprKind::Open {
10012 handle: Box::new(Expr {
10013 kind: ExprKind::OpenMyHandle { name },
10014 line,
10015 }),
10016 mode: Box::new(mode),
10017 file: file.map(Box::new),
10018 },
10019 line,
10020 })
10021 } else {
10022 let args = if paren {
10023 self.parse_arg_list()?
10024 } else {
10025 self.parse_list_until_terminator()?
10026 };
10027 if paren {
10028 self.expect(&Token::RParen)?;
10029 }
10030 if args.len() < 2 {
10031 return Err(self.syntax_err("open requires at least 2 arguments", line));
10032 }
10033 Ok(Expr {
10034 kind: ExprKind::Open {
10035 handle: Box::new(args[0].clone()),
10036 mode: Box::new(args[1].clone()),
10037 file: args.get(2).cloned().map(Box::new),
10038 },
10039 line,
10040 })
10041 }
10042 }
10043 "close" => {
10044 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10045 return Ok(e);
10046 }
10047 let a = self.parse_one_arg_or_default()?;
10048 Ok(Expr {
10049 kind: ExprKind::Close(Box::new(a)),
10050 line,
10051 })
10052 }
10053 "opendir" => {
10054 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10055 return Ok(e);
10056 }
10057 let args = self.parse_builtin_args()?;
10058 if args.len() != 2 {
10059 return Err(self.syntax_err("opendir requires two arguments", line));
10060 }
10061 Ok(Expr {
10062 kind: ExprKind::Opendir {
10063 handle: Box::new(args[0].clone()),
10064 path: Box::new(args[1].clone()),
10065 },
10066 line,
10067 })
10068 }
10069 "readdir" => {
10070 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10071 return Ok(e);
10072 }
10073 let a = self.parse_one_arg()?;
10074 Ok(Expr {
10075 kind: ExprKind::Readdir(Box::new(a)),
10076 line,
10077 })
10078 }
10079 "closedir" => {
10080 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10081 return Ok(e);
10082 }
10083 let a = self.parse_one_arg()?;
10084 Ok(Expr {
10085 kind: ExprKind::Closedir(Box::new(a)),
10086 line,
10087 })
10088 }
10089 "rewinddir" => {
10090 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10091 return Ok(e);
10092 }
10093 let a = self.parse_one_arg()?;
10094 Ok(Expr {
10095 kind: ExprKind::Rewinddir(Box::new(a)),
10096 line,
10097 })
10098 }
10099 "telldir" => {
10100 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10101 return Ok(e);
10102 }
10103 let a = self.parse_one_arg()?;
10104 Ok(Expr {
10105 kind: ExprKind::Telldir(Box::new(a)),
10106 line,
10107 })
10108 }
10109 "seekdir" => {
10110 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10111 return Ok(e);
10112 }
10113 let args = self.parse_builtin_args()?;
10114 if args.len() != 2 {
10115 return Err(self.syntax_err("seekdir requires two arguments", line));
10116 }
10117 Ok(Expr {
10118 kind: ExprKind::Seekdir {
10119 handle: Box::new(args[0].clone()),
10120 position: Box::new(args[1].clone()),
10121 },
10122 line,
10123 })
10124 }
10125 "eof" => {
10126 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10127 return Ok(e);
10128 }
10129 if matches!(self.peek(), Token::LParen) {
10130 self.advance();
10131 if matches!(self.peek(), Token::RParen) {
10132 self.advance();
10133 Ok(Expr {
10134 kind: ExprKind::Eof(None),
10135 line,
10136 })
10137 } else {
10138 let a = self.parse_expression()?;
10139 self.expect(&Token::RParen)?;
10140 Ok(Expr {
10141 kind: ExprKind::Eof(Some(Box::new(a))),
10142 line,
10143 })
10144 }
10145 } else {
10146 Ok(Expr {
10147 kind: ExprKind::Eof(None),
10148 line,
10149 })
10150 }
10151 }
10152 "system" => {
10153 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10154 return Ok(e);
10155 }
10156 let args = self.parse_builtin_args()?;
10157 Ok(Expr {
10158 kind: ExprKind::System(args),
10159 line,
10160 })
10161 }
10162 "exec" => {
10163 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10164 return Ok(e);
10165 }
10166 let args = self.parse_builtin_args()?;
10167 Ok(Expr {
10168 kind: ExprKind::Exec(args),
10169 line,
10170 })
10171 }
10172 "eval" => {
10173 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10174 return Ok(e);
10175 }
10176 let a = if matches!(self.peek(), Token::LBrace) {
10177 let block = self.parse_block()?;
10178 Expr {
10179 kind: ExprKind::CodeRef {
10180 params: vec![],
10181 body: block,
10182 },
10183 line,
10184 }
10185 } else {
10186 self.parse_one_arg_or_default()?
10187 };
10188 Ok(Expr {
10189 kind: ExprKind::Eval(Box::new(a)),
10190 line,
10191 })
10192 }
10193 "do" => {
10194 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10195 return Ok(e);
10196 }
10197 let a = self.parse_one_arg()?;
10198 Ok(Expr {
10199 kind: ExprKind::Do(Box::new(a)),
10200 line,
10201 })
10202 }
10203 "require" => {
10204 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10205 return Ok(e);
10206 }
10207 let a = self.parse_one_arg()?;
10208 Ok(Expr {
10209 kind: ExprKind::Require(Box::new(a)),
10210 line,
10211 })
10212 }
10213 "exit" => {
10214 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10215 return Ok(e);
10216 }
10217 if matches!(
10218 self.peek(),
10219 Token::Semicolon | Token::RBrace | Token::Eof | Token::PipeForward
10220 ) {
10221 Ok(Expr {
10222 kind: ExprKind::Exit(None),
10223 line,
10224 })
10225 } else {
10226 let a = self.parse_one_arg()?;
10227 Ok(Expr {
10228 kind: ExprKind::Exit(Some(Box::new(a))),
10229 line,
10230 })
10231 }
10232 }
10233 "chdir" => {
10234 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10235 return Ok(e);
10236 }
10237 let a = self.parse_one_arg_or_default()?;
10238 Ok(Expr {
10239 kind: ExprKind::Chdir(Box::new(a)),
10240 line,
10241 })
10242 }
10243 "mkdir" => {
10244 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10245 return Ok(e);
10246 }
10247 let args = self.parse_builtin_args()?;
10248 Ok(Expr {
10249 kind: ExprKind::Mkdir {
10250 path: Box::new(args[0].clone()),
10251 mode: args.get(1).cloned().map(Box::new),
10252 },
10253 line,
10254 })
10255 }
10256 "unlink" | "rm" => {
10257 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10258 return Ok(e);
10259 }
10260 let args = self.parse_builtin_args()?;
10261 Ok(Expr {
10262 kind: ExprKind::Unlink(args),
10263 line,
10264 })
10265 }
10266 "rename" => {
10267 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10268 return Ok(e);
10269 }
10270 let args = self.parse_builtin_args()?;
10271 if args.len() != 2 {
10272 return Err(self.syntax_err("rename requires two arguments", line));
10273 }
10274 Ok(Expr {
10275 kind: ExprKind::Rename {
10276 old: Box::new(args[0].clone()),
10277 new: Box::new(args[1].clone()),
10278 },
10279 line,
10280 })
10281 }
10282 "chmod" => {
10283 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10284 return Ok(e);
10285 }
10286 let args = self.parse_builtin_args()?;
10287 if args.len() < 2 {
10288 return Err(self.syntax_err("chmod requires mode and at least one file", line));
10289 }
10290 Ok(Expr {
10291 kind: ExprKind::Chmod(args),
10292 line,
10293 })
10294 }
10295 "chown" => {
10296 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10297 return Ok(e);
10298 }
10299 let args = self.parse_builtin_args()?;
10300 if args.len() < 3 {
10301 return Err(
10302 self.syntax_err("chown requires uid, gid, and at least one file", line)
10303 );
10304 }
10305 Ok(Expr {
10306 kind: ExprKind::Chown(args),
10307 line,
10308 })
10309 }
10310 "stat" => {
10311 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10312 return Ok(e);
10313 }
10314 let args = self.parse_builtin_args()?;
10315 let arg = if args.len() == 1 {
10316 args[0].clone()
10317 } else if args.is_empty() {
10318 Expr {
10319 kind: ExprKind::ScalarVar("_".into()),
10320 line,
10321 }
10322 } else {
10323 return Err(self.syntax_err("stat requires zero or one argument", line));
10324 };
10325 Ok(Expr {
10326 kind: ExprKind::Stat(Box::new(arg)),
10327 line,
10328 })
10329 }
10330 "lstat" => {
10331 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10332 return Ok(e);
10333 }
10334 let args = self.parse_builtin_args()?;
10335 let arg = if args.len() == 1 {
10336 args[0].clone()
10337 } else if args.is_empty() {
10338 Expr {
10339 kind: ExprKind::ScalarVar("_".into()),
10340 line,
10341 }
10342 } else {
10343 return Err(self.syntax_err("lstat requires zero or one argument", line));
10344 };
10345 Ok(Expr {
10346 kind: ExprKind::Lstat(Box::new(arg)),
10347 line,
10348 })
10349 }
10350 "link" => {
10351 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10352 return Ok(e);
10353 }
10354 let args = self.parse_builtin_args()?;
10355 if args.len() != 2 {
10356 return Err(self.syntax_err("link requires two arguments", line));
10357 }
10358 Ok(Expr {
10359 kind: ExprKind::Link {
10360 old: Box::new(args[0].clone()),
10361 new: Box::new(args[1].clone()),
10362 },
10363 line,
10364 })
10365 }
10366 "symlink" => {
10367 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10368 return Ok(e);
10369 }
10370 let args = self.parse_builtin_args()?;
10371 if args.len() != 2 {
10372 return Err(self.syntax_err("symlink requires two arguments", line));
10373 }
10374 Ok(Expr {
10375 kind: ExprKind::Symlink {
10376 old: Box::new(args[0].clone()),
10377 new: Box::new(args[1].clone()),
10378 },
10379 line,
10380 })
10381 }
10382 "readlink" => {
10383 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10384 return Ok(e);
10385 }
10386 let args = self.parse_builtin_args()?;
10387 let arg = if args.len() == 1 {
10388 args[0].clone()
10389 } else if args.is_empty() {
10390 Expr {
10391 kind: ExprKind::ScalarVar("_".into()),
10392 line,
10393 }
10394 } else {
10395 return Err(self.syntax_err("readlink requires zero or one argument", line));
10396 };
10397 Ok(Expr {
10398 kind: ExprKind::Readlink(Box::new(arg)),
10399 line,
10400 })
10401 }
10402 "files" => {
10403 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10404 return Ok(e);
10405 }
10406 let args = self.parse_builtin_args()?;
10407 Ok(Expr {
10408 kind: ExprKind::Files(args),
10409 line,
10410 })
10411 }
10412 "filesf" => {
10413 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10414 return Ok(e);
10415 }
10416 let args = self.parse_builtin_args()?;
10417 Ok(Expr {
10418 kind: ExprKind::Filesf(args),
10419 line,
10420 })
10421 }
10422 "fr" => {
10423 let args = self.parse_builtin_args()?;
10424 Ok(Expr {
10425 kind: ExprKind::FilesfRecursive(args),
10426 line,
10427 })
10428 }
10429 "dirs" | "d" => {
10430 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10431 return Ok(e);
10432 }
10433 let args = self.parse_builtin_args()?;
10434 Ok(Expr {
10435 kind: ExprKind::Dirs(args),
10436 line,
10437 })
10438 }
10439 "dr" => {
10440 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10441 return Ok(e);
10442 }
10443 let args = self.parse_builtin_args()?;
10444 Ok(Expr {
10445 kind: ExprKind::DirsRecursive(args),
10446 line,
10447 })
10448 }
10449 "sym_links" => {
10450 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10451 return Ok(e);
10452 }
10453 let args = self.parse_builtin_args()?;
10454 Ok(Expr {
10455 kind: ExprKind::SymLinks(args),
10456 line,
10457 })
10458 }
10459 "sockets" => {
10460 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10461 return Ok(e);
10462 }
10463 let args = self.parse_builtin_args()?;
10464 Ok(Expr {
10465 kind: ExprKind::Sockets(args),
10466 line,
10467 })
10468 }
10469 "pipes" => {
10470 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10471 return Ok(e);
10472 }
10473 let args = self.parse_builtin_args()?;
10474 Ok(Expr {
10475 kind: ExprKind::Pipes(args),
10476 line,
10477 })
10478 }
10479 "block_devices" => {
10480 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10481 return Ok(e);
10482 }
10483 let args = self.parse_builtin_args()?;
10484 Ok(Expr {
10485 kind: ExprKind::BlockDevices(args),
10486 line,
10487 })
10488 }
10489 "char_devices" => {
10490 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10491 return Ok(e);
10492 }
10493 let args = self.parse_builtin_args()?;
10494 Ok(Expr {
10495 kind: ExprKind::CharDevices(args),
10496 line,
10497 })
10498 }
10499 "glob" => {
10500 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10501 return Ok(e);
10502 }
10503 let args = self.parse_builtin_args()?;
10504 Ok(Expr {
10505 kind: ExprKind::Glob(args),
10506 line,
10507 })
10508 }
10509 "glob_par" => {
10510 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10511 return Ok(e);
10512 }
10513 let (args, progress) = self.parse_glob_par_or_par_sed_args()?;
10514 Ok(Expr {
10515 kind: ExprKind::GlobPar { args, progress },
10516 line,
10517 })
10518 }
10519 "par_sed" => {
10520 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10521 return Ok(e);
10522 }
10523 let (args, progress) = self.parse_glob_par_or_par_sed_args()?;
10524 Ok(Expr {
10525 kind: ExprKind::ParSed { args, progress },
10526 line,
10527 })
10528 }
10529 "bless" => {
10530 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10531 return Ok(e);
10532 }
10533 let args = self.parse_builtin_args()?;
10534 Ok(Expr {
10535 kind: ExprKind::Bless {
10536 ref_expr: Box::new(args[0].clone()),
10537 class: args.get(1).cloned().map(Box::new),
10538 },
10539 line,
10540 })
10541 }
10542 "caller" => {
10543 if matches!(self.peek(), Token::LParen) {
10544 self.advance();
10545 if matches!(self.peek(), Token::RParen) {
10546 self.advance();
10547 Ok(Expr {
10548 kind: ExprKind::Caller(None),
10549 line,
10550 })
10551 } else {
10552 let a = self.parse_expression()?;
10553 self.expect(&Token::RParen)?;
10554 Ok(Expr {
10555 kind: ExprKind::Caller(Some(Box::new(a))),
10556 line,
10557 })
10558 }
10559 } else {
10560 Ok(Expr {
10561 kind: ExprKind::Caller(None),
10562 line,
10563 })
10564 }
10565 }
10566 "wantarray" => {
10567 if matches!(self.peek(), Token::LParen) {
10568 self.advance();
10569 self.expect(&Token::RParen)?;
10570 }
10571 Ok(Expr {
10572 kind: ExprKind::Wantarray,
10573 line,
10574 })
10575 }
10576 "sub" | "fn" => {
10577 let (params, _prototype) = self.parse_sub_sig_or_prototype_opt()?;
10579 let body = self.parse_block()?;
10580 Ok(Expr {
10581 kind: ExprKind::CodeRef { params, body },
10582 line,
10583 })
10584 }
10585 _ => {
10586 if matches!(self.peek(), Token::FatArrow) {
10589 return Ok(Expr {
10590 kind: ExprKind::String(name),
10591 line,
10592 });
10593 }
10594 if matches!(self.peek(), Token::LParen) {
10596 self.advance();
10597 let args = self.parse_arg_list()?;
10598 self.expect(&Token::RParen)?;
10599 Ok(Expr {
10600 kind: ExprKind::FuncCall { name, args },
10601 line,
10602 })
10603 } else if self.peek().is_term_start()
10604 && !(matches!(self.peek(), Token::Ident(ref kw) if kw == "sub")
10605 && matches!(self.peek_at(1), Token::Ident(_)))
10606 && !(self.suppress_parenless_call > 0 && matches!(self.peek(), Token::Ident(_)))
10607 {
10608 let args = self.parse_list_until_terminator()?;
10616 Ok(Expr {
10617 kind: ExprKind::FuncCall { name, args },
10618 line,
10619 })
10620 } else {
10621 Ok(Expr {
10627 kind: ExprKind::Bareword(name),
10628 line,
10629 })
10630 }
10631 }
10632 }
10633 }
10634
10635 fn parse_print_like(
10636 &mut self,
10637 make: impl FnOnce(Option<String>, Vec<Expr>) -> ExprKind,
10638 ) -> PerlResult<Expr> {
10639 let line = self.peek_line();
10640 let handle = if let Token::Ident(ref h) = self.peek().clone() {
10642 if h.chars().all(|c| c.is_uppercase() || c == '_')
10643 && !matches!(self.peek(), Token::LParen)
10644 {
10645 let h = h.clone();
10646 let saved = self.pos;
10647 self.advance();
10648 if self.peek().is_term_start()
10650 || matches!(
10651 self.peek(),
10652 Token::DoubleString(_) | Token::BacktickString(_) | Token::SingleString(_)
10653 )
10654 {
10655 Some(h)
10656 } else {
10657 self.pos = saved;
10658 None
10659 }
10660 } else {
10661 None
10662 }
10663 } else if let Token::ScalarVar(ref v) = self.peek().clone() {
10664 let v = v.clone();
10674 if v == "_" {
10675 None
10676 } else {
10677 let saved = self.pos;
10678 self.advance();
10679 let next = self.peek().clone();
10680 let is_stmt_modifier = matches!(&next, Token::Ident(kw)
10681 if matches!(kw.as_str(), "if" | "unless" | "while" | "until" | "for" | "foreach"));
10682 if !is_stmt_modifier
10683 && !matches!(next, Token::LBracket | Token::LBrace)
10684 && (next.is_term_start()
10685 || matches!(
10686 next,
10687 Token::DoubleString(_)
10688 | Token::BacktickString(_)
10689 | Token::SingleString(_)
10690 ))
10691 {
10692 Some(format!("${v}"))
10694 } else {
10695 self.pos = saved;
10696 None
10697 }
10698 }
10699 } else {
10700 None
10701 };
10702 let args =
10707 if matches!(self.peek(), Token::LParen) && matches!(self.peek_at(1), Token::RParen) {
10708 let line_topic = self.peek_line();
10709 self.advance(); self.advance(); vec![Expr {
10712 kind: ExprKind::ScalarVar("_".into()),
10713 line: line_topic,
10714 }]
10715 } else {
10716 self.parse_list_until_terminator()?
10717 };
10718 Ok(Expr {
10719 kind: make(handle, args),
10720 line,
10721 })
10722 }
10723
10724 fn parse_block_list(&mut self) -> PerlResult<(Block, Expr)> {
10725 let block = self.parse_block()?;
10726 let block_end_line = self.prev_line();
10727 self.eat(&Token::Comma);
10728 if self.in_pipe_rhs()
10732 && (matches!(
10733 self.peek(),
10734 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof | Token::PipeForward
10735 ) || self.peek_line() > block_end_line)
10736 {
10737 let line = self.peek_line();
10738 return Ok((block, self.pipe_placeholder_list(line)));
10739 }
10740 let list = self.parse_expression()?;
10741 Ok((block, list))
10742 }
10743
10744 fn parse_comma_expr_list_with_timeout_tail(
10747 &mut self,
10748 paren: bool,
10749 ) -> PerlResult<(Vec<Expr>, Option<Expr>)> {
10750 let mut parts = vec![self.parse_assign_expr()?];
10751 loop {
10752 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
10753 break;
10754 }
10755 if paren && matches!(self.peek(), Token::RParen) {
10756 break;
10757 }
10758 if matches!(
10759 self.peek(),
10760 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof
10761 ) {
10762 break;
10763 }
10764 if self.peek_is_postfix_stmt_modifier_keyword() {
10765 break;
10766 }
10767 if let Token::Ident(ref kw) = self.peek().clone() {
10768 if kw == "timeout" && matches!(self.peek_at(1), Token::FatArrow) {
10769 self.advance();
10770 self.expect(&Token::FatArrow)?;
10771 let t = self.parse_assign_expr()?;
10772 return Ok((parts, Some(t)));
10773 }
10774 }
10775 parts.push(self.parse_assign_expr()?);
10776 }
10777 Ok((parts, None))
10778 }
10779
10780 fn parse_init_block_then_list_optional_progress(
10782 &mut self,
10783 ) -> PerlResult<(Expr, Block, Expr, Option<Expr>)> {
10784 let init = self.parse_assign_expr()?;
10785 self.expect(&Token::Comma)?;
10786 let block = self.parse_block_or_bareword_block()?;
10787 self.eat(&Token::Comma);
10788 let line = self.peek_line();
10789 if let Token::Ident(ref kw) = self.peek().clone() {
10790 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
10791 self.advance();
10792 self.expect(&Token::FatArrow)?;
10793 let prog = self.parse_assign_expr()?;
10794 return Ok((
10795 init,
10796 block,
10797 Expr {
10798 kind: ExprKind::List(vec![]),
10799 line,
10800 },
10801 Some(prog),
10802 ));
10803 }
10804 }
10805 if matches!(
10806 self.peek(),
10807 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof
10808 ) {
10809 return Ok((
10810 init,
10811 block,
10812 Expr {
10813 kind: ExprKind::List(vec![]),
10814 line,
10815 },
10816 None,
10817 ));
10818 }
10819 let mut parts = vec![self.parse_assign_expr()?];
10820 loop {
10821 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
10822 break;
10823 }
10824 if matches!(
10825 self.peek(),
10826 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof
10827 ) {
10828 break;
10829 }
10830 if self.peek_is_postfix_stmt_modifier_keyword() {
10831 break;
10832 }
10833 if let Token::Ident(ref kw) = self.peek().clone() {
10834 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
10835 self.advance();
10836 self.expect(&Token::FatArrow)?;
10837 let prog = self.parse_assign_expr()?;
10838 return Ok((init, block, merge_expr_list(parts), Some(prog)));
10839 }
10840 }
10841 parts.push(self.parse_assign_expr()?);
10842 }
10843 Ok((init, block, merge_expr_list(parts), None))
10844 }
10845
10846 fn parse_cluster_block_then_list_optional_progress(
10848 &mut self,
10849 ) -> PerlResult<(Expr, Block, Expr, Option<Expr>)> {
10850 let cluster = self.parse_assign_expr()?;
10851 let block = self.parse_block_or_bareword_block()?;
10852 self.eat(&Token::Comma);
10853 let line = self.peek_line();
10854 if let Token::Ident(ref kw) = self.peek().clone() {
10855 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
10856 self.advance();
10857 self.expect(&Token::FatArrow)?;
10858 let prog = self.parse_assign_expr_stop_at_pipe()?;
10859 return Ok((
10860 cluster,
10861 block,
10862 Expr {
10863 kind: ExprKind::List(vec![]),
10864 line,
10865 },
10866 Some(prog),
10867 ));
10868 }
10869 }
10870 let empty_list_ok = matches!(
10871 self.peek(),
10872 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof | Token::PipeForward
10873 ) || (self.in_pipe_rhs() && matches!(self.peek(), Token::Comma));
10874 if empty_list_ok {
10875 return Ok((
10876 cluster,
10877 block,
10878 Expr {
10879 kind: ExprKind::List(vec![]),
10880 line,
10881 },
10882 None,
10883 ));
10884 }
10885 let mut parts = vec![self.parse_assign_expr_stop_at_pipe()?];
10886 loop {
10887 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
10888 break;
10889 }
10890 if matches!(
10891 self.peek(),
10892 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof | Token::PipeForward
10893 ) {
10894 break;
10895 }
10896 if self.peek_is_postfix_stmt_modifier_keyword() {
10897 break;
10898 }
10899 if let Token::Ident(ref kw) = self.peek().clone() {
10900 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
10901 self.advance();
10902 self.expect(&Token::FatArrow)?;
10903 let prog = self.parse_assign_expr_stop_at_pipe()?;
10904 return Ok((cluster, block, merge_expr_list(parts), Some(prog)));
10905 }
10906 }
10907 parts.push(self.parse_assign_expr_stop_at_pipe()?);
10908 }
10909 Ok((cluster, block, merge_expr_list(parts), None))
10910 }
10911
10912 fn parse_block_then_list_optional_progress(
10921 &mut self,
10922 ) -> PerlResult<(Block, Expr, Option<Expr>)> {
10923 let block = self.parse_block_or_bareword_block()?;
10924 self.eat(&Token::Comma);
10925 let line = self.peek_line();
10926 if let Token::Ident(ref kw) = self.peek().clone() {
10927 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
10928 self.advance();
10929 self.expect(&Token::FatArrow)?;
10930 let prog = self.parse_assign_expr_stop_at_pipe()?;
10931 return Ok((
10932 block,
10933 Expr {
10934 kind: ExprKind::List(vec![]),
10935 line,
10936 },
10937 Some(prog),
10938 ));
10939 }
10940 }
10941 let empty_list_ok = matches!(
10947 self.peek(),
10948 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof | Token::PipeForward
10949 ) || (self.in_pipe_rhs() && matches!(self.peek(), Token::Comma));
10950 if empty_list_ok {
10951 return Ok((
10952 block,
10953 Expr {
10954 kind: ExprKind::List(vec![]),
10955 line,
10956 },
10957 None,
10958 ));
10959 }
10960 let mut parts = vec![self.parse_assign_expr_stop_at_pipe()?];
10961 loop {
10962 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
10963 break;
10964 }
10965 if matches!(
10966 self.peek(),
10967 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof | Token::PipeForward
10968 ) {
10969 break;
10970 }
10971 if self.peek_is_postfix_stmt_modifier_keyword() {
10972 break;
10973 }
10974 if let Token::Ident(ref kw) = self.peek().clone() {
10975 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
10976 self.advance();
10977 self.expect(&Token::FatArrow)?;
10978 let prog = self.parse_assign_expr_stop_at_pipe()?;
10979 return Ok((block, merge_expr_list(parts), Some(prog)));
10980 }
10981 }
10982 parts.push(self.parse_assign_expr_stop_at_pipe()?);
10983 }
10984 Ok((block, merge_expr_list(parts), None))
10985 }
10986
10987 fn parse_fan_count_and_block(&mut self, line: usize) -> PerlResult<(Option<Box<Expr>>, Block)> {
10989 if matches!(self.peek(), Token::LBrace) {
10991 let block = self.parse_block()?;
10992 return Ok((None, block));
10993 }
10994 let saved = self.pos;
10995 let first = self.parse_postfix()?;
10997 if matches!(self.peek(), Token::LBrace) {
10998 let block = self.parse_block()?;
11000 Ok((Some(Box::new(first)), block))
11001 } else if matches!(self.peek(), Token::Semicolon | Token::RBrace | Token::Eof)
11002 || (matches!(self.peek(), Token::Comma)
11003 && matches!(self.peek_at(1), Token::Ident(ref kw) if kw == "progress"))
11004 {
11005 let block = self.bareword_to_no_arg_block(first);
11007 Ok((None, block))
11008 } else if matches!(first.kind, ExprKind::Integer(_)) {
11009 self.eat(&Token::Comma);
11011 let body = self.parse_fan_blockless_body(line)?;
11012 Ok((Some(Box::new(first)), body))
11013 } else {
11014 self.pos = saved;
11017 let body = self.parse_fan_blockless_body(line)?;
11018 Ok((None, body))
11019 }
11020 }
11021
11022 fn parse_fan_blockless_body(&mut self, line: usize) -> PerlResult<Block> {
11024 if matches!(self.peek(), Token::LBrace) {
11025 return self.parse_block();
11026 }
11027 if let Token::Ident(ref name) = self.peek().clone() {
11029 if matches!(
11030 self.peek_at(1),
11031 Token::Comma | Token::Semicolon | Token::RBrace | Token::Eof | Token::PipeForward
11032 ) {
11033 let name = name.clone();
11034 self.advance();
11035 let body = Expr {
11036 kind: ExprKind::FuncCall { name, args: vec![] },
11037 line,
11038 };
11039 return Ok(vec![Statement::new(StmtKind::Expression(body), line)]);
11040 }
11041 }
11042 let expr = self.parse_assign_expr_stop_at_pipe()?;
11044 Ok(vec![Statement::new(StmtKind::Expression(expr), line)])
11045 }
11046
11047 fn bareword_to_no_arg_block(&self, expr: Expr) -> Block {
11050 let line = expr.line;
11051 let body = match &expr.kind {
11052 ExprKind::Bareword(name) => Expr {
11053 kind: ExprKind::FuncCall {
11054 name: name.clone(),
11055 args: vec![],
11056 },
11057 line,
11058 },
11059 _ => expr,
11060 };
11061 vec![Statement::new(StmtKind::Expression(body), line)]
11062 }
11063
11064 fn parse_block_or_bareword_block(&mut self) -> PerlResult<Block> {
11073 if matches!(self.peek(), Token::LBrace) {
11074 return self.parse_block();
11075 }
11076 let line = self.peek_line();
11077 if let Token::Ident(ref name) = self.peek().clone() {
11080 if matches!(
11081 self.peek_at(1),
11082 Token::Comma | Token::Semicolon | Token::RBrace | Token::Eof | Token::PipeForward
11083 ) {
11084 let name = name.clone();
11085 self.advance();
11086 let body = Expr {
11087 kind: ExprKind::FuncCall {
11088 name,
11089 args: vec![Expr {
11090 kind: ExprKind::ScalarVar("_".to_string()),
11091 line,
11092 }],
11093 },
11094 line,
11095 };
11096 return Ok(vec![Statement::new(StmtKind::Expression(body), line)]);
11097 }
11098 }
11099 let expr = self.parse_assign_expr_stop_at_pipe()?;
11101 Ok(vec![Statement::new(StmtKind::Expression(expr), line)])
11102 }
11103
11104 fn parse_block_or_bareword_block_no_args(&mut self) -> PerlResult<Block> {
11109 if matches!(self.peek(), Token::LBrace) {
11110 return self.parse_block();
11111 }
11112 let line = self.peek_line();
11113 if let Token::Ident(ref name) = self.peek().clone() {
11114 if matches!(
11115 self.peek_at(1),
11116 Token::Comma
11117 | Token::Semicolon
11118 | Token::RBrace
11119 | Token::Eof
11120 | Token::PipeForward
11121 | Token::Integer(_)
11122 ) {
11123 let name = name.clone();
11124 self.advance();
11125 let body = Expr {
11126 kind: ExprKind::FuncCall { name, args: vec![] },
11127 line,
11128 };
11129 return Ok(vec![Statement::new(StmtKind::Expression(body), line)]);
11130 }
11131 }
11132 let expr = self.parse_postfix()?;
11133 Ok(vec![Statement::new(StmtKind::Expression(expr), line)])
11134 }
11135
11136 fn is_known_bareword(name: &str) -> bool {
11144 Self::is_perl5_core(name) || Self::stryke_extension_name(name).is_some()
11145 }
11146
11147 fn is_try_builtin_name(name: &str) -> bool {
11153 crate::builtins::BUILTIN_ARMS
11154 .iter()
11155 .any(|arm| arm.contains(&name))
11156 }
11157
11158 fn is_perl5_core(name: &str) -> bool {
11163 matches!(
11164 name,
11165 "map" | "grep" | "sort" | "reverse" | "join" | "split"
11167 | "push" | "pop" | "shift" | "unshift" | "splice"
11168 | "pack" | "unpack"
11169 | "keys" | "values" | "each"
11171 | "chomp" | "chop" | "chr" | "ord" | "hex" | "oct"
11173 | "lc" | "uc" | "lcfirst" | "ucfirst"
11174 | "length" | "substr" | "index" | "rindex"
11175 | "sprintf" | "printf" | "print" | "say"
11176 | "pos" | "quotemeta" | "study"
11177 | "abs" | "int" | "sqrt" | "sin" | "cos" | "atan2"
11179 | "exp" | "log" | "rand" | "srand"
11180 | "time" | "localtime" | "gmtime"
11182 | "defined" | "undef" | "ref" | "scalar" | "wantarray"
11184 | "caller" | "delete" | "exists" | "bless" | "prototype"
11185 | "tie" | "untie" | "tied"
11186 | "open" | "close" | "read" | "readline" | "write" | "seek" | "tell"
11188 | "eof" | "binmode" | "getc" | "fileno" | "truncate"
11189 | "format" | "formline" | "select" | "vec"
11190 | "sysopen" | "sysread" | "sysseek" | "syswrite"
11191 | "stat" | "lstat" | "rename" | "unlink" | "utime"
11193 | "mkdir" | "rmdir" | "chdir" | "chmod" | "chown"
11194 | "glob" | "opendir" | "readdir" | "closedir"
11195 | "link" | "readlink" | "symlink"
11196 | "fcntl" | "flock" | "ioctl" | "pipe" | "dbmopen" | "dbmclose"
11198 | "msgctl" | "msgget" | "msgrcv" | "msgsnd"
11200 | "semctl" | "semget" | "semop"
11201 | "shmctl" | "shmget" | "shmread" | "shmwrite"
11202 | "system" | "exec" | "exit" | "die" | "warn" | "dump"
11204 | "fork" | "wait" | "waitpid" | "kill" | "alarm" | "sleep"
11205 | "chroot" | "times" | "umask" | "reset"
11206 | "getpgrp" | "setpgrp" | "getppid"
11207 | "getpriority" | "setpriority"
11208 | "socket" | "socketpair" | "connect" | "listen" | "accept" | "shutdown"
11210 | "send" | "recv" | "bind" | "setsockopt" | "getsockopt"
11211 | "getpeername" | "getsockname"
11212 | "getpwnam" | "getpwuid" | "getpwent" | "setpwent"
11214 | "getgrnam" | "getgrgid" | "getgrent" | "setgrent"
11215 | "getlogin"
11216 | "gethostbyname" | "gethostbyaddr" | "gethostent"
11217 | "getnetbyname" | "getnetent"
11218 | "getprotobyname" | "getprotoent"
11219 | "getservbyname" | "getservent"
11220 | "sethostent" | "setnetent" | "setprotoent" | "setservent"
11221 | "endpwent" | "endgrent"
11222 | "endhostent" | "endnetent" | "endprotoent" | "endservent"
11223 | "return" | "do" | "eval" | "require"
11225 | "my" | "our" | "local" | "use" | "no"
11226 | "sub" | "if" | "unless" | "while" | "until"
11227 | "for" | "foreach" | "last" | "next" | "redo" | "goto"
11228 | "not" | "and" | "or"
11229 | "qw" | "qq" | "q"
11231 | "BEGIN" | "END"
11233 )
11234 }
11235
11236 fn stryke_extension_name(name: &str) -> Option<&str> {
11239 match name {
11240 | "pmap" | "pmap_on" | "pflat_map" | "pflat_map_on" | "pmap_chunked"
11242 | "pgrep" | "pfor" | "psort" | "preduce" | "preduce_init" | "pmap_reduce"
11243 | "pcache" | "pchannel" | "pselect" | "puniq" | "pfirst" | "pany"
11244 | "fan" | "fan_cap" | "par_lines" | "par_walk" | "par_sed"
11245 | "par_find_files" | "par_line_count" | "pwatch" | "par_pipeline_stream"
11246 | "glob_par" | "ppool" | "barrier" | "pipeline" | "cluster"
11247 | "pmaps" | "pflat_maps" | "pgreps"
11248 | "fore" | "e" | "ep" | "flat_map" | "flat_maps" | "maps" | "filter" | "f" | "find_all" | "reduce" | "fold"
11250 | "inject" | "collect" | "uniq" | "distinct" | "any" | "all" | "none"
11251 | "first" | "detect" | "find" | "compact" | "concat" | "chain" | "reject" | "flatten" | "set"
11252 | "min_by" | "max_by" | "sort_by" | "tally" | "find_index"
11253 | "each_with_index" | "count" | "cnt" |"len" | "group_by" | "chunk_by"
11254 | "zip" | "chunk" | "chunked" | "sliding_window" | "windowed"
11255 | "enumerate" | "with_index" | "shuffle" | "shuffled"| "heap"
11256 | "take_while" | "drop_while" | "skip_while" | "tap" | "peek" | "partition"
11257 | "zip_with" | "count_by" | "skip" | "first_or"
11258 | "input" | "lines" | "words" | "chars" | "digits" | "letters" | "letters_uc" | "letters_lc"
11260 | "punctuation" | "punct"
11261 | "sentences" | "sents"
11262 | "paragraphs" | "paras" | "sections" | "sects"
11263 | "numbers" | "nums" | "graphemes" | "grs" | "columns" | "cols"
11264 | "trim" | "avg" | "stddev"
11265 | "squared" | "sq" | "square" | "cubed" | "cb" | "cube" | "expt" | "pow" | "pw"
11266 | "normalize" | "snake_case" | "camel_case" | "kebab_case"
11267 | "frequencies" | "freq" | "interleave" | "ddump" | "stringify" | "str" | "top"
11268 | "to_json" | "to_csv" | "to_toml" | "to_yaml" | "to_xml"
11269 | "to_html" | "to_markdown" | "to_table" | "xopen"
11270 | "clip" | "clipboard" | "paste" | "pbcopy" | "pbpaste" | "preview"
11271 | "sparkline" | "spark" | "bar_chart" | "bars" | "flame" | "flamechart"
11272 | "histo" | "gauge" | "spinner" | "spinner_start" | "spinner_stop"
11273 | "to_hash" | "to_set"
11274 | "to_file" | "read_lines" | "append_file" | "write_json" | "read_json"
11275 | "tempfile" | "tempdir" | "list_count" | "list_size" | "size"
11276 | "clamp" | "grep_v" | "select_keys" | "pluck" | "glob_match" | "which_all"
11277 | "dedup" | "nth" | "tail" | "take" | "drop" | "tee" | "range"
11278 | "inc" | "dec" | "elapsed"
11279 | "files" | "filesf" | "fr" | "dirs" | "d" | "dr" | "sym_links"
11281 | "sockets" | "pipes" | "block_devices" | "char_devices"
11282 | "basename" | "dirname" | "fileparse" | "realpath" | "canonpath"
11283 | "copy" | "move" | "spurt" | "read_bytes" | "which"
11284 | "getcwd" | "touch" | "gethostname" | "uname"
11285 | "csv_read" | "csv_write" | "dataframe" | "sqlite"
11287 | "fetch" | "fetch_json" | "fetch_async" | "fetch_async_json"
11288 | "par_fetch" | "par_csv_read" | "par_pipeline"
11289 | "json_encode" | "json_decode" | "json_jq"
11290 | "http_request" | "serve" | "ssh"
11291 | "html_parse" | "css_select" | "xml_parse" | "xpath"
11292 | "smtp_send"
11293 | "net_interfaces" | "net_ipv4" | "net_ipv6" | "net_mac"
11294 | "net_public_ip" | "net_dns" | "net_reverse_dns"
11295 | "net_ping" | "net_port_open" | "net_ports_scan"
11296 | "net_latency" | "net_download" | "net_headers"
11297 | "net_dns_servers" | "net_gateway" | "net_whois" | "net_hostname"
11298 | "git_log" | "git_status" | "git_diff" | "git_branches"
11300 | "git_tags" | "git_blame" | "git_authors" | "git_files"
11301 | "git_show" | "git_root"
11302 | "audio_convert" | "audio_info" | "id3_read" | "id3_write"
11304 | "to_pdf" | "pdf_text" | "pdf_pages"
11306 | "toml_encode" | "toml_decode"
11308 | "yaml_encode" | "yaml_decode"
11309 | "xml_encode" | "xml_decode"
11310 | "md5" | "sha1" | "sha224" | "sha256" | "sha384" | "sha512"
11312 | "sha3_256" | "s3_256" | "sha3_512" | "s3_512"
11313 | "shake128" | "shake256"
11314 | "hmac_sha256" | "hmac_sha1" | "hmac_sha384" | "hmac_sha512" | "hmac_md5"
11315 | "uuid" | "crc32"
11316 | "blake2b" | "b2b" | "blake2s" | "b2s" | "blake3" | "b3"
11317 | "ripemd160" | "rmd160" | "md4"
11318 | "xxh32" | "xxhash32" | "xxh64" | "xxhash64" | "xxh3" | "xxhash3" | "xxh3_128" | "xxhash3_128"
11319 | "murmur3" | "murmur3_32" | "murmur3_128"
11320 | "siphash" | "siphash_keyed"
11321 | "hkdf_sha256" | "hkdf" | "hkdf_sha512"
11322 | "poly1305" | "poly1305_mac"
11323 | "base32_encode" | "b32e" | "base32_decode" | "b32d"
11324 | "base58_encode" | "b58e" | "base58_decode" | "b58d"
11325 | "totp" | "totp_generate" | "totp_verify" | "hotp" | "hotp_generate"
11326 | "aes_cbc_encrypt" | "aes_cbc_enc" | "aes_cbc_decrypt" | "aes_cbc_dec"
11327 | "blowfish_encrypt" | "bf_enc" | "blowfish_decrypt" | "bf_dec"
11328 | "des3_encrypt" | "3des_enc" | "tdes_enc" | "des3_decrypt" | "3des_dec" | "tdes_dec"
11329 | "twofish_encrypt" | "tf_enc" | "twofish_decrypt" | "tf_dec"
11330 | "camellia_encrypt" | "cam_enc" | "camellia_decrypt" | "cam_dec"
11331 | "cast5_encrypt" | "cast5_enc" | "cast5_decrypt" | "cast5_dec"
11332 | "salsa20" | "salsa20_encrypt" | "salsa20_decrypt"
11333 | "xsalsa20" | "xsalsa20_encrypt" | "xsalsa20_decrypt"
11334 | "secretbox" | "secretbox_seal" | "secretbox_open"
11335 | "nacl_box_keygen" | "box_keygen" | "nacl_box" | "nacl_box_seal" | "box_seal"
11336 | "nacl_box_open" | "box_open"
11337 | "qr_ascii" | "qr" | "qr_png" | "qr_svg"
11338 | "barcode_code128" | "code128" | "barcode_code39" | "code39"
11339 | "barcode_ean13" | "ean13" | "barcode_svg"
11340 | "argon2_hash" | "argon2" | "argon2_verify"
11341 | "bcrypt_hash" | "bcrypt" | "bcrypt_verify"
11342 | "scrypt_hash" | "scrypt" | "scrypt_verify"
11343 | "pbkdf2" | "pbkdf2_derive"
11344 | "random_bytes" | "randbytes" | "random_bytes_hex" | "randhex"
11345 | "aes_encrypt" | "aes_enc" | "aes_decrypt" | "aes_dec"
11346 | "chacha_encrypt" | "chacha_enc" | "chacha_decrypt" | "chacha_dec"
11347 | "rsa_keygen" | "rsa_encrypt" | "rsa_enc" | "rsa_decrypt" | "rsa_dec"
11348 | "rsa_encrypt_pkcs1" | "rsa_decrypt_pkcs1" | "rsa_sign" | "rsa_verify"
11349 | "ecdsa_p256_keygen" | "p256_keygen" | "ecdsa_p256_sign" | "p256_sign"
11350 | "ecdsa_p256_verify" | "p256_verify"
11351 | "ecdsa_p384_keygen" | "p384_keygen" | "ecdsa_p384_sign" | "p384_sign"
11352 | "ecdsa_p384_verify" | "p384_verify"
11353 | "ecdsa_secp256k1_keygen" | "secp256k1_keygen"
11354 | "ecdsa_secp256k1_sign" | "secp256k1_sign"
11355 | "ecdsa_secp256k1_verify" | "secp256k1_verify"
11356 | "ecdh_p256" | "p256_dh" | "ecdh_p384" | "p384_dh"
11357 | "ed25519_keygen" | "ed_keygen" | "ed25519_sign" | "ed_sign"
11358 | "ed25519_verify" | "ed_verify"
11359 | "x25519_keygen" | "x_keygen" | "x25519_dh" | "x_dh"
11360 | "base64_encode" | "base64_decode"
11361 | "hex_encode" | "hex_decode"
11362 | "url_encode" | "url_decode"
11363 | "gzip" | "gunzip" | "gz" | "ugz" | "zstd" | "zstd_decode" | "zst" | "uzst"
11364 | "brotli" | "br" | "brotli_decode" | "ubr"
11365 | "xz" | "lzma" | "xz_decode" | "unxz" | "unlzma"
11366 | "bzip2" | "bz2" | "bzip2_decode" | "bunzip2" | "ubz2"
11367 | "lz4" | "lz4_decode" | "unlz4"
11368 | "snappy" | "snp" | "snappy_decode" | "unsnappy"
11369 | "lzw" | "lzw_decode" | "unlzw"
11370 | "tar_create" | "tar" | "tar_extract" | "untar" | "tar_list"
11371 | "tar_gz_create" | "tgz" | "tar_gz_extract" | "untgz"
11372 | "zip_create" | "zip_archive" | "zip_extract" | "unzip_archive" | "zip_list"
11373 | "erf" | "erfc" | "gamma" | "tgamma" | "lgamma" | "ln_gamma"
11375 | "digamma" | "psi" | "beta_fn" | "lbeta" | "ln_beta"
11376 | "betainc" | "beta_reg" | "gammainc" | "gamma_li"
11377 | "gammaincc" | "gamma_ui" | "gammainc_reg" | "gamma_lr"
11378 | "gammaincc_reg" | "gamma_ur"
11379 | "datetime_utc" | "datetime_now_tz"
11381 | "datetime_format_tz" | "datetime_add_seconds"
11382 | "datetime_from_epoch"
11383 | "datetime_parse_rfc3339" | "datetime_parse_local"
11384 | "datetime_strftime"
11385 | "dateseq" | "dategrep" | "dateround" | "datesort"
11386 | "jwt_encode" | "jwt_decode" | "jwt_decode_unsafe"
11388 | "log_info" | "log_warn" | "log_error"
11390 | "log_debug" | "log_trace" | "log_json" | "log_level"
11391 | "async" | "spawn" | "trace" | "timer" | "bench"
11393 | "eval_timeout" | "retry" | "rate_limit" | "every"
11394 | "gen" | "watch"
11395 | "assert_eq" | "assert_ne" | "assert_ok" | "assert_err"
11397 | "assert_true" | "assert_false"
11398 | "assert_gt" | "assert_lt" | "assert_ge" | "assert_le"
11399 | "assert_match" | "assert_contains" | "assert_near" | "assert_dies"
11400 | "test_run"
11401 | "mounts" | "du" | "du_tree" | "process_list"
11403 | "thread_count" | "pool_info" | "par_bench"
11404 | "slurp" | "cat" | "c" | "capture" | "pager" | "pg" | "less"
11406 | "stdin"
11407 | "__stryke_rust_compile"
11409 | "p" | "rev"
11411 | "even" | "odd" | "zero" | "nonzero"
11413 | "positive" | "pos_n" | "negative" | "neg_n"
11414 | "sign" | "negate" | "double" | "triple" | "half"
11415 | "identity" | "id"
11416 | "round" | "floor" | "ceil" | "ceiling" | "trunc" | "truncn"
11417 | "gcd" | "lcm" | "min2" | "max2"
11418 | "log2" | "log10" | "hypot"
11419 | "rad_to_deg" | "r2d" | "deg_to_rad" | "d2r"
11420 | "pow2" | "abs_diff"
11421 | "factorial" | "fact" | "fibonacci" | "fib"
11422 | "is_prime" | "is_square" | "is_power_of_two" | "is_pow2"
11423 | "cbrt" | "exp2" | "percent" | "pct" | "inverse"
11424 | "median" | "mode_val" | "variance"
11425 | "is_empty" | "is_blank" | "is_numeric"
11427 | "is_upper" | "is_lower" | "is_alpha" | "is_digit" | "is_alnum"
11428 | "is_space" | "is_whitespace"
11429 | "starts_with" | "sw" | "ends_with" | "ew" | "contains"
11430 | "capitalize" | "cap" | "swap_case" | "repeat"
11431 | "title_case" | "title" | "squish"
11432 | "pad_left" | "lpad" | "pad_right" | "rpad" | "center"
11433 | "truncate_at" | "shorten" | "reverse_str" | "rev_str"
11434 | "char_count" | "word_count" | "wc" | "line_count" | "lc_lines"
11435 | "is_array" | "is_arrayref" | "is_hash" | "is_hashref"
11437 | "is_code" | "is_coderef" | "is_ref"
11438 | "is_undef" | "is_defined" | "is_def"
11439 | "is_string" | "is_str" | "is_int" | "is_integer" | "is_float"
11440 | "invert" | "merge_hash"
11442 | "has_key" | "hk" | "has_any_key" | "has_all_keys"
11443 | "both" | "either" | "neither" | "xor_bool" | "bool_to_int" | "b2i"
11445 | "riffle" | "intersperse" | "every_nth"
11447 | "drop_n" | "take_n" | "rotate" | "swap_pairs"
11448 | "to_bin" | "bin_of" | "to_hex" | "hex_of" | "to_oct" | "oct_of"
11450 | "from_bin" | "from_hex" | "from_oct" | "to_base" | "from_base"
11451 | "bits_count" | "popcount" | "leading_zeros" | "lz"
11452 | "trailing_zeros" | "tz" | "bit_length" | "bitlen"
11453 | "bit_and" | "bit_or" | "bit_xor" | "bit_not"
11455 | "shift_left" | "shl" | "shift_right" | "shr"
11456 | "bit_set" | "bit_clear" | "bit_toggle" | "bit_test"
11457 | "c_to_f" | "f_to_c" | "c_to_k" | "k_to_c" | "f_to_k" | "k_to_f"
11459 | "miles_to_km" | "km_to_miles" | "miles_to_m" | "m_to_miles"
11461 | "feet_to_m" | "m_to_feet" | "inches_to_cm" | "cm_to_inches"
11462 | "yards_to_m" | "m_to_yards"
11463 | "kg_to_lbs" | "lbs_to_kg" | "g_to_oz" | "oz_to_g"
11465 | "stone_to_kg" | "kg_to_stone"
11466 | "bytes_to_kb" | "b_to_kb" | "kb_to_bytes" | "kb_to_b"
11468 | "bytes_to_mb" | "mb_to_bytes" | "bytes_to_gb" | "gb_to_bytes"
11469 | "kb_to_mb" | "mb_to_gb"
11470 | "bits_to_bytes" | "bytes_to_bits"
11471 | "seconds_to_minutes" | "s_to_m" | "minutes_to_seconds" | "m_to_s"
11473 | "seconds_to_hours" | "hours_to_seconds"
11474 | "seconds_to_days" | "days_to_seconds"
11475 | "minutes_to_hours" | "hours_to_minutes"
11476 | "hours_to_days" | "days_to_hours"
11477 | "is_leap_year" | "is_leap" | "days_in_month"
11479 | "month_name" | "month_short"
11480 | "weekday_name" | "weekday_short" | "quarter_of"
11481 | "now_ms" | "now_us" | "now_ns"
11483 | "unix_epoch" | "epoch" | "unix_epoch_ms" | "epoch_ms"
11484 | "rgb_to_hex" | "hex_to_rgb"
11486 | "ansi_red" | "ansi_green" | "ansi_yellow" | "ansi_blue"
11487 | "ansi_magenta" | "ansi_cyan" | "ansi_white" | "ansi_black"
11488 | "ansi_bold" | "ansi_dim" | "ansi_underline" | "ansi_reverse"
11489 | "strip_ansi"
11490 | "red" | "green" | "yellow" | "blue" | "magenta" | "purple" | "cyan"
11491 | "white" | "black" | "bold" | "dim" | "italic" | "underline"
11492 | "strikethrough" | "ansi_off" | "off" | "gray" | "grey"
11493 | "bright_red" | "bright_green" | "bright_yellow" | "bright_blue"
11494 | "bright_magenta" | "bright_cyan" | "bright_white"
11495 | "bg_red" | "bg_green" | "bg_yellow" | "bg_blue"
11496 | "bg_magenta" | "bg_cyan" | "bg_white" | "bg_black"
11497 | "red_bold" | "bold_red" | "green_bold" | "bold_green"
11498 | "yellow_bold" | "bold_yellow" | "blue_bold" | "bold_blue"
11499 | "magenta_bold" | "bold_magenta" | "cyan_bold" | "bold_cyan"
11500 | "white_bold" | "bold_white"
11501 | "blink" | "rapid_blink" | "hidden" | "overline"
11502 | "bg_bright_red" | "bg_bright_green" | "bg_bright_yellow" | "bg_bright_blue"
11503 | "bg_bright_magenta" | "bg_bright_cyan" | "bg_bright_white"
11504 | "rgb" | "bg_rgb" | "color256" | "c256" | "bg_color256" | "bg_c256"
11505 | "ipv4_to_int" | "int_to_ipv4"
11507 | "is_valid_ipv4" | "is_valid_ipv6" | "is_valid_email" | "is_valid_url"
11508 | "path_ext" | "path_stem" | "path_parent" | "path_join" | "path_split"
11510 | "strip_prefix" | "strip_suffix" | "ensure_prefix" | "ensure_suffix"
11511 | "const_fn" | "always_true" | "always_false"
11513 | "flip_args" | "first_arg" | "second_arg" | "last_arg"
11514 | "count_eq" | "count_ne" | "all_eq"
11516 | "all_distinct" | "all_unique" | "has_duplicates"
11517 | "sum_of" | "product_of" | "max_of" | "min_of" | "range_of"
11518 | "quote" | "single_quote" | "unquote"
11520 | "extract_between" | "ellipsis"
11521 | "coin_flip" | "dice_roll"
11523 | "random_int" | "random_float" | "random_bool"
11524 | "random_choice" | "random_between"
11525 | "random_string" | "random_alpha" | "random_digit"
11526 | "os_name" | "os_arch" | "num_cpus"
11528 | "pid" | "ppid" | "uid" | "gid"
11529 | "username" | "home_dir" | "temp_dir"
11530 | "mem_total" | "mem_free" | "mem_used"
11531 | "swap_total" | "swap_free" | "swap_used"
11532 | "disk_total" | "disk_free" | "disk_avail" | "disk_used"
11533 | "load_avg" | "sys_uptime" | "page_size"
11534 | "os_version" | "os_family" | "endianness" | "pointer_width"
11535 | "proc_mem" | "rss"
11536 | "transpose" | "unzip"
11538 | "run_length_encode" | "rle" | "run_length_decode" | "rld"
11539 | "sliding_pairs" | "consecutive_eq" | "flatten_deep"
11540 | "tan" | "asin" | "acos" | "atan"
11542 | "sinh" | "cosh" | "tanh" | "asinh" | "acosh" | "atanh"
11543 | "sqr" | "cube_fn"
11544 | "mod_op" | "ceil_div" | "floor_div"
11545 | "is_finite" | "is_infinite" | "is_inf" | "is_nan"
11546 | "degrees" | "radians"
11547 | "min_abs" | "max_abs"
11548 | "saturate" | "sat01" | "wrap_around"
11549 | "rot13" | "rot47" | "caesar_shift" | "reverse_words"
11551 | "count_vowels" | "count_consonants" | "is_vowel" | "is_consonant"
11552 | "first_word" | "last_word"
11553 | "left_str" | "head_str" | "right_str" | "tail_str" | "mid_str"
11554 | "lowercase" | "uppercase"
11555 | "pascal_case" | "pc_case"
11556 | "constant_case" | "upper_snake" | "dot_case" | "path_case"
11557 | "is_palindrome" | "hamming_distance"
11558 | "longest_common_prefix" | "lcp"
11559 | "ascii_ord" | "ascii_chr" | "count_char" | "indexes_of"
11560 | "replace_first" | "replace_all_str"
11561 | "contains_any" | "contains_all"
11562 | "starts_with_any" | "ends_with_any"
11563 | "is_pair" | "is_triple"
11565 | "is_sorted" | "is_asc" | "is_sorted_desc" | "is_desc"
11566 | "is_empty_arr" | "is_empty_hash"
11567 | "is_subset" | "is_superset" | "is_permutation"
11568 | "first_eq" | "last_eq"
11570 | "index_of" | "last_index_of" | "positions_of"
11571 | "batch" | "binary_search" | "bsearch" | "linear_search" | "lsearch"
11572 | "distinct_count" | "longest" | "shortest"
11573 | "array_union" | "list_union"
11574 | "array_intersection" | "list_intersection"
11575 | "array_difference" | "list_difference"
11576 | "symmetric_diff" | "group_of_n" | "chunk_n"
11577 | "repeat_list" | "cycle_n" | "random_sample" | "sample_n"
11578 | "pick_keys" | "pick" | "omit_keys" | "omit"
11580 | "map_keys_fn" | "map_values_fn"
11581 | "hash_size" | "hash_from_pairs" | "pairs_from_hash"
11582 | "hash_eq" | "keys_sorted" | "values_sorted" | "remove_keys"
11583 | "today" | "yesterday" | "tomorrow" | "is_weekend" | "is_weekday"
11585 | "json_pretty" | "json_minify" | "escape_json" | "json_escape"
11587 | "cmd_exists" | "env_get" | "env_has" | "env_keys"
11589 | "argc" | "script_name"
11590 | "has_stdin_tty" | "has_stdout_tty" | "has_stderr_tty"
11591 | "uuid_v4" | "nanoid" | "short_id" | "is_uuid" | "token"
11593 | "email_domain" | "email_local"
11595 | "url_host" | "url_path" | "url_query" | "url_scheme"
11596 | "file_size" | "fsize" | "file_mtime" | "mtime"
11598 | "file_atime" | "atime" | "file_ctime" | "ctime"
11599 | "is_symlink" | "is_readable" | "is_writable" | "is_executable"
11600 | "path_is_abs" | "path_is_rel"
11601 | "min_max" | "percentile" | "harmonic_mean" | "geometric_mean" | "zscore"
11603 | "sorted" | "sorted_desc" | "sorted_nums" | "sorted_by_length"
11604 | "reverse_list" | "list_reverse"
11605 | "without" | "without_nth" | "take_last" | "drop_last"
11606 | "pairwise" | "zipmap"
11607 | "format_bytes" | "human_bytes"
11608 | "format_duration" | "human_duration"
11609 | "format_number" | "group_number"
11610 | "format_percent" | "pad_number"
11611 | "spaceship" | "cmp_num" | "cmp_str"
11612 | "compare_versions" | "version_cmp"
11613 | "hash_insert" | "hash_update" | "hash_delete"
11614 | "matches_regex" | "re_match"
11615 | "count_regex_matches" | "regex_extract"
11616 | "regex_split_str" | "regex_replace_str"
11617 | "shuffle_chars" | "random_char" | "nth_word"
11618 | "head_lines" | "tail_lines" | "count_substring"
11619 | "is_valid_hex" | "hex_upper" | "hex_lower"
11620 | "ms_to_s" | "s_to_ms" | "ms_to_ns" | "ns_to_ms"
11621 | "us_to_ns" | "ns_to_us"
11622 | "liters_to_gallons" | "gallons_to_liters"
11623 | "liters_to_ml" | "ml_to_liters"
11624 | "cups_to_ml" | "ml_to_cups"
11625 | "newtons_to_lbf" | "lbf_to_newtons"
11626 | "joules_to_cal" | "cal_to_joules"
11627 | "watts_to_hp" | "hp_to_watts"
11628 | "pascals_to_psi" | "psi_to_pascals"
11629 | "bar_to_pascals" | "pascals_to_bar"
11630 | "match"
11632 | "fst" | "rest" | "rst" | "second" | "snd"
11634 | "last_clj" | "lastc" | "butlast" | "bl"
11635 | "ffirst" | "ffs" | "fnext" | "fne" | "nfirst" | "nfs" | "nnext" | "nne"
11636 | "cons" | "conj"
11637 | "peek_clj" | "pkc" | "pop_clj" | "popc"
11638 | "some" | "not_any" | "not_every"
11639 | "comp" | "compose" | "partial" | "constantly" | "complement" | "compl"
11640 | "fnil" | "juxt"
11641 | "memoize" | "memo" | "curry" | "once"
11642 | "deep_clone" | "dclone" | "deep_merge" | "dmerge" | "deep_equal" | "deq"
11643 | "iterate" | "iter" | "repeatedly" | "rptd" | "cycle" | "cyc"
11644 | "mapcat" | "mcat" | "keep" | "kp" | "remove_clj" | "remc"
11645 | "reductions" | "rdcs"
11646 | "partition_by" | "pby" | "partition_all" | "pall"
11647 | "split_at" | "spat" | "split_with" | "spw"
11648 | "assoc" | "dissoc" | "get_in" | "gin" | "assoc_in" | "ain" | "update_in" | "uin"
11649 | "into" | "empty_clj" | "empc" | "seq" | "vec_clj" | "vecc"
11650 | "apply" | "appl"
11651 | "divmod" | "dm" | "accumulate" | "accum" | "starmap" | "smap"
11653 | "zip_longest" | "zipl" | "combinations" | "comb" | "permutations" | "perm"
11654 | "cartesian_product" | "cprod" | "compress" | "cmpr" | "filterfalse" | "falf"
11655 | "islice" | "isl" | "chain_from" | "chfr" | "pairwise_iter" | "pwi"
11656 | "tee_iter" | "teei" | "groupby_iter" | "gbi"
11657 | "each_slice" | "eslice" | "each_cons" | "econs"
11658 | "one" | "none_match" | "nonem"
11659 | "find_index_fn" | "fidx" | "rindex_fn" | "ridx"
11660 | "minmax" | "mmx" | "minmax_by" | "mmxb"
11661 | "dig" | "values_at" | "vat" | "fetch_val" | "fv" | "slice_arr" | "sla"
11662 | "transform_keys" | "tkeys" | "transform_values" | "tvals"
11663 | "sum_by" | "sumb" | "uniq_by" | "uqb"
11664 | "flat_map_fn" | "fmf" | "then_fn" | "thfn" | "times_fn" | "timf"
11665 | "step" | "upto" | "downto"
11666 | "find_last" | "fndl" | "find_last_index" | "fndli"
11668 | "at_index" | "ati" | "replace_at" | "repa"
11669 | "to_sorted" | "tsrt" | "to_reversed" | "trev" | "to_spliced" | "tspl"
11670 | "flat_depth" | "fltd" | "fill_arr" | "filla" | "includes_val" | "incv"
11671 | "object_keys" | "okeys" | "object_values" | "ovals"
11672 | "object_entries" | "oents" | "object_from_entries" | "ofents"
11673 | "span_fn" | "spanf" | "break_fn" | "brkf" | "group_runs" | "gruns"
11675 | "nub" | "sort_on" | "srton"
11676 | "intersperse_val" | "isp" | "intercalate" | "ical"
11677 | "replicate_val" | "repv" | "elem_of" | "elof" | "not_elem" | "ntelm"
11678 | "lookup_assoc" | "lkpa" | "scanl" | "scanr" | "unfoldr" | "unfr"
11679 | "find_map" | "fndm" | "filter_map" | "fltm" | "fold_right" | "fldr"
11681 | "partition_either" | "peith" | "try_fold" | "tfld"
11682 | "map_while" | "mapw" | "inspect" | "insp"
11683 | "tally_by" | "talb" | "sole" | "chunk_while" | "chkw" | "count_while" | "cntw"
11685 | "insert_at" | "insa" | "delete_at" | "dela" | "update_at" | "upda"
11687 | "split_on" | "spon" | "words_from" | "wfrm" | "unwords" | "unwds"
11688 | "lines_from" | "lfrm" | "unlines" | "unlns"
11689 | "window_n" | "winn" | "adjacent_pairs" | "adjp"
11690 | "zip_all" | "zall" | "unzip_pairs" | "uzp"
11691 | "interpose" | "ipos" | "partition_n" | "partn"
11692 | "map_indexed" | "mapi" | "reduce_indexed" | "redi" | "filter_indexed" | "flti"
11693 | "group_by_fn" | "gbf" | "index_by" | "idxb" | "associate" | "assoc_fn"
11694 | "combinations_rep" | "combrep" | "inits" | "tails" | "subsequences" | "subseqs"
11696 | "nub_by" | "nubb" | "slice_when" | "slcw" | "slice_before" | "slcb" | "slice_after" | "slca"
11697 | "each_with_object" | "ewo" | "reduce_right" | "redr"
11698 | "is_sorted_by" | "issrtb" | "intersperse_with" | "ispw"
11699 | "running_reduce" | "runred" | "windowed_circular" | "wincirc"
11700 | "distinct_by" | "distb" | "average" | "mean" | "copy_within" | "cpyw"
11701 | "and_list" | "andl" | "or_list" | "orl" | "concat_map" | "cmap"
11702 | "elem_index" | "elidx" | "elem_indices" | "elidxs" | "find_indices" | "fndidxs"
11703 | "delete_first" | "delfst" | "delete_by" | "delby" | "insert_sorted" | "inssrt"
11704 | "union_list" | "unionl" | "intersect_list" | "intl"
11705 | "maximum_by" | "maxby" | "minimum_by" | "minby" | "batched" | "btch"
11706 | "match_all" | "mall" | "capture_groups" | "capg" | "is_match" | "ism"
11708 | "split_regex" | "splre" | "replace_regex" | "replre"
11709 | "is_ascii" | "isasc" | "to_ascii" | "toasc"
11710 | "char_at" | "chat" | "code_point_at" | "cpat" | "from_code_point" | "fcp"
11711 | "normalize_spaces" | "nrmsp" | "remove_whitespace" | "rmws"
11712 | "pluralize" | "plur" | "ordinalize" | "ordn"
11713 | "parse_int" | "pint" | "parse_float" | "pflt" | "parse_bool" | "pbool"
11714 | "levenshtein" | "lev" | "soundex" | "sdx" | "similarity" | "sim"
11715 | "common_prefix" | "cpfx" | "common_suffix" | "csfx"
11716 | "wrap_text" | "wrpt" | "dedent" | "ddt" | "indent" | "idt"
11717 | "lerp" | "inv_lerp" | "ilerp" | "smoothstep" | "smst" | "remap"
11719 | "dot_product" | "dotp" | "cross_product" | "crossp"
11720 | "magnitude" | "mag" | "normalize_vec" | "nrmv"
11721 | "distance" | "dist" | "manhattan_distance" | "mdist"
11722 | "covariance" | "cov" | "correlation" | "corr"
11723 | "iqr" | "quantile" | "qntl" | "clamp_int" | "clpi"
11724 | "in_range" | "inrng" | "wrap_range" | "wrprng"
11725 | "sum_squares" | "sumsq" | "rms" | "cumsum" | "csum" | "cumprod" | "cprod_acc" | "diff"
11726 | "add_days" | "addd" | "add_hours" | "addh" | "add_minutes" | "addm"
11728 | "diff_days" | "diffd" | "diff_hours" | "diffh"
11729 | "start_of_day" | "sod" | "end_of_day" | "eod"
11730 | "start_of_hour" | "soh" | "start_of_minute" | "som"
11731 | "urle" | "urld"
11733 | "html_encode" | "htmle" | "html_decode" | "htmld"
11734 | "adler32" | "adl32" | "fnv1a" | "djb2"
11735 | "is_credit_card" | "iscc" | "is_isbn10" | "isbn10" | "is_isbn13" | "isbn13"
11737 | "is_iban" | "isiban" | "is_hex_str" | "ishex" | "is_binary_str" | "isbin"
11738 | "is_octal_str" | "isoct" | "is_json" | "isjson" | "is_base64" | "isb64"
11739 | "is_semver" | "issv" | "is_slug" | "isslug" | "slugify" | "slug"
11740 | "mode_stat" | "mstat" | "sampn" | "weighted_sample" | "wsamp"
11742 | "shuffle_arr" | "shuf" | "argmax" | "amax" | "argmin" | "amin"
11743 | "argsort" | "asrt" | "rank" | "rnk" | "dense_rank" | "drnk"
11744 | "partition_point" | "ppt" | "lower_bound" | "lbound"
11745 | "upper_bound" | "ubound" | "equal_range" | "eqrng"
11746 | "matrix_add" | "madd" | "matrix_sub" | "msub" | "matrix_mult" | "mmult"
11748 | "matrix_scalar" | "mscal" | "matrix_identity" | "mident"
11749 | "matrix_zeros" | "mzeros" | "matrix_ones" | "mones"
11750 | "matrix_diag" | "mdiag" | "matrix_trace" | "mtrace"
11751 | "matrix_row" | "mrow" | "matrix_col" | "mcol"
11752 | "matrix_shape" | "mshape" | "matrix_det" | "mdet"
11753 | "matrix_scale" | "mat_scale" | "diagonal" | "diag"
11754 | "topological_sort" | "toposort" | "bfs_traverse" | "bfs"
11756 | "dfs_traverse" | "dfs" | "shortest_path_bfs" | "spbfs"
11757 | "connected_components_graph" | "ccgraph"
11758 | "has_cycle_graph" | "hascyc" | "is_bipartite_graph" | "isbip"
11759 | "is_ipv4_addr" | "isip4" | "is_ipv6_addr" | "isip6"
11761 | "is_mac_addr" | "ismac" | "is_port_num" | "isport"
11762 | "is_hostname_valid" | "ishost"
11763 | "is_iso_date" | "isisodt" | "is_iso_time" | "isisotm"
11764 | "is_iso_datetime" | "isisodtm"
11765 | "is_phone_num" | "isphone" | "is_us_zip" | "iszip"
11766 | "word_wrap_text" | "wwrap" | "center_text" | "ctxt"
11768 | "ljust_text" | "ljt" | "rjust_text" | "rjt" | "zfill_num" | "zfill"
11769 | "remove_all_str" | "rmall" | "replace_n_times" | "repln"
11770 | "find_all_indices" | "fndalli"
11771 | "text_between" | "txbtwn" | "text_before" | "txbef" | "text_after" | "txaft"
11772 | "text_before_last" | "txbefl" | "text_after_last" | "txaftl"
11773 | "is_even_num" | "iseven" | "is_odd_num" | "isodd"
11775 | "is_positive_num" | "ispos" | "is_negative_num" | "isneg"
11776 | "is_zero_num" | "iszero" | "is_whole_num" | "iswhole"
11777 | "log_with_base" | "logb" | "nth_root_of" | "nroot"
11778 | "frac_part" | "fracp" | "reciprocal_of" | "recip"
11779 | "copy_sign" | "cpsgn" | "fused_mul_add" | "fmadd"
11780 | "floor_mod" | "fmod" | "floor_div_op" | "fdivop"
11781 | "signum_of" | "sgnum" | "midpoint_of" | "midpt"
11782 | "longest_run" | "lrun" | "longest_increasing" | "linc"
11784 | "longest_decreasing" | "ldec" | "max_sum_subarray" | "maxsub"
11785 | "majority_element" | "majority" | "kth_largest" | "kthl"
11786 | "kth_smallest" | "kths" | "count_inversions" | "cinv"
11787 | "is_monotonic" | "ismono" | "equilibrium_index" | "eqidx"
11788 | "jaccard_index" | "jaccard" | "dice_coefficient" | "dicecoef"
11790 | "overlap_coefficient" | "overlapcoef"
11791 | "power_set" | "powerset" | "cartesian_power" | "cartpow"
11792 | "is_isogram" | "isiso" | "is_heterogram" | "ishet"
11794 | "hamdist" | "jaro_similarity" | "jarosim"
11795 | "longest_common_substring" | "lcsub"
11796 | "longest_common_subsequence" | "lcseq"
11797 | "count_words" | "wcount" | "count_lines" | "lcount"
11798 | "count_chars" | "ccount" | "count_bytes" | "bcount"
11799 | "binomial" | "binom" | "catalan" | "catn" | "pascal_row" | "pascrow"
11801 | "is_coprime" | "iscopr" | "euler_totient" | "etot"
11802 | "mobius" | "mob" | "is_squarefree" | "issqfr"
11803 | "digital_root" | "digroot" | "is_narcissistic" | "isnarc"
11804 | "is_harshad" | "isharsh" | "is_kaprekar" | "iskap"
11805 | "day_of_year" | "doy" | "week_of_year" | "woy"
11807 | "days_in_month_fn" | "daysinmo" | "is_valid_date" | "isvdate"
11808 | "age_in_years" | "ageyrs"
11809 | "when_true" | "when_false" | "if_else" | "clamp_fn"
11812 | "attempt" | "try_fn" | "safe_div" | "safe_mod" | "safe_sqrt" | "safe_log"
11813 | "juxt2" | "juxt3" | "tap_val" | "debug_val" | "converge"
11814 | "iterate_n" | "unfold" | "arity_of" | "is_callable"
11815 | "coalesce" | "default_to" | "fallback"
11816 | "apply_list" | "zip_apply" | "scan"
11817 | "keep_if" | "reject_if" | "group_consecutive"
11818 | "after_n" | "before_n" | "clamp_list" | "normalize_list" | "softmax"
11819
11820 | "matrix_multiply" | "mat_mul"
11824 | "identity_matrix" | "eye" | "zeros_matrix" | "zeros" | "ones_matrix" | "ones"
11825
11826
11827
11828 | "vec_normalize" | "unit_vec" | "vec_add" | "vec_sub" | "vec_scale"
11829 | "linspace" | "arange"
11830 | "re_test" | "re_find_all" | "re_groups" | "re_escape"
11832 | "re_split_limit" | "glob_to_regex" | "is_regex_valid"
11833 | "cwd" | "pwd_str" | "cpu_count" | "is_root" | "uptime_secs"
11835 | "env_pairs" | "env_set" | "env_remove" | "hostname_str" | "is_tty" | "signal_name"
11836 | "stack_new" | "queue_new" | "lru_new"
11838 | "counter" | "counter_most_common" | "defaultdict" | "ordered_set"
11839 | "bitset_new" | "bitset_set" | "bitset_test" | "bitset_clear"
11840 | "abs_ceil" | "abs_each" | "abs_floor" | "ceil_each" | "dec_each"
11842 | "double_each" | "floor_each" | "half_each" | "inc_each" | "length_each"
11843 | "negate_each" | "not_each" | "offset_each" | "reverse_each" | "round_each"
11844 | "scale_each" | "sqrt_each" | "square_each" | "to_float_each" | "to_int_each"
11845 | "trim_each" | "type_each" | "upcase_each" | "downcase_each" | "bool_each"
11846 | "avogadro" | "boltzmann" | "golden_ratio" | "gravity" | "ln10" | "ln2"
11848 | "planck" | "speed_of_light" | "sqrt2"
11849 | "bmi_calc" | "compound_interest" | "dew_point" | "discount_amount"
11851 | "force_mass_acc" | "freq_wavelength" | "future_value" | "haversine"
11852 | "heat_index" | "kinetic_energy" | "margin_price" | "markup_price"
11853 | "mortgage_payment" | "ohms_law_i" | "ohms_law_r" | "ohms_law_v"
11854 | "potential_energy" | "present_value" | "simple_interest" | "speed_distance_time"
11855 | "tax_amount" | "tip_amount" | "wavelength_freq" | "wind_chill"
11856 | "angle_between_deg" | "approx_eq" | "chebyshev_distance" | "copysign"
11858 | "cosine_similarity" | "cube_root" | "entropy" | "float_bits" | "fma"
11859 | "int_bits" | "jaccard_similarity" | "log_base" | "mae" | "mse" | "nth_root"
11860 | "r_squared" | "reciprocal" | "relu" | "rmse" | "rotate_point" | "round_to"
11861 | "sigmoid" | "signum" | "square_root"
11862 | "cubes_seq" | "fibonacci_seq" | "powers_of_seq" | "primes_seq"
11864 | "squares_seq" | "triangular_seq"
11865 | "alternate_case" | "angle_bracket" | "bracket" | "byte_length"
11867 | "bytes_to_hex_str" | "camel_words" | "char_length" | "chars_to_string"
11868 | "chomp_str" | "chop_str" | "filter_chars" | "from_csv_line" | "hex_to_bytes"
11869 | "insert_str" | "intersperse_char" | "ljust" | "map_chars" | "mirror_string"
11870 | "normalize_whitespace" | "only_alnum" | "only_alpha" | "only_ascii"
11871 | "only_digits" | "parenthesize" | "remove_str" | "repeat_string" | "rjust"
11872 | "sentence_case" | "string_count" | "string_sort" | "string_to_chars"
11873 | "string_unique_chars" | "substring" | "to_csv_line" | "trim_left" | "trim_right"
11874 | "xor_strings"
11875 | "adjacent_difference" | "append_elem" | "consecutive_pairs" | "contains_elem"
11877 | "count_elem" | "drop_every" | "duplicate_count" | "elem_at" | "find_first"
11878 | "first_elem" | "flatten_once" | "fold_left" | "from_digits" | "from_pairs"
11879 | "group_by_size" | "hash_filter_keys" | "hash_from_list" | "hash_map_values"
11880 | "hash_merge_deep" | "hash_to_list" | "hash_zip" | "head_n" | "histogram_bins"
11881 | "index_of_elem" | "init_list" | "interleave_lists" | "last_elem" | "least_common"
11882 | "list_compact" | "list_eq" | "list_flatten_deep" | "max_list" | "mean_list"
11883 | "min_list" | "mode_list" | "most_common" | "partition_two" | "prefix_sums"
11884 | "prepend" | "product_list" | "remove_at" | "remove_elem" | "remove_first_elem"
11885 | "repeat_elem" | "running_max" | "running_min" | "sample_one" | "scan_left"
11886 | "second_elem" | "span" | "suffix_sums" | "sum_list" | "tail_n" | "take_every"
11887 | "third_elem" | "to_array" | "to_pairs" | "trimmed_mean" | "unique_count_of"
11888 | "wrap_index" | "digits_of"
11889 | "all_match" | "any_match" | "is_between" | "is_blank_or_nil" | "is_divisible_by"
11891 | "is_email" | "is_even" | "is_falsy" | "is_fibonacci" | "is_hex_color"
11892 | "is_in_range" | "is_ipv4" | "is_multiple_of" | "is_negative" | "is_nil"
11893 | "is_nonzero" | "is_odd" | "is_perfect_square" | "is_positive" | "is_power_of"
11894 | "is_prefix" | "is_present" | "is_strictly_decreasing" | "is_strictly_increasing"
11895 | "is_suffix" | "is_triangular" | "is_truthy" | "is_url" | "is_whole" | "is_zero"
11896 | "count_digits" | "count_letters" | "count_lower" | "count_match"
11898 | "count_punctuation" | "count_spaces" | "count_upper" | "defined_count"
11899 | "empty_count" | "falsy_count" | "nonempty_count" | "numeric_count"
11900 | "truthy_count" | "undef_count"
11901 | "assert_type" | "between" | "clamp_each" | "die_if" | "die_unless"
11903 | "join_colons" | "join_commas" | "join_dashes" | "join_dots" | "join_lines"
11904 | "join_pipes" | "join_slashes" | "join_spaces" | "join_tabs" | "measure"
11905 | "max_float" | "min_float" | "noop_val" | "nop" | "pass" | "pred" | "succ"
11906 | "tap_debug" | "to_bool" | "to_float" | "to_int" | "to_string" | "void"
11907 | "range_exclusive" | "range_inclusive"
11908 | "aliquot_sum" | "autocorrelation" | "bell_number" | "cagr" | "coeff_of_variation"
11910 | "collatz_length" | "collatz_sequence" | "convolution" | "cross_entropy"
11911 | "depreciation_double" | "depreciation_linear" | "discount" | "divisors"
11912 | "epsilon" | "euclidean_distance" | "euler_number" | "exponential_moving_average"
11913 | "f64_max" | "f64_min" | "fft_magnitude" | "goldbach" | "i64_max" | "i64_min"
11914 | "kurtosis" | "linear_regression" | "look_and_say" | "lucas" | "luhn_check"
11915 | "mean_absolute_error" | "mean_squared_error" | "median_absolute_deviation"
11916 | "minkowski_distance" | "moving_average" | "multinomial" | "neg_inf" | "npv"
11917 | "num_divisors" | "partition_number" | "pascals_triangle" | "skewness"
11918 | "standard_error" | "subfactorial" | "sum_divisors" | "totient_sum"
11919 | "tribonacci" | "weighted_mean" | "winsorize"
11920 | "chi_square_stat" | "describe" | "five_number_summary"
11922 | "gini" | "gini_coefficient" | "lorenz_curve" | "outliers_iqr"
11923 | "percentile_rank" | "quartiles" | "sample_stddev" | "sample_variance"
11924 | "spearman_correlation" | "t_test_one_sample" | "t_test_two_sample"
11925 | "z_score" | "z_scores"
11926 | "abundant_numbers" | "deficient_numbers" | "is_abundant" | "is_deficient"
11928 | "is_pentagonal" | "is_perfect" | "is_smith" | "next_prime" | "nth_prime"
11929 | "pentagonal_number" | "perfect_numbers" | "prev_prime" | "prime_factors"
11930 | "prime_pi" | "primes_up_to" | "triangular_number" | "twin_primes"
11931 | "area_circle" | "area_ellipse" | "area_rectangle" | "area_trapezoid" | "area_triangle"
11933 | "bearing" | "circumference" | "cone_volume" | "cylinder_volume" | "heron_area"
11934 | "midpoint" | "perimeter_rectangle" | "perimeter_triangle" | "point_distance"
11935 | "polygon_area" | "slope" | "sphere_surface" | "sphere_volume" | "triangle_hypotenuse"
11936 | "angle_between" | "arc_length" | "bounding_box" | "centroid"
11938 | "circle_from_three_points" | "convex_hull" | "ellipse_perimeter"
11939 | "frustum_volume" | "haversine_distance" | "line_intersection"
11940 | "point_in_polygon" | "polygon_perimeter" | "pyramid_volume"
11941 | "reflect_point" | "scale_point" | "sector_area"
11942 | "torus_surface" | "torus_volume" | "translate_point"
11943 | "vector_angle" | "vector_cross" | "vector_dot" | "vector_magnitude" | "vector_normalize"
11944 | "avogadro_number" | "boltzmann_constant" | "electron_mass" | "elementary_charge"
11946 | "gravitational_constant" | "phi" | "pi" | "planck_constant" | "proton_mass"
11947 | "sol" | "tau"
11948 | "bac_estimate" | "bmi" | "break_even" | "margin" | "markup" | "roi" | "tax" | "tip"
11950 | "amortization_schedule" | "black_scholes_call" | "black_scholes_put"
11952 | "bond_price" | "bond_yield" | "capm" | "continuous_compound"
11953 | "discounted_payback" | "duration" | "irr"
11954 | "max_drawdown" | "modified_duration" | "nper" | "num_periods" | "payback_period"
11955 | "pmt" | "pv" | "rule_of_72" | "sharpe_ratio" | "sortino_ratio"
11956 | "wacc" | "xirr"
11957 | "acronym" | "atbash" | "bigrams" | "camel_to_snake" | "char_frequencies"
11959 | "chunk_string" | "collapse_whitespace" | "dedent_text" | "indent_text"
11960 | "initials" | "leetspeak" | "mask_string" | "ngrams" | "pig_latin"
11961 | "remove_consonants" | "remove_vowels" | "reverse_each_word" | "snake_to_camel"
11962 | "sort_words" | "string_distance" | "string_multiply" | "strip_html"
11963 | "trigrams" | "unique_words" | "word_frequencies" | "zalgo"
11964 | "braille_encode" | "double_metaphone" | "metaphone" | "morse_decode"
11966 | "morse_encode" | "nato_phonetic" | "phonetic_digit" | "subscript" | "superscript"
11967 | "to_emoji_num"
11968 | "int_to_roman" | "roman_add" | "roman_numeral_list" | "roman_to_int"
11970 | "base_convert" | "binary_to_gray" | "gray_code_sequence" | "gray_to_binary"
11972 | "ansi_256" | "ansi_truecolor" | "color_blend" | "color_complement"
11974 | "color_darken" | "color_distance" | "color_grayscale" | "color_invert"
11975 | "color_lighten" | "hsl_to_rgb" | "hsv_to_rgb" | "random_color"
11976 | "rgb_to_hsl" | "rgb_to_hsv"
11977 | "matrix_flatten" | "matrix_from_rows" | "matrix_hadamard" | "matrix_inverse"
11979 | "matrix_map" | "matrix_max" | "matrix_min" | "matrix_power" | "matrix_sum"
11980 | "matrix_transpose"
11981 | "binary_insert" | "bucket" | "clamp_array" | "group_consecutive_by"
11983 | "histogram" | "merge_sorted" | "next_permutation" | "normalize_array"
11984 | "normalize_range" | "peak_detect" | "range_compress" | "range_expand"
11985 | "reservoir_sample" | "run_length_decode_str" | "run_length_encode_str"
11986 | "zero_crossings"
11987 | "apply_window" | "bandpass_filter" | "cross_correlation" | "dft"
11989 | "downsample" | "energy" | "envelope" | "highpass_filter" | "idft"
11990 | "lowpass_filter" | "median_filter" | "normalize_signal" | "phase_spectrum"
11991 | "power_spectrum" | "resample" | "spectral_centroid" | "spectrogram" | "upsample"
11992 | "window_blackman" | "window_hamming" | "window_hann" | "window_kaiser"
11993 | "is_anagram" | "is_balanced_parens" | "is_control" | "is_numeric_string"
11995 | "is_pangram" | "is_printable" | "is_valid_cidr" | "is_valid_cron"
11996 | "is_valid_hex_color" | "is_valid_latitude" | "is_valid_longitude" | "is_valid_mime"
11997 | "eval_rpn" | "fizzbuzz" | "game_of_life_step" | "mandelbrot_char"
11999 | "sierpinski" | "tower_of_hanoi" | "truth_table"
12000 | "byte_size" | "degrees_to_compass" | "to_string_val" | "type_of"
12002 | "quadratic_roots" | "quadratic_discriminant" | "arithmetic_series"
12004 | "geometric_series" | "stirling_approx"
12005 | "double_factorial" | "rising_factorial" | "falling_factorial"
12006 | "gamma_approx" | "erf_approx" | "normal_pdf" | "normal_cdf"
12007 | "poisson_pmf" | "exponential_pdf" | "inverse_lerp"
12008 | "map_range"
12009 | "momentum" | "impulse" | "work" | "power_phys" | "torque" | "angular_velocity"
12011 | "centripetal_force" | "escape_velocity" | "orbital_velocity" | "orbital_period"
12012 | "gravitational_force" | "coulomb_force" | "electric_field" | "capacitance"
12013 | "capacitor_energy" | "inductor_energy" | "resonant_frequency"
12014 | "rc_time_constant" | "rl_time_constant" | "impedance_rlc"
12015 | "relativistic_mass" | "lorentz_factor" | "time_dilation" | "length_contraction"
12016 | "relativistic_energy" | "rest_energy" | "de_broglie_wavelength"
12017 | "photon_energy" | "photon_energy_wavelength" | "schwarzschild_radius"
12018 | "stefan_boltzmann" | "wien_displacement" | "ideal_gas_pressure" | "ideal_gas_volume"
12019 | "projectile_range" | "projectile_max_height" | "projectile_time"
12020 | "spring_force" | "spring_energy" | "pendulum_period" | "doppler_frequency"
12021 | "decibel_ratio" | "snells_law" | "brewster_angle" | "critical_angle"
12022 | "lens_power" | "thin_lens" | "magnification_lens"
12023 | "euler_mascheroni" | "apery_constant" | "feigenbaum_delta" | "feigenbaum_alpha"
12025 | "catalan_constant" | "khinchin_constant" | "glaisher_constant"
12026 | "plastic_number" | "silver_ratio" | "supergolden_ratio"
12027 | "vacuum_permittivity" | "vacuum_permeability" | "coulomb_constant"
12029 | "fine_structure_constant" | "rydberg_constant" | "bohr_radius"
12030 | "bohr_magneton" | "nuclear_magneton" | "stefan_boltzmann_constant"
12031 | "wien_constant" | "gas_constant" | "faraday_constant" | "neutron_mass"
12032 | "atomic_mass_unit" | "earth_mass" | "earth_radius" | "sun_mass" | "sun_radius"
12033 | "astronomical_unit" | "light_year" | "parsec" | "hubble_constant"
12034 | "planck_length" | "planck_time" | "planck_mass" | "planck_temperature"
12035 | "matrix_solve" | "msolve" | "solve"
12037 | "matrix_lu" | "mlu" | "matrix_qr" | "mqr"
12038 | "matrix_eigenvalues" | "meig" | "eigenvalues" | "eig"
12039 | "matrix_norm" | "mnorm" | "matrix_cond" | "mcond" | "cond"
12040 | "matrix_pinv" | "mpinv" | "pinv"
12041 | "matrix_cholesky" | "mchol" | "cholesky"
12042 | "matrix_det_general" | "mdetg" | "det"
12043 | "welch_ttest" | "welcht" | "paired_ttest" | "pairedt"
12045 | "cohen_d" | "cohend" | "anova_oneway" | "anova" | "anova1"
12046 | "spearman_corr" | "rho" | "kendall_tau" | "kendall" | "ktau"
12047 | "confidence_interval" | "ci"
12048 | "beta_pdf" | "betapdf" | "gamma_pdf" | "gammapdf"
12050 | "chi2_pdf" | "chi2pdf" | "chi_squared_pdf"
12051 | "t_pdf" | "tpdf" | "student_pdf"
12052 | "f_pdf" | "fpdf" | "fisher_pdf"
12053 | "lognormal_pdf" | "lnormpdf" | "weibull_pdf" | "weibpdf"
12054 | "cauchy_pdf" | "cauchypdf" | "laplace_pdf" | "laplacepdf"
12055 | "pareto_pdf" | "paretopdf"
12056 | "lagrange_interp" | "lagrange" | "linterp"
12058 | "cubic_spline" | "cspline" | "spline"
12059 | "poly_eval" | "polyval" | "polynomial_fit" | "polyfit"
12060 | "trapz" | "trapezoid" | "simpson" | "simps"
12062 | "numerical_diff" | "numdiff" | "diff_array"
12063 | "cumtrapz" | "cumulative_trapz"
12064 | "bisection" | "bisect" | "newton_method" | "newton" | "newton_raphson"
12066 | "golden_section" | "golden" | "gss"
12067 | "rk4" | "runge_kutta" | "rk4_ode" | "euler_ode" | "euler_method"
12069 | "dijkstra" | "shortest_path" | "bellman_ford" | "bellmanford"
12071 | "floyd_warshall" | "floydwarshall" | "apsp"
12072 | "prim_mst" | "mst" | "prim"
12073 | "cot" | "sec" | "csc" | "acot" | "asec" | "acsc" | "sinc" | "versin" | "versine"
12075 | "leaky_relu" | "lrelu" | "elu" | "selu" | "gelu"
12077 | "silu" | "swish" | "mish" | "softplus"
12078 | "hard_sigmoid" | "hardsigmoid" | "hard_swish" | "hardswish"
12079 | "bessel_j0" | "j0" | "bessel_j1" | "j1"
12081 | "lambert_w" | "lambertw" | "productlog"
12082 | "mod_exp" | "modexp" | "powmod"
12084 | "mod_inv" | "modinv" | "chinese_remainder" | "crt"
12085 | "miller_rabin" | "millerrabin" | "is_probable_prime"
12086 | "derangements" | "stirling2" | "stirling_second"
12088 | "bernoulli_number" | "bernoulli" | "harmonic_number" | "harmonic"
12089 | "drag_force" | "fdrag" | "ideal_gas" | "pv_nrt"
12091 | "bs_delta" | "bsdelta" | "option_delta"
12093 | "bs_gamma" | "bsgamma" | "option_gamma"
12094 | "bs_vega" | "bsvega" | "option_vega"
12095 | "bs_theta" | "bstheta" | "option_theta"
12096 | "bs_rho" | "bsrho" | "option_rho"
12097 | "bond_duration" | "mac_duration"
12098 | "dct" | "idct" | "goertzel" | "chirp" | "chirp_signal"
12100 | "base85_encode" | "b85e" | "ascii85_encode" | "a85e"
12102 | "base85_decode" | "b85d" | "ascii85_decode" | "a85d"
12103 | "pnorm" | "qnorm" | "pbinom" | "dbinom" | "ppois"
12105 | "punif" | "pexp" | "pweibull" | "plnorm" | "pcauchy"
12106 | "rbind" | "cbind"
12108 | "row_sums" | "rowSums" | "col_sums" | "colSums"
12109 | "row_means" | "rowMeans" | "col_means" | "colMeans"
12110 | "outer_product" | "outer" | "crossprod" | "tcrossprod"
12111 | "nrow" | "ncol" | "prop_table" | "proptable"
12112 | "cummax" | "cummin" | "scale_vec" | "scale"
12114 | "which_fn" | "tabulate"
12115 | "duplicated" | "duped" | "rev_vec"
12116 | "seq_fn" | "rep_fn" | "rep"
12117 | "cut_bins" | "cut" | "find_interval" | "findInterval"
12118 | "ecdf_fn" | "ecdf" | "density_est" | "density"
12119 | "embed_ts" | "embed"
12120 | "shapiro_test" | "shapiro" | "ks_test" | "ks"
12122 | "wilcox_test" | "wilcox" | "mann_whitney"
12123 | "prop_test" | "proptest" | "binom_test" | "binomtest"
12124 | "sapply" | "tapply" | "do_call" | "docall"
12126 | "kmeans" | "prcomp" | "pca"
12128 | "rnorm" | "runif" | "rexp" | "rbinom" | "rpois" | "rgeom"
12130 | "rgamma" | "rbeta" | "rchisq" | "rt" | "rf"
12131 | "rweibull" | "rlnorm" | "rcauchy"
12132 | "qunif" | "qexp" | "qweibull" | "qlnorm" | "qcauchy"
12134 | "pgamma" | "pbeta" | "pchisq" | "pt_cdf" | "pt" | "pf_cdf" | "pf"
12136 | "dgeom" | "dunif" | "dnbinom" | "dhyper"
12138 | "lowess" | "loess" | "approx_fn" | "approx"
12140 | "lm_fit" | "lm"
12142 | "qgamma" | "qbeta" | "qchisq" | "qt_fn" | "qt" | "qf_fn" | "qf"
12144 | "qbinom" | "qpois"
12145 | "acf_fn" | "acf" | "pacf_fn" | "pacf"
12147 | "diff_lag" | "diff_ts" | "ts_filter" | "filter_ts"
12148 | "predict_lm" | "predict" | "confint_lm" | "confint"
12150 | "cor_matrix" | "cor_mat" | "cov_matrix" | "cov_mat"
12152 | "mahalanobis" | "mahal" | "dist_matrix" | "dist_mat"
12153 | "hclust" | "cutree" | "weighted_var" | "wvar" | "cov2cor"
12154 | "scatter_svg" | "scatter_plot" | "line_svg" | "line_plot"
12156 | "plot_svg" | "hist_svg" | "histogram_svg"
12157 | "boxplot_svg" | "box_plot" | "bar_svg" | "barchart_svg"
12158 | "pie_svg" | "pie_chart" | "heatmap_svg" | "heatmap"
12159 | "donut_svg" | "donut" | "area_svg" | "area_chart"
12160 | "hbar_svg" | "hbar" | "radar_svg" | "radar" | "spider"
12161 | "candlestick_svg" | "candlestick" | "ohlc"
12162 | "violin_svg" | "violin" | "cor_heatmap" | "cor_matrix_svg"
12163 | "stacked_bar_svg" | "stacked_bar"
12164 | "wordcloud_svg" | "wordcloud" | "wcloud"
12165 | "treemap_svg" | "treemap"
12166 | "pvw"
12167 | "cyber_city" | "cyber_grid" | "cyber_rain" | "matrix_rain"
12169 | "cyber_glitch" | "glitch_text" | "cyber_banner" | "neon_banner"
12170 | "cyber_circuit" | "cyber_skull" | "cyber_eye"
12171 => Some(name),
12172 _ => None,
12173 }
12174 }
12175
12176 fn parse_block_or_bareword_cmp_block(&mut self) -> PerlResult<Block> {
12180 if matches!(self.peek(), Token::LBrace) {
12181 return self.parse_block();
12182 }
12183 let line = self.peek_line();
12184 if let Token::Ident(ref name) = self.peek().clone() {
12186 if matches!(
12187 self.peek_at(1),
12188 Token::Comma | Token::Semicolon | Token::RBrace | Token::Eof | Token::PipeForward
12189 ) {
12190 let name = name.clone();
12191 self.advance();
12192 let body = Expr {
12193 kind: ExprKind::FuncCall {
12194 name,
12195 args: vec![
12196 Expr {
12197 kind: ExprKind::ScalarVar("a".to_string()),
12198 line,
12199 },
12200 Expr {
12201 kind: ExprKind::ScalarVar("b".to_string()),
12202 line,
12203 },
12204 ],
12205 },
12206 line,
12207 };
12208 return Ok(vec![Statement::new(StmtKind::Expression(body), line)]);
12209 }
12210 }
12211 let expr = self.parse_assign_expr_stop_at_pipe()?;
12213 Ok(vec![Statement::new(StmtKind::Expression(expr), line)])
12214 }
12215
12216 fn parse_fan_optional_progress(
12218 &mut self,
12219 which: &'static str,
12220 ) -> PerlResult<Option<Box<Expr>>> {
12221 let line = self.peek_line();
12222 if self.eat(&Token::Comma) {
12223 match self.peek() {
12224 Token::Ident(ref kw)
12225 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) =>
12226 {
12227 self.advance();
12228 self.expect(&Token::FatArrow)?;
12229 return Ok(Some(Box::new(self.parse_assign_expr()?)));
12230 }
12231 _ => {
12232 return Err(self.syntax_err(
12233 format!("{which}: expected `progress => EXPR` after comma"),
12234 line,
12235 ));
12236 }
12237 }
12238 }
12239 if let Token::Ident(ref kw) = self.peek().clone() {
12240 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
12241 self.advance();
12242 self.expect(&Token::FatArrow)?;
12243 return Ok(Some(Box::new(self.parse_assign_expr()?)));
12244 }
12245 }
12246 Ok(None)
12247 }
12248
12249 fn parse_assign_expr_list_optional_progress(&mut self) -> PerlResult<(Expr, Option<Expr>)> {
12256 if self.in_pipe_rhs()
12262 && matches!(
12263 self.peek(),
12264 Token::Semicolon
12265 | Token::RBrace
12266 | Token::RParen
12267 | Token::Eof
12268 | Token::PipeForward
12269 | Token::Comma
12270 )
12271 {
12272 return Ok((self.pipe_placeholder_list(self.peek_line()), None));
12273 }
12274 let mut parts = vec![self.parse_assign_expr_stop_at_pipe()?];
12275 loop {
12276 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
12277 break;
12278 }
12279 if matches!(
12280 self.peek(),
12281 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof | Token::PipeForward
12282 ) {
12283 break;
12284 }
12285 if self.peek_is_postfix_stmt_modifier_keyword() {
12286 break;
12287 }
12288 if let Token::Ident(ref kw) = self.peek().clone() {
12289 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
12290 self.advance();
12291 self.expect(&Token::FatArrow)?;
12292 let prog = self.parse_assign_expr_stop_at_pipe()?;
12293 return Ok((merge_expr_list(parts), Some(prog)));
12294 }
12295 }
12296 parts.push(self.parse_assign_expr_stop_at_pipe()?);
12297 }
12298 Ok((merge_expr_list(parts), None))
12299 }
12300
12301 fn parse_one_arg(&mut self) -> PerlResult<Expr> {
12302 if matches!(self.peek(), Token::LParen) {
12303 self.advance();
12304 let expr = self.parse_expression()?;
12305 self.expect(&Token::RParen)?;
12306 Ok(expr)
12307 } else {
12308 self.parse_assign_expr_stop_at_pipe()
12309 }
12310 }
12311
12312 fn parse_one_arg_or_default(&mut self) -> PerlResult<Expr> {
12313 if matches!(
12320 self.peek(),
12321 Token::Semicolon
12323 | Token::RBrace
12324 | Token::RParen
12325 | Token::RBracket
12326 | Token::Eof
12327 | Token::Comma
12328 | Token::FatArrow
12329 | Token::PipeForward
12330 | Token::Question
12332 | Token::Colon
12333 | Token::NumEq | Token::NumNe | Token::NumLt | Token::NumGt
12335 | Token::NumLe | Token::NumGe | Token::Spaceship
12336 | Token::StrEq | Token::StrNe | Token::StrLt | Token::StrGt
12337 | Token::StrLe | Token::StrGe | Token::StrCmp
12338 | Token::LogAnd | Token::LogOr | Token::LogNot
12340 | Token::LogAndWord | Token::LogOrWord | Token::LogNotWord
12341 | Token::DefinedOr
12342 | Token::Range | Token::RangeExclusive
12344 | Token::Assign | Token::PlusAssign | Token::MinusAssign
12346 | Token::MulAssign | Token::DivAssign | Token::ModAssign
12347 | Token::PowAssign | Token::DotAssign | Token::AndAssign
12348 | Token::OrAssign | Token::XorAssign | Token::DefinedOrAssign
12349 | Token::ShiftLeftAssign | Token::ShiftRightAssign
12350 | Token::BitAndAssign | Token::BitOrAssign
12351 ) {
12352 return Ok(Expr {
12353 kind: ExprKind::ScalarVar("_".into()),
12354 line: self.peek_line(),
12355 });
12356 }
12357 if matches!(self.peek(), Token::LParen) && matches!(self.peek_at(1), Token::RParen) {
12361 let line = self.peek_line();
12362 self.advance(); self.advance(); return Ok(Expr {
12365 kind: ExprKind::ScalarVar("_".into()),
12366 line,
12367 });
12368 }
12369 self.parse_one_arg()
12370 }
12371
12372 fn parse_one_arg_or_argv(&mut self) -> PerlResult<Expr> {
12374 let line = self.prev_line(); if matches!(self.peek(), Token::LParen) {
12376 self.advance();
12377 if matches!(self.peek(), Token::RParen) {
12378 self.advance();
12379 return Ok(Expr {
12380 kind: ExprKind::ArrayVar("_".into()),
12381 line: self.peek_line(),
12382 });
12383 }
12384 let expr = self.parse_expression()?;
12385 self.expect(&Token::RParen)?;
12386 return Ok(expr);
12387 }
12388 if matches!(
12390 self.peek(),
12391 Token::Semicolon
12392 | Token::RBrace
12393 | Token::RParen
12394 | Token::Eof
12395 | Token::Comma
12396 | Token::PipeForward
12397 ) || self.peek_line() > line
12398 {
12399 Ok(Expr {
12400 kind: ExprKind::ArrayVar("_".into()),
12401 line,
12402 })
12403 } else {
12404 self.parse_assign_expr()
12405 }
12406 }
12407
12408 fn parse_builtin_args(&mut self) -> PerlResult<Vec<Expr>> {
12409 if matches!(self.peek(), Token::LParen) {
12410 self.advance();
12411 let args = self.parse_arg_list()?;
12412 self.expect(&Token::RParen)?;
12413 Ok(args)
12414 } else if self.suppress_parenless_call > 0 && matches!(self.peek(), Token::Ident(_)) {
12415 Ok(vec![])
12418 } else {
12419 self.parse_list_until_terminator()
12420 }
12421 }
12422
12423 #[inline]
12427 fn fat_arrow_autoquote(&self, name: &str, line: usize) -> Option<Expr> {
12428 if matches!(self.peek(), Token::FatArrow) {
12429 Some(Expr {
12430 kind: ExprKind::String(name.to_string()),
12431 line,
12432 })
12433 } else {
12434 None
12435 }
12436 }
12437
12438 fn parse_hash_subscript_key(&mut self) -> PerlResult<Expr> {
12443 let line = self.peek_line();
12444 if let Token::Ident(ref k) = self.peek().clone() {
12445 if matches!(self.peek_at(1), Token::RBrace) {
12446 let s = k.clone();
12447 self.advance();
12448 return Ok(Expr {
12449 kind: ExprKind::String(s),
12450 line,
12451 });
12452 }
12453 }
12454 self.parse_expression()
12455 }
12456
12457 #[inline]
12459 fn peek_is_glob_par_progress_kw(&self) -> bool {
12460 matches!(self.peek(), Token::Ident(ref kw) if kw == "progress")
12461 && matches!(self.peek_at(1), Token::FatArrow)
12462 }
12463
12464 fn parse_pattern_list_until_rparen_or_progress(&mut self) -> PerlResult<Vec<Expr>> {
12466 let mut args = Vec::new();
12467 loop {
12468 if matches!(self.peek(), Token::RParen | Token::Eof) {
12469 break;
12470 }
12471 if self.peek_is_glob_par_progress_kw() {
12472 break;
12473 }
12474 args.push(self.parse_assign_expr()?);
12475 match self.peek() {
12476 Token::RParen => break,
12477 Token::Comma => {
12478 self.advance();
12479 if matches!(self.peek(), Token::RParen) {
12480 break;
12481 }
12482 if self.peek_is_glob_par_progress_kw() {
12483 break;
12484 }
12485 }
12486 _ => {
12487 return Err(self.syntax_err(
12488 "expected `,`, `)`, or `progress =>` after argument in `glob_par` / `par_sed`",
12489 self.peek_line(),
12490 ));
12491 }
12492 }
12493 }
12494 Ok(args)
12495 }
12496
12497 fn parse_pattern_list_glob_par_bare(&mut self) -> PerlResult<Vec<Expr>> {
12499 let mut args = Vec::new();
12500 loop {
12501 if matches!(
12502 self.peek(),
12503 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof
12504 ) {
12505 break;
12506 }
12507 if self.peek_is_postfix_stmt_modifier_keyword() {
12508 break;
12509 }
12510 if self.peek_is_glob_par_progress_kw() {
12511 break;
12512 }
12513 args.push(self.parse_assign_expr()?);
12514 if !self.eat(&Token::Comma) {
12515 break;
12516 }
12517 if self.peek_is_glob_par_progress_kw() {
12518 break;
12519 }
12520 }
12521 Ok(args)
12522 }
12523
12524 fn parse_glob_par_or_par_sed_args(&mut self) -> PerlResult<(Vec<Expr>, Option<Box<Expr>>)> {
12526 if matches!(self.peek(), Token::LParen) {
12527 self.advance();
12528 let args = self.parse_pattern_list_until_rparen_or_progress()?;
12529 let progress = if self.peek_is_glob_par_progress_kw() {
12530 self.advance();
12531 self.expect(&Token::FatArrow)?;
12532 Some(Box::new(self.parse_assign_expr()?))
12533 } else {
12534 None
12535 };
12536 self.expect(&Token::RParen)?;
12537 Ok((args, progress))
12538 } else {
12539 let args = self.parse_pattern_list_glob_par_bare()?;
12540 let progress = if self.peek_is_glob_par_progress_kw() {
12542 self.advance();
12543 self.expect(&Token::FatArrow)?;
12544 Some(Box::new(self.parse_assign_expr()?))
12545 } else {
12546 None
12547 };
12548 Ok((args, progress))
12549 }
12550 }
12551
12552 pub(crate) fn parse_arg_list(&mut self) -> PerlResult<Vec<Expr>> {
12553 let mut args = Vec::new();
12554 let saved_no_pf = self.no_pipe_forward_depth;
12558 self.no_pipe_forward_depth = 0;
12559 while !matches!(
12560 self.peek(),
12561 Token::RParen | Token::RBracket | Token::RBrace | Token::Eof
12562 ) {
12563 let arg = match self.parse_assign_expr() {
12564 Ok(e) => e,
12565 Err(err) => {
12566 self.no_pipe_forward_depth = saved_no_pf;
12567 return Err(err);
12568 }
12569 };
12570 args.push(arg);
12571 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
12572 break;
12573 }
12574 }
12575 self.no_pipe_forward_depth = saved_no_pf;
12576 Ok(args)
12577 }
12578
12579 fn parse_method_arg_list_no_paren(&mut self) -> PerlResult<Vec<Expr>> {
12583 let mut args = Vec::new();
12584 let call_line = self.prev_line();
12585 loop {
12586 if args.is_empty() && matches!(self.peek(), Token::LBrace) {
12589 break;
12590 }
12591 if matches!(
12592 self.peek(),
12593 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof | Token::PipeForward
12594 ) {
12595 break;
12596 }
12597 if let Token::Ident(ref kw) = self.peek().clone() {
12598 if matches!(
12599 kw.as_str(),
12600 "if" | "unless" | "while" | "until" | "for" | "foreach"
12601 ) {
12602 break;
12603 }
12604 }
12605 if args.is_empty()
12608 && (self.peek_method_arg_infix_terminator() || matches!(self.peek(), Token::Comma))
12609 {
12610 break;
12611 }
12612 if args.is_empty() && self.peek_line() > call_line {
12615 break;
12616 }
12617 args.push(self.parse_assign_expr()?);
12618 if !self.eat(&Token::Comma) {
12619 break;
12620 }
12621 }
12622 Ok(args)
12623 }
12624
12625 fn peek_method_arg_infix_terminator(&self) -> bool {
12628 matches!(
12629 self.peek(),
12630 Token::Plus
12631 | Token::Minus
12632 | Token::Star
12633 | Token::Slash
12634 | Token::Percent
12635 | Token::Power
12636 | Token::Dot
12637 | Token::X
12638 | Token::NumEq
12639 | Token::NumNe
12640 | Token::NumLt
12641 | Token::NumGt
12642 | Token::NumLe
12643 | Token::NumGe
12644 | Token::Spaceship
12645 | Token::StrEq
12646 | Token::StrNe
12647 | Token::StrLt
12648 | Token::StrGt
12649 | Token::StrLe
12650 | Token::StrGe
12651 | Token::StrCmp
12652 | Token::LogAnd
12653 | Token::LogOr
12654 | Token::LogAndWord
12655 | Token::LogOrWord
12656 | Token::DefinedOr
12657 | Token::BitAnd
12658 | Token::BitOr
12659 | Token::BitXor
12660 | Token::ShiftLeft
12661 | Token::ShiftRight
12662 | Token::Range
12663 | Token::RangeExclusive
12664 | Token::BindMatch
12665 | Token::BindNotMatch
12666 | Token::Arrow
12667 | Token::Question
12669 | Token::Colon
12670 )
12671 }
12672
12673 fn parse_list_until_terminator(&mut self) -> PerlResult<Vec<Expr>> {
12674 let mut args = Vec::new();
12675 let call_line = self.prev_line();
12680 loop {
12681 if matches!(
12682 self.peek(),
12683 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof | Token::PipeForward
12684 ) {
12685 break;
12686 }
12687 if let Token::Ident(ref kw) = self.peek().clone() {
12689 if matches!(
12690 kw.as_str(),
12691 "if" | "unless" | "while" | "until" | "for" | "foreach"
12692 ) {
12693 break;
12694 }
12695 }
12696 if args.is_empty() && self.peek_line() > call_line {
12703 break;
12704 }
12705 args.push(self.parse_assign_expr_stop_at_pipe()?);
12708 if !self.eat(&Token::Comma) {
12709 break;
12710 }
12711 }
12712 Ok(args)
12713 }
12714
12715 fn try_parse_hash_ref(&mut self) -> PerlResult<Vec<(Expr, Expr)>> {
12716 let mut pairs = Vec::new();
12717 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
12718 let line = self.peek_line();
12721 let key = if let Token::Ident(ref name) = self.peek().clone() {
12722 if matches!(self.peek_at(1), Token::FatArrow) {
12723 self.advance();
12724 Expr {
12725 kind: ExprKind::String(name.clone()),
12726 line,
12727 }
12728 } else {
12729 self.parse_assign_expr()?
12730 }
12731 } else {
12732 self.parse_assign_expr()?
12733 };
12734 if matches!(self.peek(), Token::RBrace | Token::Comma)
12738 && matches!(
12739 key.kind,
12740 ExprKind::HashVar(_)
12741 | ExprKind::Deref {
12742 kind: Sigil::Hash,
12743 ..
12744 }
12745 )
12746 {
12747 let sentinel_key = Expr {
12751 kind: ExprKind::String("__HASH_SPREAD__".into()),
12752 line,
12753 };
12754 pairs.push((sentinel_key, key));
12755 self.eat(&Token::Comma);
12756 continue;
12757 }
12758 if self.eat(&Token::FatArrow) || self.eat(&Token::Comma) {
12760 let val = self.parse_assign_expr()?;
12761 pairs.push((key, val));
12762 self.eat(&Token::Comma);
12763 } else {
12764 return Err(self.syntax_err("Expected => or , in hash ref", key.line));
12765 }
12766 }
12767 self.expect(&Token::RBrace)?;
12768 Ok(pairs)
12769 }
12770
12771 fn parse_hashref_pairs_until(&mut self, term: &Token) -> PerlResult<Vec<(Expr, Expr)>> {
12776 let mut pairs = Vec::new();
12777 while !matches!(&self.peek(), t if std::mem::discriminant(*t) == std::mem::discriminant(term))
12778 && !matches!(self.peek(), Token::Eof)
12779 {
12780 let line = self.peek_line();
12781 let key = if let Token::Ident(ref name) = self.peek().clone() {
12782 if matches!(self.peek_at(1), Token::FatArrow) {
12783 self.advance();
12784 Expr {
12785 kind: ExprKind::String(name.clone()),
12786 line,
12787 }
12788 } else {
12789 self.parse_assign_expr()?
12790 }
12791 } else {
12792 self.parse_assign_expr()?
12793 };
12794 if self.eat(&Token::FatArrow) || self.eat(&Token::Comma) {
12795 let val = self.parse_assign_expr()?;
12796 pairs.push((key, val));
12797 self.eat(&Token::Comma);
12798 } else {
12799 return Err(self.syntax_err("Expected => or , in hash ref", key.line));
12800 }
12801 }
12802 Ok(pairs)
12803 }
12804
12805 fn interp_chain_subscripts(
12811 &self,
12812 chars: &[char],
12813 i: &mut usize,
12814 mut base: Expr,
12815 line: usize,
12816 ) -> Expr {
12817 loop {
12818 let (after, requires_subscript) =
12820 if *i + 1 < chars.len() && chars[*i] == '-' && chars[*i + 1] == '>' {
12821 (*i + 2, true)
12822 } else {
12823 (*i, false)
12824 };
12825 if after >= chars.len() {
12826 break;
12827 }
12828 match chars[after] {
12829 '[' => {
12830 *i = after + 1;
12831 let mut idx_str = String::new();
12832 while *i < chars.len() && chars[*i] != ']' {
12833 idx_str.push(chars[*i]);
12834 *i += 1;
12835 }
12836 if *i < chars.len() {
12837 *i += 1;
12838 }
12839 let idx_expr = if let Some(rest) = idx_str.strip_prefix('$') {
12840 Expr {
12841 kind: ExprKind::ScalarVar(rest.to_string()),
12842 line,
12843 }
12844 } else if let Ok(n) = idx_str.parse::<i64>() {
12845 Expr {
12846 kind: ExprKind::Integer(n),
12847 line,
12848 }
12849 } else {
12850 Expr {
12851 kind: ExprKind::String(idx_str),
12852 line,
12853 }
12854 };
12855 base = Expr {
12856 kind: ExprKind::ArrowDeref {
12857 expr: Box::new(base),
12858 index: Box::new(idx_expr),
12859 kind: DerefKind::Array,
12860 },
12861 line,
12862 };
12863 }
12864 '{' => {
12865 *i = after + 1;
12866 let mut key = String::new();
12867 let mut depth = 1usize;
12868 while *i < chars.len() && depth > 0 {
12869 if chars[*i] == '{' {
12870 depth += 1;
12871 } else if chars[*i] == '}' {
12872 depth -= 1;
12873 if depth == 0 {
12874 break;
12875 }
12876 }
12877 key.push(chars[*i]);
12878 *i += 1;
12879 }
12880 if *i < chars.len() {
12881 *i += 1;
12882 }
12883 let key_expr = if let Some(rest) = key.strip_prefix('$') {
12884 Expr {
12885 kind: ExprKind::ScalarVar(rest.to_string()),
12886 line,
12887 }
12888 } else {
12889 Expr {
12890 kind: ExprKind::String(key),
12891 line,
12892 }
12893 };
12894 base = Expr {
12895 kind: ExprKind::ArrowDeref {
12896 expr: Box::new(base),
12897 index: Box::new(key_expr),
12898 kind: DerefKind::Hash,
12899 },
12900 line,
12901 };
12902 }
12903 _ => {
12904 if requires_subscript {
12905 }
12907 break;
12908 }
12909 }
12910 }
12911 base
12912 }
12913
12914 fn parse_interpolated_string(&self, s: &str, line: usize) -> PerlResult<Expr> {
12915 let mut parts = Vec::new();
12917 let mut literal = String::new();
12918 let chars: Vec<char> = s.chars().collect();
12919 let mut i = 0;
12920
12921 'istr: while i < chars.len() {
12922 if chars[i] == LITERAL_DOLLAR_IN_DQUOTE {
12923 literal.push('$');
12924 i += 1;
12925 continue;
12926 }
12927 if chars[i] == '\\' && i + 1 < chars.len() && chars[i + 1] == '$' {
12929 literal.push('\\');
12930 i += 1;
12931 }
12933 if chars[i] == '$' && i + 1 < chars.len() {
12934 if !literal.is_empty() {
12935 parts.push(StringPart::Literal(std::mem::take(&mut literal)));
12936 }
12937 i += 1; while i < chars.len() && chars[i].is_whitespace() {
12940 i += 1;
12941 }
12942 if i >= chars.len() {
12943 return Err(self.syntax_err("Final $ should be \\$ or $name", line));
12944 }
12945 if chars[i] == '#' {
12947 i += 1;
12948 let mut sname = String::from("#");
12949 while i < chars.len()
12950 && (chars[i].is_alphanumeric() || chars[i] == '_' || chars[i] == ':')
12951 {
12952 sname.push(chars[i]);
12953 i += 1;
12954 }
12955 while i + 1 < chars.len() && chars[i] == ':' && chars[i + 1] == ':' {
12956 sname.push_str("::");
12957 i += 2;
12958 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
12959 sname.push(chars[i]);
12960 i += 1;
12961 }
12962 }
12963 parts.push(StringPart::ScalarVar(sname));
12964 continue;
12965 }
12966 if chars[i] == '$' {
12970 let next_c = chars.get(i + 1).copied();
12971 let is_pid = match next_c {
12972 None => true,
12973 Some(c)
12974 if !c.is_ascii_digit() && !matches!(c, 'A'..='Z' | 'a'..='z' | '_') =>
12975 {
12976 true
12977 }
12978 _ => false,
12979 };
12980 if is_pid {
12981 parts.push(StringPart::ScalarVar("$$".to_string()));
12982 i += 1; continue;
12984 }
12985 i += 1; }
12987 if chars[i] == '{' {
12988 i += 1;
12994 let mut inner = String::new();
12995 let mut depth = 1usize;
12996 while i < chars.len() && depth > 0 {
12997 match chars[i] {
12998 '{' => depth += 1,
12999 '}' => {
13000 depth -= 1;
13001 if depth == 0 {
13002 break;
13003 }
13004 }
13005 _ => {}
13006 }
13007 inner.push(chars[i]);
13008 i += 1;
13009 }
13010 if i < chars.len() {
13011 i += 1; }
13013
13014 let trimmed = inner.trim();
13018 let is_expr = trimmed.starts_with('$')
13019 || trimmed.starts_with('\\')
13020 || trimmed.starts_with('@') || trimmed.starts_with('%') || trimmed.contains(['(', '+', '-', '*', '/', '.', '?', '&', '|']);
13023 let mut base: Expr = if is_expr {
13024 match parse_expression_from_str(trimmed, "<interp>") {
13028 Ok(e) => Expr {
13029 kind: ExprKind::Deref {
13030 expr: Box::new(e),
13031 kind: Sigil::Scalar,
13032 },
13033 line,
13034 },
13035 Err(_) => Expr {
13036 kind: ExprKind::ScalarVar(inner.clone()),
13037 line,
13038 },
13039 }
13040 } else {
13041 Expr {
13043 kind: ExprKind::ScalarVar(inner),
13044 line,
13045 }
13046 };
13047
13048 base = self.interp_chain_subscripts(&chars, &mut i, base, line);
13052 parts.push(StringPart::Expr(base));
13053 } else if chars[i] == '^' {
13054 let mut name = String::from("^");
13056 i += 1;
13057 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
13058 name.push(chars[i]);
13059 i += 1;
13060 }
13061 if i < chars.len() && chars[i] == '{' {
13062 i += 1; let mut key = String::new();
13064 let mut depth = 1;
13065 while i < chars.len() && depth > 0 {
13066 if chars[i] == '{' {
13067 depth += 1;
13068 } else if chars[i] == '}' {
13069 depth -= 1;
13070 if depth == 0 {
13071 break;
13072 }
13073 }
13074 key.push(chars[i]);
13075 i += 1;
13076 }
13077 if i < chars.len() {
13078 i += 1;
13079 }
13080 let key_expr = if let Some(rest) = key.strip_prefix('$') {
13081 Expr {
13082 kind: ExprKind::ScalarVar(rest.to_string()),
13083 line,
13084 }
13085 } else {
13086 Expr {
13087 kind: ExprKind::String(key),
13088 line,
13089 }
13090 };
13091 parts.push(StringPart::Expr(Expr {
13092 kind: ExprKind::HashElement {
13093 hash: name,
13094 key: Box::new(key_expr),
13095 },
13096 line,
13097 }));
13098 } else if i < chars.len() && chars[i] == '[' {
13099 i += 1;
13100 let mut idx_str = String::new();
13101 while i < chars.len() && chars[i] != ']' {
13102 idx_str.push(chars[i]);
13103 i += 1;
13104 }
13105 if i < chars.len() {
13106 i += 1;
13107 }
13108 let idx_expr = if let Some(rest) = idx_str.strip_prefix('$') {
13109 Expr {
13110 kind: ExprKind::ScalarVar(rest.to_string()),
13111 line,
13112 }
13113 } else if let Ok(n) = idx_str.parse::<i64>() {
13114 Expr {
13115 kind: ExprKind::Integer(n),
13116 line,
13117 }
13118 } else {
13119 Expr {
13120 kind: ExprKind::String(idx_str),
13121 line,
13122 }
13123 };
13124 parts.push(StringPart::Expr(Expr {
13125 kind: ExprKind::ArrayElement {
13126 array: name,
13127 index: Box::new(idx_expr),
13128 },
13129 line,
13130 }));
13131 } else {
13132 parts.push(StringPart::ScalarVar(name));
13133 }
13134 } else if chars[i].is_alphabetic() || chars[i] == '_' {
13135 let mut name = String::new();
13136 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
13137 name.push(chars[i]);
13138 i += 1;
13139 }
13140 if name == "_" {
13142 while i < chars.len() && chars[i] == '<' {
13143 name.push('<');
13144 i += 1;
13145 }
13146 }
13147 let mut base = if i < chars.len() && chars[i] == '{' {
13152 i += 1; let mut key = String::new();
13155 let mut depth = 1;
13156 while i < chars.len() && depth > 0 {
13157 if chars[i] == '{' {
13158 depth += 1;
13159 } else if chars[i] == '}' {
13160 depth -= 1;
13161 if depth == 0 {
13162 break;
13163 }
13164 }
13165 key.push(chars[i]);
13166 i += 1;
13167 }
13168 if i < chars.len() {
13169 i += 1;
13170 } let key_expr = if let Some(rest) = key.strip_prefix('$') {
13172 Expr {
13173 kind: ExprKind::ScalarVar(rest.to_string()),
13174 line,
13175 }
13176 } else {
13177 Expr {
13178 kind: ExprKind::String(key),
13179 line,
13180 }
13181 };
13182 Expr {
13183 kind: ExprKind::HashElement {
13184 hash: name,
13185 key: Box::new(key_expr),
13186 },
13187 line,
13188 }
13189 } else if i < chars.len() && chars[i] == '[' {
13190 i += 1;
13192 let mut idx_str = String::new();
13193 while i < chars.len() && chars[i] != ']' {
13194 idx_str.push(chars[i]);
13195 i += 1;
13196 }
13197 if i < chars.len() {
13198 i += 1;
13199 }
13200 let idx_expr = if let Some(rest) = idx_str.strip_prefix('$') {
13201 Expr {
13202 kind: ExprKind::ScalarVar(rest.to_string()),
13203 line,
13204 }
13205 } else if let Ok(n) = idx_str.parse::<i64>() {
13206 Expr {
13207 kind: ExprKind::Integer(n),
13208 line,
13209 }
13210 } else {
13211 Expr {
13212 kind: ExprKind::String(idx_str),
13213 line,
13214 }
13215 };
13216 Expr {
13217 kind: ExprKind::ArrayElement {
13218 array: name,
13219 index: Box::new(idx_expr),
13220 },
13221 line,
13222 }
13223 } else {
13224 Expr {
13226 kind: ExprKind::ScalarVar(name),
13227 line,
13228 }
13229 };
13230
13231 base = self.interp_chain_subscripts(&chars, &mut i, base, line);
13235 parts.push(StringPart::Expr(base));
13236 } else if chars[i].is_ascii_digit() {
13237 if chars[i] == '0' {
13239 i += 1;
13240 if i < chars.len() && chars[i].is_ascii_digit() {
13241 return Err(self.syntax_err(
13242 "Numeric variables with more than one digit may not start with '0'",
13243 line,
13244 ));
13245 }
13246 parts.push(StringPart::ScalarVar("0".into()));
13247 } else {
13248 let start = i;
13249 while i < chars.len() && chars[i].is_ascii_digit() {
13250 i += 1;
13251 }
13252 parts.push(StringPart::ScalarVar(chars[start..i].iter().collect()));
13253 }
13254 } else {
13255 let c = chars[i];
13256 let probe = c.to_string();
13257 if Interpreter::is_special_scalar_name_for_get(&probe)
13258 || matches!(c, '\'' | '`')
13259 {
13260 i += 1;
13261 if i < chars.len() && chars[i] == '{' {
13263 i += 1; let mut key = String::new();
13265 let mut depth = 1;
13266 while i < chars.len() && depth > 0 {
13267 if chars[i] == '{' {
13268 depth += 1;
13269 } else if chars[i] == '}' {
13270 depth -= 1;
13271 if depth == 0 {
13272 break;
13273 }
13274 }
13275 key.push(chars[i]);
13276 i += 1;
13277 }
13278 if i < chars.len() {
13279 i += 1;
13280 } let key_expr = if let Some(rest) = key.strip_prefix('$') {
13282 Expr {
13283 kind: ExprKind::ScalarVar(rest.to_string()),
13284 line,
13285 }
13286 } else {
13287 Expr {
13288 kind: ExprKind::String(key),
13289 line,
13290 }
13291 };
13292 let mut base = Expr {
13293 kind: ExprKind::HashElement {
13294 hash: probe,
13295 key: Box::new(key_expr),
13296 },
13297 line,
13298 };
13299 base = self.interp_chain_subscripts(&chars, &mut i, base, line);
13300 parts.push(StringPart::Expr(base));
13301 } else {
13302 let mut base = Expr {
13304 kind: ExprKind::ScalarVar(probe),
13305 line,
13306 };
13307 base = self.interp_chain_subscripts(&chars, &mut i, base, line);
13308 if matches!(base.kind, ExprKind::ScalarVar(_)) {
13309 if let ExprKind::ScalarVar(name) = base.kind {
13311 parts.push(StringPart::ScalarVar(name));
13312 }
13313 } else {
13314 parts.push(StringPart::Expr(base));
13315 }
13316 }
13317 } else {
13318 literal.push('$');
13319 literal.push(c);
13320 i += 1;
13321 }
13322 }
13323 } else if chars[i] == '@' && i + 1 < chars.len() {
13324 let next = chars[i + 1];
13325 if next == '$' {
13327 if !literal.is_empty() {
13328 parts.push(StringPart::Literal(std::mem::take(&mut literal)));
13329 }
13330 i += 1; debug_assert_eq!(chars[i], '$');
13332 i += 1; while i < chars.len() && chars[i].is_whitespace() {
13334 i += 1;
13335 }
13336 if i >= chars.len() {
13337 return Err(self.syntax_err(
13338 "Expected variable or block after `@$` in double-quoted string",
13339 line,
13340 ));
13341 }
13342 let inner_expr = if chars[i] == '{' {
13343 i += 1;
13344 let start = i;
13345 let mut depth = 1usize;
13346 while i < chars.len() && depth > 0 {
13347 match chars[i] {
13348 '{' => depth += 1,
13349 '}' => {
13350 depth -= 1;
13351 if depth == 0 {
13352 break;
13353 }
13354 }
13355 _ => {}
13356 }
13357 i += 1;
13358 }
13359 if depth != 0 {
13360 return Err(self.syntax_err(
13361 "Unterminated `${ ... }` after `@` in double-quoted string",
13362 line,
13363 ));
13364 }
13365 let inner: String = chars[start..i].iter().collect();
13366 i += 1; parse_expression_from_str(inner.trim(), "-e")?
13368 } else {
13369 let mut name = String::new();
13370 if chars[i] == '^' {
13371 name.push('^');
13372 i += 1;
13373 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_')
13374 {
13375 name.push(chars[i]);
13376 i += 1;
13377 }
13378 } else {
13379 while i < chars.len()
13380 && (chars[i].is_alphanumeric()
13381 || chars[i] == '_'
13382 || chars[i] == ':')
13383 {
13384 name.push(chars[i]);
13385 i += 1;
13386 }
13387 while i + 1 < chars.len() && chars[i] == ':' && chars[i + 1] == ':' {
13388 name.push_str("::");
13389 i += 2;
13390 while i < chars.len()
13391 && (chars[i].is_alphanumeric() || chars[i] == '_')
13392 {
13393 name.push(chars[i]);
13394 i += 1;
13395 }
13396 }
13397 }
13398 if name.is_empty() {
13399 return Err(self.syntax_err(
13400 "Expected identifier after `@$` in double-quoted string",
13401 line,
13402 ));
13403 }
13404 Expr {
13405 kind: ExprKind::ScalarVar(name),
13406 line,
13407 }
13408 };
13409 parts.push(StringPart::Expr(Expr {
13410 kind: ExprKind::Deref {
13411 expr: Box::new(inner_expr),
13412 kind: Sigil::Array,
13413 },
13414 line,
13415 }));
13416 continue 'istr;
13417 }
13418 if next == '{' {
13419 if !literal.is_empty() {
13420 parts.push(StringPart::Literal(std::mem::take(&mut literal)));
13421 }
13422 i += 2; let start = i;
13424 let mut depth = 1usize;
13425 while i < chars.len() && depth > 0 {
13426 match chars[i] {
13427 '{' => depth += 1,
13428 '}' => {
13429 depth -= 1;
13430 if depth == 0 {
13431 break;
13432 }
13433 }
13434 _ => {}
13435 }
13436 i += 1;
13437 }
13438 if depth != 0 {
13439 return Err(
13440 self.syntax_err("Unterminated @{ ... } in double-quoted string", line)
13441 );
13442 }
13443 let inner: String = chars[start..i].iter().collect();
13444 i += 1; let inner_expr = parse_expression_from_str(inner.trim(), "-e")?;
13446 parts.push(StringPart::Expr(Expr {
13447 kind: ExprKind::Deref {
13448 expr: Box::new(inner_expr),
13449 kind: Sigil::Array,
13450 },
13451 line,
13452 }));
13453 continue 'istr;
13454 }
13455 if !(next.is_alphabetic() || next == '_' || next == '+' || next == '-') {
13456 literal.push(chars[i]);
13457 i += 1;
13458 } else {
13459 if !literal.is_empty() {
13460 parts.push(StringPart::Literal(std::mem::take(&mut literal)));
13461 }
13462 i += 1;
13463 let mut name = String::new();
13464 if i < chars.len() && (chars[i] == '+' || chars[i] == '-') {
13465 name.push(chars[i]);
13466 i += 1;
13467 } else {
13468 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
13469 name.push(chars[i]);
13470 i += 1;
13471 }
13472 while i + 1 < chars.len() && chars[i] == ':' && chars[i + 1] == ':' {
13473 name.push_str("::");
13474 i += 2;
13475 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_')
13476 {
13477 name.push(chars[i]);
13478 i += 1;
13479 }
13480 }
13481 }
13482 if i < chars.len() && chars[i] == '[' {
13483 i += 1;
13484 let start_inner = i;
13485 let mut depth = 1usize;
13486 while i < chars.len() && depth > 0 {
13487 match chars[i] {
13488 '[' => depth += 1,
13489 ']' => depth -= 1,
13490 _ => {}
13491 }
13492 if depth == 0 {
13493 let inner: String = chars[start_inner..i].iter().collect();
13494 i += 1; let indices = parse_slice_indices_from_str(inner.trim(), "-e")?;
13496 parts.push(StringPart::Expr(Expr {
13497 kind: ExprKind::ArraySlice {
13498 array: name.clone(),
13499 indices,
13500 },
13501 line,
13502 }));
13503 continue 'istr;
13504 }
13505 i += 1;
13506 }
13507 return Err(self.syntax_err(
13508 "Unterminated [ in array slice inside quoted string",
13509 line,
13510 ));
13511 }
13512 parts.push(StringPart::ArrayVar(name));
13513 }
13514 } else if chars[i] == '#'
13515 && i + 1 < chars.len()
13516 && chars[i + 1] == '{'
13517 && !crate::compat_mode()
13518 {
13519 if !literal.is_empty() {
13521 parts.push(StringPart::Literal(std::mem::take(&mut literal)));
13522 }
13523 i += 2; let mut inner = String::new();
13525 let mut depth = 1usize;
13526 while i < chars.len() && depth > 0 {
13527 match chars[i] {
13528 '{' => depth += 1,
13529 '}' => {
13530 depth -= 1;
13531 if depth == 0 {
13532 break;
13533 }
13534 }
13535 _ => {}
13536 }
13537 inner.push(chars[i]);
13538 i += 1;
13539 }
13540 if i < chars.len() {
13541 i += 1; }
13543 let expr = parse_block_from_str(inner.trim(), "-e", line)?;
13544 parts.push(StringPart::Expr(expr));
13545 } else {
13546 literal.push(chars[i]);
13547 i += 1;
13548 }
13549 }
13550 if !literal.is_empty() {
13551 parts.push(StringPart::Literal(literal));
13552 }
13553
13554 if parts.len() == 1 {
13555 if let StringPart::Literal(s) = &parts[0] {
13556 return Ok(Expr {
13557 kind: ExprKind::String(s.clone()),
13558 line,
13559 });
13560 }
13561 }
13562 if parts.is_empty() {
13563 return Ok(Expr {
13564 kind: ExprKind::String(String::new()),
13565 line,
13566 });
13567 }
13568
13569 Ok(Expr {
13570 kind: ExprKind::InterpolatedString(parts),
13571 line,
13572 })
13573 }
13574
13575 fn expr_to_overload_key(&self, e: &Expr) -> PerlResult<String> {
13576 match &e.kind {
13577 ExprKind::String(s) => Ok(s.clone()),
13578 _ => Err(self.syntax_err(
13579 "overload key must be a string literal (e.g. '\"\"' or '+')",
13580 e.line,
13581 )),
13582 }
13583 }
13584
13585 fn expr_to_overload_sub(&self, e: &Expr) -> PerlResult<String> {
13586 match &e.kind {
13587 ExprKind::String(s) => Ok(s.clone()),
13588 ExprKind::Integer(n) => Ok(n.to_string()),
13589 ExprKind::SubroutineRef(s) | ExprKind::SubroutineCodeRef(s) => Ok(s.clone()),
13590 _ => Err(self.syntax_err(
13591 "overload handler must be a string literal, number (e.g. fallback => 1), or \\&subname (method in current package)",
13592 e.line,
13593 )),
13594 }
13595 }
13596}
13597
13598fn merge_expr_list(parts: Vec<Expr>) -> Expr {
13599 if parts.len() == 1 {
13600 parts.into_iter().next().unwrap()
13601 } else {
13602 let line = parts.first().map(|e| e.line).unwrap_or(0);
13603 Expr {
13604 kind: ExprKind::List(parts),
13605 line,
13606 }
13607 }
13608}
13609
13610pub fn parse_expression_from_str(s: &str, file: &str) -> PerlResult<Expr> {
13612 let mut lexer = Lexer::new_with_file(s, file);
13613 let tokens = lexer.tokenize()?;
13614 let mut parser = Parser::new_with_file(tokens, file);
13615 let e = parser.parse_expression()?;
13616 if !parser.at_eof() {
13617 return Err(parser.syntax_err(
13618 "Extra tokens in embedded string expression",
13619 parser.peek_line(),
13620 ));
13621 }
13622 Ok(e)
13623}
13624
13625pub fn parse_block_from_str(s: &str, file: &str, line: usize) -> PerlResult<Expr> {
13627 let mut lexer = Lexer::new_with_file(s, file);
13628 let tokens = lexer.tokenize()?;
13629 let mut parser = Parser::new_with_file(tokens, file);
13630 let stmts = parser.parse_statements()?;
13631 let inner_line = stmts.first().map(|st| st.line).unwrap_or(line);
13632 let inner = Expr {
13633 kind: ExprKind::CodeRef {
13634 params: vec![],
13635 body: stmts,
13636 },
13637 line: inner_line,
13638 };
13639 Ok(Expr {
13640 kind: ExprKind::Do(Box::new(inner)),
13641 line,
13642 })
13643}
13644
13645pub fn parse_slice_indices_from_str(s: &str, file: &str) -> PerlResult<Vec<Expr>> {
13648 let mut lexer = Lexer::new_with_file(s, file);
13649 let tokens = lexer.tokenize()?;
13650 let mut parser = Parser::new_with_file(tokens, file);
13651 parser.parse_arg_list()
13652}
13653
13654pub fn parse_format_value_line(line: &str) -> PerlResult<Vec<Expr>> {
13655 let trimmed = line.trim();
13656 if trimmed.is_empty() {
13657 return Ok(vec![]);
13658 }
13659 let mut lexer = Lexer::new(trimmed);
13660 let tokens = lexer.tokenize()?;
13661 let mut parser = Parser::new(tokens);
13662 let mut exprs = Vec::new();
13663 loop {
13664 if parser.at_eof() {
13665 break;
13666 }
13667 exprs.push(parser.parse_assign_expr()?);
13669 if parser.eat(&Token::Comma) {
13670 continue;
13671 }
13672 if !parser.at_eof() {
13673 return Err(parser.syntax_err("Extra tokens in format value line", parser.peek_line()));
13674 }
13675 break;
13676 }
13677 Ok(exprs)
13678}
13679
13680#[cfg(test)]
13681mod tests {
13682 use super::*;
13683
13684 fn parse_ok(code: &str) -> Program {
13685 let mut lexer = Lexer::new(code);
13686 let tokens = lexer.tokenize().expect("tokenize");
13687 let mut parser = Parser::new(tokens);
13688 parser.parse_program().expect("parse")
13689 }
13690
13691 fn parse_err(code: &str) -> String {
13692 let mut lexer = Lexer::new(code);
13693 let tokens = match lexer.tokenize() {
13694 Ok(t) => t,
13695 Err(e) => return e.message,
13696 };
13697 let mut parser = Parser::new(tokens);
13698 parser.parse_program().unwrap_err().message
13699 }
13700
13701 #[test]
13702 fn parse_empty_program() {
13703 let p = parse_ok("");
13704 assert!(p.statements.is_empty());
13705 }
13706
13707 #[test]
13708 fn parse_semicolons_only() {
13709 let p = parse_ok(";;");
13710 assert!(p.statements.len() <= 3);
13711 }
13712
13713 #[test]
13714 fn parse_simple_scalar_assignment() {
13715 let p = parse_ok("$x = 1");
13716 assert_eq!(p.statements.len(), 1);
13717 }
13718
13719 #[test]
13720 fn parse_simple_array_assignment() {
13721 let p = parse_ok("@arr = (1, 2, 3)");
13722 assert_eq!(p.statements.len(), 1);
13723 }
13724
13725 #[test]
13726 fn parse_simple_hash_assignment() {
13727 let p = parse_ok("%h = (a => 1, b => 2)");
13728 assert_eq!(p.statements.len(), 1);
13729 }
13730
13731 #[test]
13732 fn parse_subroutine_decl() {
13733 let p = parse_ok("sub foo { 1 }");
13734 assert_eq!(p.statements.len(), 1);
13735 match &p.statements[0].kind {
13736 StmtKind::SubDecl { name, .. } => assert_eq!(name, "foo"),
13737 _ => panic!("expected SubDecl"),
13738 }
13739 }
13740
13741 #[test]
13742 fn parse_subroutine_with_prototype() {
13743 let p = parse_ok("sub foo ($$) { 1 }");
13744 assert_eq!(p.statements.len(), 1);
13745 match &p.statements[0].kind {
13746 StmtKind::SubDecl { prototype, .. } => {
13747 assert!(prototype.is_some());
13748 }
13749 _ => panic!("expected SubDecl"),
13750 }
13751 }
13752
13753 #[test]
13754 fn parse_anonymous_sub() {
13755 let p = parse_ok("my $f = sub { 1 }");
13756 assert_eq!(p.statements.len(), 1);
13757 }
13758
13759 #[test]
13760 fn parse_if_statement() {
13761 let p = parse_ok("if (1) { 2 }");
13762 assert_eq!(p.statements.len(), 1);
13763 matches!(&p.statements[0].kind, StmtKind::If { .. });
13764 }
13765
13766 #[test]
13767 fn parse_if_elsif_else() {
13768 let p = parse_ok("if (0) { 1 } elsif (1) { 2 } else { 3 }");
13769 assert_eq!(p.statements.len(), 1);
13770 }
13771
13772 #[test]
13773 fn parse_unless_statement() {
13774 let p = parse_ok("unless (0) { 1 }");
13775 assert_eq!(p.statements.len(), 1);
13776 }
13777
13778 #[test]
13779 fn parse_while_loop() {
13780 let p = parse_ok("while ($x) { $x-- }");
13781 assert_eq!(p.statements.len(), 1);
13782 }
13783
13784 #[test]
13785 fn parse_until_loop() {
13786 let p = parse_ok("until ($x) { $x++ }");
13787 assert_eq!(p.statements.len(), 1);
13788 }
13789
13790 #[test]
13791 fn parse_for_c_style() {
13792 let p = parse_ok("for (my $i=0; $i<10; $i++) { 1 }");
13793 assert_eq!(p.statements.len(), 1);
13794 }
13795
13796 #[test]
13797 fn parse_foreach_loop() {
13798 let p = parse_ok("foreach my $x (@arr) { 1 }");
13799 assert_eq!(p.statements.len(), 1);
13800 }
13801
13802 #[test]
13803 fn parse_loop_with_label() {
13804 let p = parse_ok("OUTER: for my $i (1..10) { last OUTER }");
13805 assert_eq!(p.statements.len(), 1);
13806 assert_eq!(p.statements[0].label.as_deref(), Some("OUTER"));
13807 }
13808
13809 #[test]
13810 fn parse_begin_block() {
13811 let p = parse_ok("BEGIN { 1 }");
13812 assert_eq!(p.statements.len(), 1);
13813 matches!(&p.statements[0].kind, StmtKind::Begin(_));
13814 }
13815
13816 #[test]
13817 fn parse_end_block() {
13818 let p = parse_ok("END { 1 }");
13819 assert_eq!(p.statements.len(), 1);
13820 matches!(&p.statements[0].kind, StmtKind::End(_));
13821 }
13822
13823 #[test]
13824 fn parse_package_statement() {
13825 let p = parse_ok("package Foo::Bar");
13826 assert_eq!(p.statements.len(), 1);
13827 match &p.statements[0].kind {
13828 StmtKind::Package { name } => assert_eq!(name, "Foo::Bar"),
13829 _ => panic!("expected Package"),
13830 }
13831 }
13832
13833 #[test]
13834 fn parse_use_statement() {
13835 let p = parse_ok("use strict");
13836 assert_eq!(p.statements.len(), 1);
13837 }
13838
13839 #[test]
13840 fn parse_no_statement() {
13841 let p = parse_ok("no warnings");
13842 assert_eq!(p.statements.len(), 1);
13843 }
13844
13845 #[test]
13846 fn parse_require_bareword() {
13847 let p = parse_ok("require Foo::Bar");
13848 assert_eq!(p.statements.len(), 1);
13849 }
13850
13851 #[test]
13852 fn parse_require_string() {
13853 let p = parse_ok(r#"require "foo.pl""#);
13854 assert_eq!(p.statements.len(), 1);
13855 }
13856
13857 #[test]
13858 fn parse_eval_block() {
13859 let p = parse_ok("eval { 1 }");
13860 assert_eq!(p.statements.len(), 1);
13861 }
13862
13863 #[test]
13864 fn parse_eval_string() {
13865 let p = parse_ok(r#"eval "1 + 2""#);
13866 assert_eq!(p.statements.len(), 1);
13867 }
13868
13869 #[test]
13870 fn parse_qw_word_list() {
13871 let p = parse_ok("my @a = qw(foo bar baz)");
13872 assert_eq!(p.statements.len(), 1);
13873 }
13874
13875 #[test]
13876 fn parse_q_string() {
13877 let p = parse_ok("my $s = q{hello}");
13878 assert_eq!(p.statements.len(), 1);
13879 }
13880
13881 #[test]
13882 fn parse_qq_string() {
13883 let p = parse_ok(r#"my $s = qq(hello $x)"#);
13884 assert_eq!(p.statements.len(), 1);
13885 }
13886
13887 #[test]
13888 fn parse_regex_match() {
13889 let p = parse_ok(r#"$x =~ /foo/"#);
13890 assert_eq!(p.statements.len(), 1);
13891 }
13892
13893 #[test]
13894 fn parse_regex_substitution() {
13895 let p = parse_ok(r#"$x =~ s/foo/bar/g"#);
13896 assert_eq!(p.statements.len(), 1);
13897 }
13898
13899 #[test]
13900 fn parse_transliterate() {
13901 let p = parse_ok(r#"$x =~ tr/a-z/A-Z/"#);
13902 assert_eq!(p.statements.len(), 1);
13903 }
13904
13905 #[test]
13906 fn parse_ternary_operator() {
13907 let p = parse_ok("my $x = $a ? 1 : 2");
13908 assert_eq!(p.statements.len(), 1);
13909 }
13910
13911 #[test]
13912 fn parse_arrow_method_call() {
13913 let p = parse_ok("$obj->method()");
13914 assert_eq!(p.statements.len(), 1);
13915 }
13916
13917 #[test]
13918 fn parse_arrow_deref_hash() {
13919 let p = parse_ok("$r->{key}");
13920 assert_eq!(p.statements.len(), 1);
13921 }
13922
13923 #[test]
13924 fn parse_arrow_deref_array() {
13925 let p = parse_ok("$r->[0]");
13926 assert_eq!(p.statements.len(), 1);
13927 }
13928
13929 #[test]
13930 fn parse_chained_arrow_deref() {
13931 let p = parse_ok("$r->{a}[0]{b}");
13932 assert_eq!(p.statements.len(), 1);
13933 }
13934
13935 #[test]
13936 fn parse_my_multiple_vars() {
13937 let p = parse_ok("my ($a, $b, $c) = (1, 2, 3)");
13938 assert_eq!(p.statements.len(), 1);
13939 }
13940
13941 #[test]
13942 fn parse_our_scalar() {
13943 let p = parse_ok("our $VERSION = '1.0'");
13944 assert_eq!(p.statements.len(), 1);
13945 }
13946
13947 #[test]
13948 fn parse_local_scalar() {
13949 let p = parse_ok("local $/ = undef");
13950 assert_eq!(p.statements.len(), 1);
13951 }
13952
13953 #[test]
13954 fn parse_state_variable() {
13955 let p = parse_ok("sub counter { state $n = 0; $n++ }");
13956 assert_eq!(p.statements.len(), 1);
13957 }
13958
13959 #[test]
13960 fn parse_postfix_if() {
13961 let p = parse_ok("print 1 if $x");
13962 assert_eq!(p.statements.len(), 1);
13963 }
13964
13965 #[test]
13966 fn parse_postfix_unless() {
13967 let p = parse_ok("die 'error' unless $ok");
13968 assert_eq!(p.statements.len(), 1);
13969 }
13970
13971 #[test]
13972 fn parse_postfix_while() {
13973 let p = parse_ok("$x++ while $x < 10");
13974 assert_eq!(p.statements.len(), 1);
13975 }
13976
13977 #[test]
13978 fn parse_postfix_for() {
13979 let p = parse_ok("print for @arr");
13980 assert_eq!(p.statements.len(), 1);
13981 }
13982
13983 #[test]
13984 fn parse_last_next_redo() {
13985 let p = parse_ok("for (@a) { next if $_ < 0; last if $_ > 10 }");
13986 assert_eq!(p.statements.len(), 1);
13987 }
13988
13989 #[test]
13990 fn parse_return_statement() {
13991 let p = parse_ok("sub foo { return 42 }");
13992 assert_eq!(p.statements.len(), 1);
13993 }
13994
13995 #[test]
13996 fn parse_wantarray() {
13997 let p = parse_ok("sub foo { wantarray ? @a : $a }");
13998 assert_eq!(p.statements.len(), 1);
13999 }
14000
14001 #[test]
14002 fn parse_caller_builtin() {
14003 let p = parse_ok("my @c = caller");
14004 assert_eq!(p.statements.len(), 1);
14005 }
14006
14007 #[test]
14008 fn parse_ref_to_array() {
14009 let p = parse_ok("my $r = \\@arr");
14010 assert_eq!(p.statements.len(), 1);
14011 }
14012
14013 #[test]
14014 fn parse_ref_to_hash() {
14015 let p = parse_ok("my $r = \\%hash");
14016 assert_eq!(p.statements.len(), 1);
14017 }
14018
14019 #[test]
14020 fn parse_ref_to_scalar() {
14021 let p = parse_ok("my $r = \\$x");
14022 assert_eq!(p.statements.len(), 1);
14023 }
14024
14025 #[test]
14026 fn parse_deref_scalar() {
14027 let p = parse_ok("my $v = $$r");
14028 assert_eq!(p.statements.len(), 1);
14029 }
14030
14031 #[test]
14032 fn parse_deref_array() {
14033 let p = parse_ok("my @a = @$r");
14034 assert_eq!(p.statements.len(), 1);
14035 }
14036
14037 #[test]
14038 fn parse_deref_hash() {
14039 let p = parse_ok("my %h = %$r");
14040 assert_eq!(p.statements.len(), 1);
14041 }
14042
14043 #[test]
14044 fn parse_blessed_ref() {
14045 let p = parse_ok("bless $r, 'Foo'");
14046 assert_eq!(p.statements.len(), 1);
14047 }
14048
14049 #[test]
14050 fn parse_heredoc_basic() {
14051 let p = parse_ok("my $s = <<END;\nfoo\nEND");
14052 assert_eq!(p.statements.len(), 1);
14053 }
14054
14055 #[test]
14056 fn parse_heredoc_quoted() {
14057 let p = parse_ok("my $s = <<'END';\nfoo\nEND");
14058 assert_eq!(p.statements.len(), 1);
14059 }
14060
14061 #[test]
14062 fn parse_do_block() {
14063 let p = parse_ok("my $x = do { 1 + 2 }");
14064 assert_eq!(p.statements.len(), 1);
14065 }
14066
14067 #[test]
14068 fn parse_do_file() {
14069 let p = parse_ok(r#"do "foo.pl""#);
14070 assert_eq!(p.statements.len(), 1);
14071 }
14072
14073 #[test]
14074 fn parse_map_expression() {
14075 let p = parse_ok("my @b = map { $_ * 2 } @a");
14076 assert_eq!(p.statements.len(), 1);
14077 }
14078
14079 #[test]
14080 fn parse_grep_expression() {
14081 let p = parse_ok("my @b = grep { $_ > 0 } @a");
14082 assert_eq!(p.statements.len(), 1);
14083 }
14084
14085 #[test]
14086 fn parse_sort_expression() {
14087 let p = parse_ok("my @b = sort { $a <=> $b } @a");
14088 assert_eq!(p.statements.len(), 1);
14089 }
14090
14091 #[test]
14092 fn parse_pipe_forward() {
14093 let p = parse_ok("@a |> map { $_ * 2 }");
14094 assert_eq!(p.statements.len(), 1);
14095 }
14096
14097 #[test]
14098 fn parse_expression_from_str_simple() {
14099 let e = parse_expression_from_str("$x + 1", "-e").unwrap();
14100 assert!(matches!(e.kind, ExprKind::BinOp { .. }));
14101 }
14102
14103 #[test]
14104 fn parse_expression_from_str_extra_tokens_error() {
14105 let err = parse_expression_from_str("$x; $y", "-e").unwrap_err();
14106 assert!(err.message.contains("Extra tokens"));
14107 }
14108
14109 #[test]
14110 fn parse_slice_indices_from_str_basic() {
14111 let indices = parse_slice_indices_from_str("0, 1, 2", "-e").unwrap();
14112 assert_eq!(indices.len(), 3);
14113 }
14114
14115 #[test]
14116 fn parse_format_value_line_empty() {
14117 let exprs = parse_format_value_line("").unwrap();
14118 assert!(exprs.is_empty());
14119 }
14120
14121 #[test]
14122 fn parse_format_value_line_single() {
14123 let exprs = parse_format_value_line("$x").unwrap();
14124 assert_eq!(exprs.len(), 1);
14125 }
14126
14127 #[test]
14128 fn parse_format_value_line_multiple() {
14129 let exprs = parse_format_value_line("$a, $b, $c").unwrap();
14130 assert_eq!(exprs.len(), 3);
14131 }
14132
14133 #[test]
14134 fn parse_unclosed_brace_error() {
14135 let err = parse_err("sub foo {");
14136 assert!(!err.is_empty());
14137 }
14138
14139 #[test]
14140 fn parse_unclosed_paren_error() {
14141 let err = parse_err("print (1, 2");
14142 assert!(!err.is_empty());
14143 }
14144
14145 #[test]
14146 fn parse_invalid_statement_error() {
14147 let err = parse_err("???");
14148 assert!(!err.is_empty());
14149 }
14150
14151 #[test]
14152 fn merge_expr_list_single() {
14153 let e = Expr {
14154 kind: ExprKind::Integer(1),
14155 line: 1,
14156 };
14157 let merged = merge_expr_list(vec![e.clone()]);
14158 matches!(merged.kind, ExprKind::Integer(1));
14159 }
14160
14161 #[test]
14162 fn merge_expr_list_multiple() {
14163 let e1 = Expr {
14164 kind: ExprKind::Integer(1),
14165 line: 1,
14166 };
14167 let e2 = Expr {
14168 kind: ExprKind::Integer(2),
14169 line: 1,
14170 };
14171 let merged = merge_expr_list(vec![e1, e2]);
14172 matches!(merged.kind, ExprKind::List(_));
14173 }
14174}