1use crate::ast::*;
2use crate::error::{ErrorKind, PerlError, PerlResult};
3use crate::interpreter::Interpreter;
4use crate::lexer::{Lexer, LITERAL_DOLLAR_IN_DQUOTE};
5use crate::token::Token;
6
7fn postfix_lbracket_is_arrow_container(expr: &Expr) -> bool {
10 matches!(
11 expr.kind,
12 ExprKind::ArrayElement { .. }
13 | ExprKind::HashElement { .. }
14 | ExprKind::ArrowDeref { .. }
15 | ExprKind::Deref {
16 kind: Sigil::Scalar,
17 ..
18 }
19 )
20}
21
22fn destructure_stmt_from_var_decls(keyword: &str, decls: Vec<VarDecl>, line: usize) -> Statement {
23 let kind = match keyword {
24 "my" => StmtKind::My(decls),
25 "mysync" => StmtKind::MySync(decls),
26 "our" => StmtKind::Our(decls),
27 "local" => StmtKind::Local(decls),
28 "state" => StmtKind::State(decls),
29 _ => unreachable!("parse_my_our_local keyword"),
30 };
31 Statement {
32 label: None,
33 kind,
34 line,
35 }
36}
37
38fn destructure_stmt_die_string(line: usize, msg: &str) -> Statement {
39 Statement {
40 label: None,
41 kind: StmtKind::Expression(Expr {
42 kind: ExprKind::Die(vec![Expr {
43 kind: ExprKind::String(msg.to_string()),
44 line,
45 }]),
46 line,
47 }),
48 line,
49 }
50}
51
52fn destructure_stmt_unless_die(line: usize, cond: Expr, msg: &str) -> Statement {
53 Statement {
54 label: None,
55 kind: StmtKind::Unless {
56 condition: cond,
57 body: vec![destructure_stmt_die_string(line, msg)],
58 else_block: None,
59 },
60 line,
61 }
62}
63
64fn destructure_expr_scalar_tmp(name: &str, line: usize) -> Expr {
65 Expr {
66 kind: ExprKind::ScalarVar(name.to_string()),
67 line,
68 }
69}
70
71fn destructure_expr_array_len(tmp: &str, line: usize) -> Expr {
72 Expr {
73 kind: ExprKind::Deref {
74 expr: Box::new(destructure_expr_scalar_tmp(tmp, line)),
75 kind: Sigil::Array,
76 },
77 line,
78 }
79}
80
81pub struct Parser {
82 tokens: Vec<(Token, usize)>,
83 pos: usize,
84 next_rate_limit_slot: u32,
86 suppress_indirect_paren_call: u32,
89 pipe_rhs_depth: u32,
95 no_pipe_forward_depth: u32,
103 suppress_scalar_hash_brace: u32,
106 next_desugar_tmp: u32,
108 error_file: String,
110 declared_subs: std::collections::HashSet<String>,
112 suppress_parenless_call: u32,
116 suppress_slash_as_div: u32,
119 pub suppress_m_regex: u32,
122 suppress_colon_range: u32,
126 thread_last_mode: bool,
129}
130
131impl Parser {
132 pub fn new(tokens: Vec<(Token, usize)>) -> Self {
133 Self::new_with_file(tokens, "-e")
134 }
135
136 pub fn new_with_file(tokens: Vec<(Token, usize)>, file: impl Into<String>) -> Self {
137 Self {
138 tokens,
139 pos: 0,
140 next_rate_limit_slot: 0,
141 suppress_indirect_paren_call: 0,
142 pipe_rhs_depth: 0,
143 no_pipe_forward_depth: 0,
144 suppress_scalar_hash_brace: 0,
145 next_desugar_tmp: 0,
146 error_file: file.into(),
147 declared_subs: std::collections::HashSet::new(),
148 suppress_parenless_call: 0,
149 suppress_slash_as_div: 0,
150 suppress_m_regex: 0,
151 suppress_colon_range: 0,
152 thread_last_mode: false,
153 }
154 }
155
156 fn alloc_desugar_tmp(&mut self) -> u32 {
157 let n = self.next_desugar_tmp;
158 self.next_desugar_tmp = self.next_desugar_tmp.saturating_add(1);
159 n
160 }
161
162 #[inline]
166 fn in_pipe_rhs(&self) -> bool {
167 self.pipe_rhs_depth > 0
168 }
169
170 fn pipe_supplies_slurped_list_operand(&self) -> bool {
173 self.in_pipe_rhs()
174 && (matches!(
175 self.peek(),
176 Token::Semicolon
177 | Token::RBrace
178 | Token::RParen
179 | Token::Eof
180 | Token::Comma
181 | Token::PipeForward
182 ) || self.peek_line() > self.prev_line())
183 }
184
185 #[inline]
190 fn pipe_placeholder_list(&self, line: usize) -> Expr {
191 Expr {
192 kind: ExprKind::List(vec![]),
193 line,
194 }
195 }
196
197 fn lift_bareword_to_topic_call(expr: Expr) -> Expr {
208 let line = expr.line;
209 let topic = || Expr {
210 kind: ExprKind::ScalarVar("_".into()),
211 line,
212 };
213 match expr.kind {
214 ExprKind::Bareword(ref name) => Expr {
215 kind: ExprKind::FuncCall {
216 name: name.clone(),
217 args: vec![topic()],
218 },
219 line,
220 },
221 ExprKind::Unlink(ref args) if args.is_empty() => Expr {
223 kind: ExprKind::Unlink(vec![topic()]),
224 line,
225 },
226 ExprKind::Chmod(ref args) if args.is_empty() => Expr {
227 kind: ExprKind::Chmod(vec![topic()]),
228 line,
229 },
230 ExprKind::Stat(_) => expr,
232 ExprKind::Lstat(_) => expr,
233 ExprKind::Readlink(_) => expr,
234 ExprKind::Rev(ref inner) => {
236 if matches!(inner.kind, ExprKind::List(ref v) if v.is_empty()) {
237 Expr {
238 kind: ExprKind::Rev(Box::new(topic())),
239 line,
240 }
241 } else {
242 expr
243 }
244 }
245 _ => expr,
246 }
247 }
248
249 fn parse_assign_expr_stop_at_pipe(&mut self) -> PerlResult<Expr> {
257 self.no_pipe_forward_depth = self.no_pipe_forward_depth.saturating_add(1);
258 let r = self.parse_assign_expr();
259 self.no_pipe_forward_depth = self.no_pipe_forward_depth.saturating_sub(1);
260 r
261 }
262
263 fn syntax_err(&self, message: impl Into<String>, line: usize) -> PerlError {
264 PerlError::new(ErrorKind::Syntax, message, line, self.error_file.clone())
265 }
266
267 fn alloc_rate_limit_slot(&mut self) -> u32 {
268 let s = self.next_rate_limit_slot;
269 self.next_rate_limit_slot = self.next_rate_limit_slot.saturating_add(1);
270 s
271 }
272
273 fn peek(&self) -> &Token {
274 self.tokens
275 .get(self.pos)
276 .map(|(t, _)| t)
277 .unwrap_or(&Token::Eof)
278 }
279
280 fn peek_line(&self) -> usize {
281 self.tokens.get(self.pos).map(|(_, l)| *l).unwrap_or(0)
282 }
283
284 fn peek_at(&self, offset: usize) -> &Token {
285 self.tokens
286 .get(self.pos + offset)
287 .map(|(t, _)| t)
288 .unwrap_or(&Token::Eof)
289 }
290
291 fn advance(&mut self) -> (Token, usize) {
292 let tok = self
293 .tokens
294 .get(self.pos)
295 .cloned()
296 .unwrap_or((Token::Eof, 0));
297 self.pos += 1;
298 tok
299 }
300
301 fn prev_line(&self) -> usize {
303 if self.pos > 0 {
304 self.tokens.get(self.pos - 1).map(|(_, l)| *l).unwrap_or(0)
305 } else {
306 0
307 }
308 }
309
310 fn looks_like_hashref(&self) -> bool {
319 debug_assert!(matches!(self.peek(), Token::LBrace));
320 let tok1 = self.peek_at(1);
321 let tok2 = self.peek_at(2);
322 match tok1 {
323 Token::RBrace => true,
324 Token::Ident(_)
325 | Token::SingleString(_)
326 | Token::DoubleString(_)
327 | Token::ScalarVar(_)
328 | Token::Integer(_) => matches!(tok2, Token::FatArrow),
329 Token::HashVar(_) => matches!(tok2, Token::RBrace | Token::Comma),
330 _ => false,
331 }
332 }
333
334 fn expect(&mut self, expected: &Token) -> PerlResult<usize> {
335 let (tok, line) = self.advance();
336 if std::mem::discriminant(&tok) == std::mem::discriminant(expected) {
337 Ok(line)
338 } else {
339 Err(self.syntax_err(format!("Expected {:?}, got {:?}", expected, tok), line))
340 }
341 }
342
343 fn eat(&mut self, expected: &Token) -> bool {
344 if std::mem::discriminant(self.peek()) == std::mem::discriminant(expected) {
345 self.advance();
346 true
347 } else {
348 false
349 }
350 }
351
352 fn at_eof(&self) -> bool {
353 matches!(self.peek(), Token::Eof)
354 }
355
356 fn filetest_allows_implicit_topic(tok: &Token) -> bool {
358 matches!(
359 tok,
360 Token::RParen
361 | Token::Semicolon
362 | Token::Comma
363 | Token::RBrace
364 | Token::Eof
365 | Token::LogAnd
366 | Token::LogOr
367 | Token::LogAndWord
368 | Token::LogOrWord
369 | Token::PipeForward
370 )
371 }
372
373 fn next_is_new_stmt_keyword(&self, stmt_line: usize) -> bool {
377 if crate::compat_mode() {
379 return false;
380 }
381 if self.peek_line() == stmt_line {
382 return false;
383 }
384 matches!(
385 self.peek(),
386 Token::Ident(ref kw) if matches!(kw.as_str(),
387 "use" | "no" | "my" | "our" | "local" | "sub" | "struct" | "enum"
388 | "if" | "unless" | "while" | "until" | "for" | "foreach"
389 | "return" | "last" | "next" | "redo" | "package" | "require"
390 | "BEGIN" | "END" | "UNITCHECK" | "frozen" | "const" | "typed"
391 )
392 )
393 }
394
395 fn next_is_new_statement_start(&self, stmt_line: usize) -> bool {
399 if crate::compat_mode() {
400 return false;
401 }
402 if self.peek_line() == stmt_line {
403 return false;
404 }
405 matches!(
406 self.peek(),
407 Token::ScalarVar(_)
408 | Token::DerefScalarVar(_)
409 | Token::ArrayVar(_)
410 | Token::HashVar(_)
411 | Token::LBrace
412 ) || self.next_is_new_stmt_keyword(stmt_line)
413 }
414
415 pub fn parse_program(&mut self) -> PerlResult<Program> {
418 let statements = self.parse_statements()?;
419 Ok(Program { statements })
420 }
421
422 pub fn parse_statements(&mut self) -> PerlResult<Vec<Statement>> {
424 let mut statements = Vec::new();
425 while !self.at_eof() {
426 if matches!(self.peek(), Token::Semicolon) {
427 let line = self.peek_line();
428 self.advance();
429 statements.push(Statement {
430 label: None,
431 kind: StmtKind::Empty,
432 line,
433 });
434 continue;
435 }
436 statements.push(self.parse_statement()?);
437 }
438 Ok(statements)
439 }
440
441 fn parse_statement(&mut self) -> PerlResult<Statement> {
444 let line = self.peek_line();
445
446 let label = match self.peek().clone() {
449 Token::Ident(_) => {
450 if matches!(self.peek_at(1), Token::Colon)
451 && !matches!(self.peek_at(2), Token::Colon)
452 {
453 let (tok, _) = self.advance();
454 let l = match tok {
455 Token::Ident(l) => l,
456 _ => unreachable!(),
457 };
458 self.advance(); Some(l)
460 } else {
461 None
462 }
463 }
464 _ => None,
465 };
466
467 let mut stmt = match self.peek().clone() {
468 Token::FormatDecl { .. } => {
469 let tok_line = self.peek_line();
470 let (tok, _) = self.advance();
471 match tok {
472 Token::FormatDecl { name, lines } => Statement {
473 label: label.clone(),
474 kind: StmtKind::FormatDecl { name, lines },
475 line: tok_line,
476 },
477 _ => unreachable!(),
478 }
479 }
480 Token::Ident(ref kw) => match kw.as_str() {
481 "if" => self.parse_if()?,
482 "unless" => self.parse_unless()?,
483 "while" => {
484 let mut s = self.parse_while()?;
485 if let StmtKind::While {
486 label: ref mut lbl, ..
487 } = s.kind
488 {
489 *lbl = label.clone();
490 }
491 s
492 }
493 "until" => {
494 let mut s = self.parse_until()?;
495 if let StmtKind::Until {
496 label: ref mut lbl, ..
497 } = s.kind
498 {
499 *lbl = label.clone();
500 }
501 s
502 }
503 "for" => {
504 let mut s = self.parse_for_or_foreach()?;
505 match s.kind {
506 StmtKind::For {
507 label: ref mut lbl, ..
508 }
509 | StmtKind::Foreach {
510 label: ref mut lbl, ..
511 } => *lbl = label.clone(),
512 _ => {}
513 }
514 s
515 }
516 "foreach" => {
517 let mut s = self.parse_foreach()?;
518 if let StmtKind::Foreach {
519 label: ref mut lbl, ..
520 } = s.kind
521 {
522 *lbl = label.clone();
523 }
524 s
525 }
526 "sub" => {
527 if !crate::compat_mode() {
528 return Err(self.syntax_err(
529 "stryke uses `fn` instead of `sub` (this is not Perl 5)",
530 self.peek_line(),
531 ));
532 }
533 self.parse_sub_decl(true)?
534 }
535 "fn" => self.parse_sub_decl(false)?,
536 "struct" => {
537 if crate::compat_mode() {
538 return Err(self.syntax_err(
539 "`struct` is a stryke extension (disabled by --compat)",
540 self.peek_line(),
541 ));
542 }
543 self.parse_struct_decl()?
544 }
545 "enum" => {
546 if crate::compat_mode() {
547 return Err(self.syntax_err(
548 "`enum` is a stryke extension (disabled by --compat)",
549 self.peek_line(),
550 ));
551 }
552 self.parse_enum_decl()?
553 }
554 "class" => {
555 if crate::compat_mode() {
556 return Err(self.syntax_err(
558 "Perl 5.38 `class` syntax not yet implemented in --compat mode",
559 self.peek_line(),
560 ));
561 }
562 self.parse_class_decl(false, false)?
563 }
564 "abstract" => {
565 self.advance(); if !matches!(self.peek(), Token::Ident(ref s) if s == "class") {
567 return Err(self.syntax_err(
568 "`abstract` must be followed by `class`",
569 self.peek_line(),
570 ));
571 }
572 self.parse_class_decl(true, false)?
573 }
574 "final" => {
575 self.advance(); if !matches!(self.peek(), Token::Ident(ref s) if s == "class") {
577 return Err(self
578 .syntax_err("`final` must be followed by `class`", self.peek_line()));
579 }
580 self.parse_class_decl(false, true)?
581 }
582 "trait" => {
583 if crate::compat_mode() {
584 return Err(self.syntax_err(
585 "`trait` is a stryke extension (disabled by --compat)",
586 self.peek_line(),
587 ));
588 }
589 self.parse_trait_decl()?
590 }
591 "my" => self.parse_my_our_local("my", false)?,
592 "state" => self.parse_my_our_local("state", false)?,
593 "mysync" => {
594 if crate::compat_mode() {
595 return Err(self.syntax_err(
596 "`mysync` is a stryke extension (disabled by --compat)",
597 self.peek_line(),
598 ));
599 }
600 self.parse_my_our_local("mysync", false)?
601 }
602 "frozen" | "const" => {
603 let leading = kw.as_str().to_string();
604 if crate::compat_mode() {
605 return Err(self.syntax_err(
606 format!("`{leading}` is a stryke extension (disabled by --compat)"),
607 self.peek_line(),
608 ));
609 }
610 self.advance(); if let Token::Ident(ref kw) = self.peek().clone() {
616 if kw == "my" {
617 let mut stmt = self.parse_my_our_local("my", false)?;
618 if let StmtKind::My(ref mut decls) = stmt.kind {
619 for decl in decls.iter_mut() {
620 decl.frozen = true;
621 }
622 }
623 stmt
624 } else {
625 return Err(self.syntax_err(
626 format!("Expected 'my' after '{leading}'"),
627 self.peek_line(),
628 ));
629 }
630 } else {
631 return Err(self.syntax_err(
632 format!("Expected 'my' after '{leading}'"),
633 self.peek_line(),
634 ));
635 }
636 }
637 "typed" => {
638 if crate::compat_mode() {
639 return Err(self.syntax_err(
640 "`typed` is a stryke extension (disabled by --compat)",
641 self.peek_line(),
642 ));
643 }
644 self.advance();
645 if let Token::Ident(ref kw) = self.peek().clone() {
646 if kw == "my" {
647 self.parse_my_our_local("my", true)?
648 } else {
649 return Err(
650 self.syntax_err("Expected 'my' after 'typed'", self.peek_line())
651 );
652 }
653 } else {
654 return Err(
655 self.syntax_err("Expected 'my' after 'typed'", self.peek_line())
656 );
657 }
658 }
659 "our" => self.parse_my_our_local("our", false)?,
660 "local" => self.parse_my_our_local("local", false)?,
661 "package" => self.parse_package()?,
662 "use" => self.parse_use()?,
663 "no" => self.parse_no()?,
664 "return" => self.parse_return()?,
665 "last" => {
666 self.advance();
667 let lbl = if let Token::Ident(ref s) = self.peek() {
668 if s.chars().all(|c| c.is_uppercase() || c == '_') {
669 let (Token::Ident(l), _) = self.advance() else {
670 unreachable!()
671 };
672 Some(l)
673 } else {
674 None
675 }
676 } else {
677 None
678 };
679 let stmt = Statement {
680 label: None,
681 kind: StmtKind::Last(lbl.or(label.clone())),
682 line,
683 };
684 self.parse_stmt_postfix_modifier(stmt)?
685 }
686 "next" => {
687 self.advance();
688 let lbl = if let Token::Ident(ref s) = self.peek() {
689 if s.chars().all(|c| c.is_uppercase() || c == '_') {
690 let (Token::Ident(l), _) = self.advance() else {
691 unreachable!()
692 };
693 Some(l)
694 } else {
695 None
696 }
697 } else {
698 None
699 };
700 let stmt = Statement {
701 label: None,
702 kind: StmtKind::Next(lbl.or(label.clone())),
703 line,
704 };
705 self.parse_stmt_postfix_modifier(stmt)?
706 }
707 "redo" => {
708 self.advance();
709 self.eat(&Token::Semicolon);
710 Statement {
711 label: None,
712 kind: StmtKind::Redo(label.clone()),
713 line,
714 }
715 }
716 "BEGIN" => {
717 self.advance();
718 let block = self.parse_block()?;
719 Statement {
720 label: None,
721 kind: StmtKind::Begin(block),
722 line,
723 }
724 }
725 "END" => {
726 self.advance();
727 let block = self.parse_block()?;
728 Statement {
729 label: None,
730 kind: StmtKind::End(block),
731 line,
732 }
733 }
734 "UNITCHECK" => {
735 self.advance();
736 let block = self.parse_block()?;
737 Statement {
738 label: None,
739 kind: StmtKind::UnitCheck(block),
740 line,
741 }
742 }
743 "CHECK" => {
744 self.advance();
745 let block = self.parse_block()?;
746 Statement {
747 label: None,
748 kind: StmtKind::Check(block),
749 line,
750 }
751 }
752 "INIT" => {
753 self.advance();
754 let block = self.parse_block()?;
755 Statement {
756 label: None,
757 kind: StmtKind::Init(block),
758 line,
759 }
760 }
761 "goto" => {
762 self.advance();
763 let target = self.parse_expression()?;
764 let stmt = Statement {
765 label: None,
766 kind: StmtKind::Goto {
767 target: Box::new(target),
768 },
769 line,
770 };
771 self.parse_stmt_postfix_modifier(stmt)?
773 }
774 "continue" => {
775 self.advance();
776 let block = self.parse_block()?;
777 Statement {
778 label: None,
779 kind: StmtKind::Continue(block),
780 line,
781 }
782 }
783 "try" => self.parse_try_catch()?,
784 "defer" => self.parse_defer_stmt()?,
785 "tie" => self.parse_tie_stmt()?,
786 "given" => self.parse_given()?,
787 "when" => self.parse_when_stmt()?,
788 "default" => self.parse_default_stmt()?,
789 "eval_timeout" => self.parse_eval_timeout()?,
790 "do" => {
791 if matches!(self.peek_at(1), Token::LBrace) {
792 self.advance();
793 let body = self.parse_block()?;
794 if let Token::Ident(ref w) = self.peek().clone() {
795 if w == "while" {
796 self.advance();
797 self.expect(&Token::LParen)?;
798 let mut condition = self.parse_expression()?;
799 Self::mark_match_scalar_g_for_boolean_condition(&mut condition);
800 self.expect(&Token::RParen)?;
801 self.eat(&Token::Semicolon);
802 Statement {
803 label: label.clone(),
804 kind: StmtKind::DoWhile { body, condition },
805 line,
806 }
807 } else {
808 let inner_line = body.first().map(|s| s.line).unwrap_or(line);
809 let inner = Expr {
810 kind: ExprKind::CodeRef {
811 params: vec![],
812 body,
813 },
814 line: inner_line,
815 };
816 let expr = Expr {
817 kind: ExprKind::Do(Box::new(inner)),
818 line,
819 };
820 let stmt = Statement {
821 label: label.clone(),
822 kind: StmtKind::Expression(expr),
823 line,
824 };
825 self.parse_stmt_postfix_modifier(stmt)?
827 }
828 } else {
829 let inner_line = body.first().map(|s| s.line).unwrap_or(line);
830 let inner = Expr {
831 kind: ExprKind::CodeRef {
832 params: vec![],
833 body,
834 },
835 line: inner_line,
836 };
837 let expr = Expr {
838 kind: ExprKind::Do(Box::new(inner)),
839 line,
840 };
841 let stmt = Statement {
842 label: label.clone(),
843 kind: StmtKind::Expression(expr),
844 line,
845 };
846 self.parse_stmt_postfix_modifier(stmt)?
847 }
848 } else {
849 if let Some(expr) = self.try_parse_bareword_stmt_call() {
850 let stmt = self.maybe_postfix_modifier(expr)?;
851 self.parse_stmt_postfix_modifier(stmt)?
852 } else {
853 let expr = self.parse_expression()?;
854 let stmt = self.maybe_postfix_modifier(expr)?;
855 self.parse_stmt_postfix_modifier(stmt)?
856 }
857 }
858 }
859 _ => {
860 if let Some(expr) = self.try_parse_bareword_stmt_call() {
862 let stmt = self.maybe_postfix_modifier(expr)?;
863 self.parse_stmt_postfix_modifier(stmt)?
864 } else {
865 let expr = self.parse_expression()?;
866 let stmt = self.maybe_postfix_modifier(expr)?;
867 self.parse_stmt_postfix_modifier(stmt)?
868 }
869 }
870 },
871 Token::LBrace => {
872 if self.looks_like_hashref() {
875 let expr = self.parse_expression()?;
876 let stmt = self.maybe_postfix_modifier(expr)?;
877 self.parse_stmt_postfix_modifier(stmt)?
878 } else {
879 let block = self.parse_block()?;
880 let stmt = Statement {
881 label: None,
882 kind: StmtKind::Block(block),
883 line,
884 };
885 self.parse_stmt_postfix_modifier(stmt)?
887 }
888 }
889 _ => {
890 let expr = self.parse_expression()?;
891 let stmt = self.maybe_postfix_modifier(expr)?;
892 self.parse_stmt_postfix_modifier(stmt)?
893 }
894 };
895
896 stmt.label = label;
897 Ok(stmt)
898 }
899
900 fn parse_stmt_postfix_modifier(&mut self, stmt: Statement) -> PerlResult<Statement> {
902 let line = stmt.line;
903 if self.peek_line() > self.prev_line() {
908 self.eat(&Token::Semicolon);
909 return Ok(stmt);
910 }
911 if let Token::Ident(ref kw) = self.peek().clone() {
912 match kw.as_str() {
913 "if" => {
914 self.advance();
915 let mut cond = self.parse_expression()?;
916 Self::mark_match_scalar_g_for_boolean_condition(&mut cond);
917 self.eat(&Token::Semicolon);
918 return Ok(Statement {
919 label: None,
920 kind: StmtKind::If {
921 condition: cond,
922 body: vec![stmt],
923 elsifs: vec![],
924 else_block: None,
925 },
926 line,
927 });
928 }
929 "unless" => {
930 self.advance();
931 let mut cond = self.parse_expression()?;
932 Self::mark_match_scalar_g_for_boolean_condition(&mut cond);
933 self.eat(&Token::Semicolon);
934 return Ok(Statement {
935 label: None,
936 kind: StmtKind::Unless {
937 condition: cond,
938 body: vec![stmt],
939 else_block: None,
940 },
941 line,
942 });
943 }
944 "while" | "until" | "for" | "foreach" => {
945 if let Some(expr) = Self::stmt_into_postfix_body_expr(stmt) {
948 let out = self.maybe_postfix_modifier(expr)?;
949 self.eat(&Token::Semicolon);
950 return Ok(out);
951 }
952 return Err(self.syntax_err(
953 format!("postfix `{}` is not supported on this statement form", kw),
954 self.peek_line(),
955 ));
956 }
957 "pmap" | "pflat_map" | "pgrep" | "pfor" | "preduce" | "pcache" => {
959 let line = stmt.line;
960 let block = self.stmt_into_parallel_block(stmt)?;
961 let which = kw.as_str();
962 self.advance();
963 self.eat(&Token::Comma);
964 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
965 self.eat(&Token::Semicolon);
966 let list = Box::new(list);
967 let progress = progress.map(Box::new);
968 let kind = match which {
969 "pmap" => ExprKind::PMapExpr {
970 block,
971 list,
972 progress,
973 flat_outputs: false,
974 on_cluster: None,
975 stream: false,
976 },
977 "pflat_map" => ExprKind::PMapExpr {
978 block,
979 list,
980 progress,
981 flat_outputs: true,
982 on_cluster: None,
983 stream: false,
984 },
985 "pgrep" => ExprKind::PGrepExpr {
986 block,
987 list,
988 progress,
989 stream: false,
990 },
991 "pfor" => ExprKind::PForExpr {
992 block,
993 list,
994 progress,
995 },
996 "preduce" => ExprKind::PReduceExpr {
997 block,
998 list,
999 progress,
1000 },
1001 "pcache" => ExprKind::PcacheExpr {
1002 block,
1003 list,
1004 progress,
1005 },
1006 _ => unreachable!(),
1007 };
1008 return Ok(Statement {
1009 label: None,
1010 kind: StmtKind::Expression(Expr { kind, line }),
1011 line,
1012 });
1013 }
1014 _ => {}
1015 }
1016 }
1017 self.eat(&Token::Semicolon);
1018 Ok(stmt)
1019 }
1020
1021 fn stmt_into_parallel_block(&self, stmt: Statement) -> PerlResult<Block> {
1024 let line = stmt.line;
1025 match stmt.kind {
1026 StmtKind::Block(block) => Ok(block),
1027 StmtKind::Expression(expr) => {
1028 if let ExprKind::Do(ref inner) = expr.kind {
1029 if let ExprKind::CodeRef { ref body, .. } = inner.kind {
1030 return Ok(body.clone());
1031 }
1032 }
1033 Ok(vec![Statement {
1034 label: None,
1035 kind: StmtKind::Expression(expr),
1036 line,
1037 }])
1038 }
1039 _ => Err(self.syntax_err(
1040 "postfix parallel op expects `do { }`, a bare `{ }` block, or an expression statement",
1041 line,
1042 )),
1043 }
1044 }
1045
1046 fn stmt_into_postfix_body_expr(stmt: Statement) -> Option<Expr> {
1049 match stmt.kind {
1050 StmtKind::Expression(expr) => Some(expr),
1051 StmtKind::Block(block) => {
1052 let line = stmt.line;
1053 let inner = Expr {
1054 kind: ExprKind::CodeRef {
1055 params: vec![],
1056 body: block,
1057 },
1058 line,
1059 };
1060 Some(Expr {
1061 kind: ExprKind::Do(Box::new(inner)),
1062 line,
1063 })
1064 }
1065 _ => None,
1066 }
1067 }
1068
1069 fn peek_is_postfix_stmt_modifier_keyword(&self) -> bool {
1072 matches!(
1073 self.peek(),
1074 Token::Ident(ref kw)
1075 if matches!(
1076 kw.as_str(),
1077 "if" | "unless" | "while" | "until" | "for" | "foreach"
1078 )
1079 )
1080 }
1081
1082 fn maybe_postfix_modifier(&mut self, expr: Expr) -> PerlResult<Statement> {
1083 let line = expr.line;
1084 if self.peek_line() > self.prev_line() {
1086 return Ok(Statement {
1087 label: None,
1088 kind: StmtKind::Expression(expr),
1089 line,
1090 });
1091 }
1092 match self.peek() {
1093 Token::Ident(ref kw) => match kw.as_str() {
1094 "if" => {
1095 self.advance();
1096 let cond = self.parse_expression()?;
1097 Ok(Statement {
1098 label: None,
1099 kind: StmtKind::Expression(Expr {
1100 kind: ExprKind::PostfixIf {
1101 expr: Box::new(expr),
1102 condition: Box::new(cond),
1103 },
1104 line,
1105 }),
1106 line,
1107 })
1108 }
1109 "unless" => {
1110 self.advance();
1111 let cond = self.parse_expression()?;
1112 Ok(Statement {
1113 label: None,
1114 kind: StmtKind::Expression(Expr {
1115 kind: ExprKind::PostfixUnless {
1116 expr: Box::new(expr),
1117 condition: Box::new(cond),
1118 },
1119 line,
1120 }),
1121 line,
1122 })
1123 }
1124 "while" => {
1125 self.advance();
1126 let cond = self.parse_expression()?;
1127 Ok(Statement {
1128 label: None,
1129 kind: StmtKind::Expression(Expr {
1130 kind: ExprKind::PostfixWhile {
1131 expr: Box::new(expr),
1132 condition: Box::new(cond),
1133 },
1134 line,
1135 }),
1136 line,
1137 })
1138 }
1139 "until" => {
1140 self.advance();
1141 let cond = self.parse_expression()?;
1142 Ok(Statement {
1143 label: None,
1144 kind: StmtKind::Expression(Expr {
1145 kind: ExprKind::PostfixUntil {
1146 expr: Box::new(expr),
1147 condition: Box::new(cond),
1148 },
1149 line,
1150 }),
1151 line,
1152 })
1153 }
1154 "for" | "foreach" => {
1155 self.advance();
1156 let list = self.parse_expression()?;
1157 Ok(Statement {
1158 label: None,
1159 kind: StmtKind::Expression(Expr {
1160 kind: ExprKind::PostfixForeach {
1161 expr: Box::new(expr),
1162 list: Box::new(list),
1163 },
1164 line,
1165 }),
1166 line,
1167 })
1168 }
1169 _ => Ok(Statement {
1170 label: None,
1171 kind: StmtKind::Expression(expr),
1172 line,
1173 }),
1174 },
1175 _ => Ok(Statement {
1176 label: None,
1177 kind: StmtKind::Expression(expr),
1178 line,
1179 }),
1180 }
1181 }
1182
1183 fn try_parse_bareword_stmt_call(&mut self) -> Option<Expr> {
1185 let saved = self.pos;
1186 let line = self.peek_line();
1187 let mut name = match self.peek() {
1188 Token::Ident(n) => n.clone(),
1189 _ => return None,
1190 };
1191 if name.starts_with('\x00') || !Self::bareword_stmt_may_be_sub(&name) {
1193 return None;
1194 }
1195 self.advance();
1196 while self.eat(&Token::PackageSep) {
1197 match self.advance() {
1198 (Token::Ident(part), _) => {
1199 name = format!("{}::{}", name, part);
1200 }
1201 _ => {
1202 self.pos = saved;
1203 return None;
1204 }
1205 }
1206 }
1207 match self.peek() {
1208 Token::Semicolon | Token::RBrace => Some(Expr {
1209 kind: ExprKind::FuncCall { name, args: vec![] },
1210 line,
1211 }),
1212 _ => {
1213 self.pos = saved;
1214 None
1215 }
1216 }
1217 }
1218
1219 fn bareword_stmt_may_be_sub(name: &str) -> bool {
1221 !matches!(
1222 name,
1223 "__FILE__"
1224 | "__LINE__"
1225 | "abs"
1226 | "async"
1227 | "spawn"
1228 | "atan2"
1229 | "await"
1230 | "barrier"
1231 | "bless"
1232 | "caller"
1233 | "capture"
1234 | "cat"
1235 | "chdir"
1236 | "chmod"
1237 | "chomp"
1238 | "chop"
1239 | "chr"
1240 | "chown"
1241 | "closedir"
1242 | "close"
1243 | "collect"
1244 | "cos"
1245 | "crypt"
1246 | "defined"
1247 | "dec"
1248 | "delete"
1249 | "die"
1250 | "deque"
1251 | "do"
1252 | "each"
1253 | "eof"
1254 | "fore"
1255 | "eval"
1256 | "exec"
1257 | "exists"
1258 | "exit"
1259 | "exp"
1260 | "fan"
1261 | "fan_cap"
1262 | "fc"
1263 | "fetch_url"
1264 | "d"
1265 | "dirs"
1266 | "dr"
1267 | "f"
1268 | "fi"
1269 | "files"
1270 | "filesf"
1271 | "filter"
1272 | "fr"
1273 | "getcwd"
1274 | "glob_par"
1275 | "par_sed"
1276 | "glob"
1277 | "grep"
1278 | "greps"
1279 | "heap"
1280 | "hex"
1281 | "inc"
1282 | "index"
1283 | "int"
1284 | "join"
1285 | "keys"
1286 | "lcfirst"
1287 | "lc"
1288 | "length"
1289 | "link"
1290 | "log"
1291 | "lstat"
1292 | "map"
1293 | "flat_map"
1294 | "maps"
1295 | "flat_maps"
1296 | "flatten"
1297 | "frequencies"
1298 | "freq"
1299 | "interleave"
1300 | "ddump"
1301 | "stringify"
1302 | "str"
1303 | "s"
1304 | "input"
1305 | "lines"
1306 | "words"
1307 | "chars"
1308 | "digits"
1309 | "letters"
1310 | "letters_uc"
1311 | "letters_lc"
1312 | "punctuation"
1313 | "sentences"
1314 | "paragraphs"
1315 | "sections"
1316 | "numbers"
1317 | "graphemes"
1318 | "columns"
1319 | "trim"
1320 | "avg"
1321 | "top"
1322 | "pager"
1323 | "pg"
1324 | "less"
1325 | "count_by"
1326 | "to_file"
1327 | "to_json"
1328 | "to_csv"
1329 | "grep_v"
1330 | "select_keys"
1331 | "pluck"
1332 | "clamp"
1333 | "normalize"
1334 | "stddev"
1335 | "squared"
1336 | "square"
1337 | "cubed"
1338 | "cube"
1339 | "expt"
1340 | "pow"
1341 | "pw"
1342 | "snake_case"
1343 | "camel_case"
1344 | "kebab_case"
1345 | "to_toml"
1346 | "to_yaml"
1347 | "to_xml"
1348 | "to_html"
1349 | "to_markdown"
1350 | "xopen"
1351 | "clip"
1352 | "paste"
1353 | "to_table"
1354 | "sparkline"
1355 | "bar_chart"
1356 | "flame"
1357 | "set"
1358 | "list_count"
1359 | "list_size"
1360 | "count"
1361 | "size"
1362 | "cnt"
1363 | "len"
1364 | "all"
1365 | "any"
1366 | "none"
1367 | "take_while"
1368 | "drop_while"
1369 | "skip_while"
1370 | "skip"
1371 | "first_or"
1372 | "tap"
1373 | "peek"
1374 | "partition"
1375 | "min_by"
1376 | "max_by"
1377 | "zip_with"
1378 | "group_by"
1379 | "chunk_by"
1380 | "with_index"
1381 | "puniq"
1382 | "pfirst"
1383 | "pany"
1384 | "uniq"
1385 | "distinct"
1386 | "shuffle"
1387 | "shuffled"
1388 | "chunked"
1389 | "windowed"
1390 | "match"
1391 | "mkdir"
1392 | "every"
1393 | "gen"
1394 | "oct"
1395 | "open"
1396 | "p"
1397 | "opendir"
1398 | "ord"
1399 | "par_lines"
1400 | "par_walk"
1401 | "pipe"
1402 | "pipes"
1403 | "block_devices"
1404 | "char_devices"
1405 | "exe"
1406 | "executables"
1407 | "rate_limit"
1408 | "retry"
1409 | "pcache"
1410 | "pchannel"
1411 | "pfor"
1412 | "pgrep"
1413 | "pgreps"
1414 | "pipeline"
1415 | "pmap_chunked"
1416 | "pmap_reduce"
1417 | "pmap_on"
1418 | "pflat_map_on"
1419 | "pmap"
1420 | "pmaps"
1421 | "pflat_map"
1422 | "pflat_maps"
1423 | "pop"
1424 | "pos"
1425 | "ppool"
1426 | "preduce_init"
1427 | "preduce"
1428 | "pselect"
1429 | "printf"
1430 | "print"
1431 | "pr"
1432 | "psort"
1433 | "push"
1434 | "pwatch"
1435 | "rand"
1436 | "readdir"
1437 | "readlink"
1438 | "reduce"
1439 | "fold"
1440 | "inject"
1441 | "first"
1442 | "detect"
1443 | "find"
1444 | "find_all"
1445 | "ref"
1446 | "rename"
1447 | "require"
1448 | "rev"
1449 | "reverse"
1450 | "reversed"
1451 | "rewinddir"
1452 | "rindex"
1453 | "rmdir"
1454 | "rm"
1455 | "say"
1456 | "scalar"
1457 | "seekdir"
1458 | "shift"
1459 | "sin"
1460 | "slurp"
1461 | "sockets"
1462 | "sort"
1463 | "splice"
1464 | "split"
1465 | "sprintf"
1466 | "sqrt"
1467 | "srand"
1468 | "stat"
1469 | "study"
1470 | "substr"
1471 | "symlink"
1472 | "sym_links"
1473 | "system"
1474 | "telldir"
1475 | "timer"
1476 | "trace"
1477 | "ucfirst"
1478 | "uc"
1479 | "undef"
1480 | "umask"
1481 | "unlink"
1482 | "unshift"
1483 | "utime"
1484 | "values"
1485 | "wantarray"
1486 | "warn"
1487 | "watch"
1488 | "yield"
1489 | "sub"
1490 )
1491 }
1492
1493 fn parse_block(&mut self) -> PerlResult<Block> {
1494 self.expect(&Token::LBrace)?;
1495 let saved_pipe_rhs_depth = self.pipe_rhs_depth;
1498 self.pipe_rhs_depth = 0;
1499 let mut stmts = Vec::new();
1500 if let Some(param_stmts) = self.try_parse_block_params()? {
1504 stmts.extend(param_stmts);
1505 }
1506 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
1507 if self.eat(&Token::Semicolon) {
1508 continue;
1509 }
1510 stmts.push(self.parse_statement()?);
1511 }
1512 self.expect(&Token::RBrace)?;
1513 self.pipe_rhs_depth = saved_pipe_rhs_depth;
1514 Self::default_topic_for_sole_bareword(&mut stmts);
1515 Ok(stmts)
1516 }
1517
1518 fn try_parse_block_params(&mut self) -> PerlResult<Option<Vec<Statement>>> {
1523 if !matches!(self.peek(), Token::BitOr) {
1524 return Ok(None);
1525 }
1526 let mut i = 1; loop {
1529 match self.peek_at(i) {
1530 Token::ScalarVar(_) => i += 1,
1531 _ => return Ok(None), }
1533 match self.peek_at(i) {
1534 Token::BitOr => break, Token::Comma => i += 1, _ => return Ok(None), }
1538 }
1539 let line = self.peek_line();
1541 self.advance(); let mut names = Vec::new();
1543 loop {
1544 if let Token::ScalarVar(ref name) = self.peek().clone() {
1545 names.push(name.clone());
1546 self.advance();
1547 }
1548 if self.eat(&Token::BitOr) {
1549 break;
1550 }
1551 self.expect(&Token::Comma)?;
1552 }
1553 let sources: Vec<&str> = match names.len() {
1558 1 => vec!["_"],
1559 2 => vec!["a", "b"],
1560 n => {
1561 let _ = n;
1563 vec![] }
1565 };
1566 let mut stmts = Vec::with_capacity(names.len());
1567 if !sources.is_empty() {
1568 for (name, src) in names.iter().zip(sources.iter()) {
1569 stmts.push(Statement {
1570 label: None,
1571 kind: StmtKind::My(vec![VarDecl {
1572 sigil: Sigil::Scalar,
1573 name: name.clone(),
1574 initializer: Some(Expr {
1575 kind: ExprKind::ScalarVar(src.to_string()),
1576 line,
1577 }),
1578 frozen: false,
1579 type_annotation: None,
1580 }]),
1581 line,
1582 });
1583 }
1584 } else {
1585 for (idx, name) in names.iter().enumerate() {
1587 let src = if idx == 0 {
1588 "_".to_string()
1589 } else {
1590 format!("_{idx}")
1591 };
1592 stmts.push(Statement {
1593 label: None,
1594 kind: StmtKind::My(vec![VarDecl {
1595 sigil: Sigil::Scalar,
1596 name: name.clone(),
1597 initializer: Some(Expr {
1598 kind: ExprKind::ScalarVar(src),
1599 line,
1600 }),
1601 frozen: false,
1602 type_annotation: None,
1603 }]),
1604 line,
1605 });
1606 }
1607 }
1608 Ok(Some(stmts))
1609 }
1610
1611 fn default_topic_for_sole_bareword(stmts: &mut [Statement]) {
1626 let [only] = stmts else { return };
1627 let StmtKind::Expression(ref mut expr) = only.kind else {
1628 return;
1629 };
1630 let topic_line = expr.line;
1631 let topic_arg = || Expr {
1632 kind: ExprKind::ScalarVar("_".to_string()),
1633 line: topic_line,
1634 };
1635 match expr.kind {
1636 ExprKind::FuncCall {
1638 ref name,
1639 ref mut args,
1640 } if args.is_empty()
1641 && (Self::is_known_bareword(name) || Self::is_try_builtin_name(name)) =>
1642 {
1643 args.push(topic_arg());
1644 }
1645 ExprKind::Bareword(ref name)
1649 if (Self::is_known_bareword(name) || Self::is_try_builtin_name(name)) =>
1650 {
1651 let n = name.clone();
1652 expr.kind = ExprKind::FuncCall {
1653 name: n,
1654 args: vec![topic_arg()],
1655 };
1656 }
1657 _ => {}
1658 }
1659 }
1660
1661 fn parse_defer_stmt(&mut self) -> PerlResult<Statement> {
1665 let line = self.peek_line();
1666 self.advance(); let body = self.parse_block()?;
1668 self.eat(&Token::Semicolon);
1669 let coderef = Expr {
1671 kind: ExprKind::CodeRef {
1672 params: vec![],
1673 body,
1674 },
1675 line,
1676 };
1677 Ok(Statement {
1678 label: None,
1679 kind: StmtKind::Expression(Expr {
1680 kind: ExprKind::FuncCall {
1681 name: "defer__internal".to_string(),
1682 args: vec![coderef],
1683 },
1684 line,
1685 }),
1686 line,
1687 })
1688 }
1689
1690 fn parse_try_catch(&mut self) -> PerlResult<Statement> {
1692 let line = self.peek_line();
1693 self.advance(); let try_block = self.parse_block()?;
1695 match self.peek() {
1696 Token::Ident(ref k) if k == "catch" => {
1697 self.advance();
1698 }
1699 _ => {
1700 return Err(self.syntax_err("expected 'catch' after try block", self.peek_line()));
1701 }
1702 }
1703 self.expect(&Token::LParen)?;
1704 let catch_var = self.parse_scalar_var_name()?;
1705 self.expect(&Token::RParen)?;
1706 let catch_block = self.parse_block()?;
1707 let finally_block = match self.peek() {
1708 Token::Ident(ref k) if k == "finally" => {
1709 self.advance();
1710 Some(self.parse_block()?)
1711 }
1712 _ => None,
1713 };
1714 self.eat(&Token::Semicolon);
1715 Ok(Statement {
1716 label: None,
1717 kind: StmtKind::TryCatch {
1718 try_block,
1719 catch_var,
1720 catch_block,
1721 finally_block,
1722 },
1723 line,
1724 })
1725 }
1726
1727 fn parse_thread_macro(&mut self, _line: usize, thread_last: bool) -> PerlResult<Expr> {
1741 let saved_thread_last = self.thread_last_mode;
1743 self.thread_last_mode = thread_last;
1744
1745 let pipe_rhs_wrap = self.in_pipe_rhs();
1746 let mut result = if pipe_rhs_wrap {
1747 Expr {
1748 kind: ExprKind::ArrayElement {
1749 array: "_".to_string(),
1750 index: Box::new(Expr {
1751 kind: ExprKind::Integer(0),
1752 line: _line,
1753 }),
1754 },
1755 line: _line,
1756 }
1757 } else {
1758 self.suppress_parenless_call = self.suppress_parenless_call.saturating_add(1);
1761 let expr = self.parse_thread_input();
1762 self.suppress_parenless_call = self.suppress_parenless_call.saturating_sub(1);
1763 expr?
1764 };
1765
1766 let mut last_stage_end_line = self.prev_line();
1768
1769 loop {
1771 if self.peek_line() > last_stage_end_line {
1776 break;
1777 }
1778
1779 match self.peek() {
1783 Token::Semicolon
1784 | Token::RBrace
1785 | Token::RParen
1786 | Token::RBracket
1787 | Token::PipeForward
1788 | Token::Eof
1789 | Token::ScalarVar(_)
1790 | Token::ArrayVar(_)
1791 | Token::HashVar(_)
1792 | Token::Comma => break,
1793 Token::Ident(ref kw)
1794 if matches!(
1795 kw.as_str(),
1796 "my" | "our"
1797 | "local"
1798 | "state"
1799 | "if"
1800 | "unless"
1801 | "while"
1802 | "until"
1803 | "for"
1804 | "foreach"
1805 | "return"
1806 | "last"
1807 | "next"
1808 | "redo"
1809 ) =>
1810 {
1811 break
1812 }
1813 _ => {}
1814 }
1815
1816 let stage_line = self.peek_line();
1817
1818 match self.peek().clone() {
1820 Token::ArrowBrace => {
1822 self.advance(); let mut stmts = Vec::new();
1824 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
1825 if self.eat(&Token::Semicolon) {
1826 continue;
1827 }
1828 stmts.push(self.parse_statement()?);
1829 }
1830 self.expect(&Token::RBrace)?;
1831 let code_ref = Expr {
1832 kind: ExprKind::CodeRef {
1833 params: vec![],
1834 body: stmts,
1835 },
1836 line: stage_line,
1837 };
1838 result = self.pipe_forward_apply(result, code_ref, stage_line)?;
1839 }
1840 Token::Ident(ref name) if name == "sub" => {
1842 if !crate::compat_mode() {
1843 return Err(self.syntax_err(
1844 "stryke uses `fn {}` instead of `fn {}` (this is not Perl 5)",
1845 stage_line,
1846 ));
1847 }
1848 self.advance(); let (params, _prototype) = self.parse_sub_sig_or_prototype_opt()?;
1850 let body = self.parse_block()?;
1851 let code_ref = Expr {
1852 kind: ExprKind::CodeRef { params, body },
1853 line: stage_line,
1854 };
1855 result = self.pipe_forward_apply(result, code_ref, stage_line)?;
1856 }
1857 Token::Ident(ref name) if name == "fn" => {
1859 self.advance(); let (params, _prototype) = self.parse_sub_sig_or_prototype_opt()?;
1861 let body = self.parse_block()?;
1862 let code_ref = Expr {
1863 kind: ExprKind::CodeRef { params, body },
1864 line: stage_line,
1865 };
1866 result = self.pipe_forward_apply(result, code_ref, stage_line)?;
1867 }
1868 Token::Ident(ref name) => {
1870 let func_name = name.clone();
1871 self.advance();
1872
1873 if func_name.starts_with('\x00') {
1875 let parts: Vec<&str> = func_name.split('\x00').collect();
1876 if parts.len() >= 4 && parts[1] == "s" {
1877 let delim = parts.get(5).and_then(|s| s.chars().next()).unwrap_or('/');
1878 let stage = Expr {
1879 kind: ExprKind::Substitution {
1880 expr: Box::new(result.clone()),
1881 pattern: parts[2].to_string(),
1882 replacement: parts[3].to_string(),
1883 flags: format!("{}r", parts.get(4).unwrap_or(&"")),
1884 delim,
1885 },
1886 line: stage_line,
1887 };
1888 result = stage;
1889 last_stage_end_line = self.prev_line();
1890 continue;
1891 }
1892 if parts.len() >= 4 && parts[1] == "tr" {
1893 let delim = parts.get(5).and_then(|s| s.chars().next()).unwrap_or('/');
1894 let stage = Expr {
1895 kind: ExprKind::Transliterate {
1896 expr: Box::new(result.clone()),
1897 from: parts[2].to_string(),
1898 to: parts[3].to_string(),
1899 flags: format!("{}r", parts.get(4).unwrap_or(&"")),
1900 delim,
1901 },
1902 line: stage_line,
1903 };
1904 result = stage;
1905 last_stage_end_line = self.prev_line();
1906 continue;
1907 }
1908 return Err(
1909 self.syntax_err("Unexpected encoded token in thread", stage_line)
1910 );
1911 }
1912
1913 if matches!(self.peek(), Token::Plus)
1918 && matches!(self.peek_at(1), Token::LBrace)
1919 {
1920 self.advance(); self.expect(&Token::LBrace)?;
1922 let pairs = self.try_parse_hash_ref()?;
1924 let hashref_expr = Expr {
1925 kind: ExprKind::HashRef(pairs),
1926 line: stage_line,
1927 };
1928 let flatten_array_refs =
1929 matches!(func_name.as_str(), "flat_map" | "flat_maps");
1930 let stream = matches!(func_name.as_str(), "maps" | "flat_maps");
1931 let placeholder = Expr {
1933 kind: ExprKind::Undef,
1934 line: stage_line,
1935 };
1936 let map_node = Expr {
1937 kind: ExprKind::MapExprComma {
1938 expr: Box::new(hashref_expr),
1939 list: Box::new(placeholder),
1940 flatten_array_refs,
1941 stream,
1942 },
1943 line: stage_line,
1944 };
1945 result = self.pipe_forward_apply(result, map_node, stage_line)?;
1946 } else if func_name == "pmap_chunked" {
1948 let chunk_size = self.parse_assign_expr()?;
1949 let block = self.parse_block_or_bareword_block()?;
1950 let placeholder = self.pipe_placeholder_list(stage_line);
1951 let stage = Expr {
1952 kind: ExprKind::PMapChunkedExpr {
1953 chunk_size: Box::new(chunk_size),
1954 block,
1955 list: Box::new(placeholder),
1956 progress: None,
1957 },
1958 line: stage_line,
1959 };
1960 result = self.pipe_forward_apply(result, stage, stage_line)?;
1961 } else if func_name == "preduce_init" {
1963 let init = self.parse_assign_expr()?;
1964 let block = self.parse_block_or_bareword_block()?;
1965 let placeholder = self.pipe_placeholder_list(stage_line);
1966 let stage = Expr {
1967 kind: ExprKind::PReduceInitExpr {
1968 init: Box::new(init),
1969 block,
1970 list: Box::new(placeholder),
1971 progress: None,
1972 },
1973 line: stage_line,
1974 };
1975 result = self.pipe_forward_apply(result, stage, stage_line)?;
1976 } else if func_name == "pmap_reduce" {
1978 let map_block = self.parse_block_or_bareword_block()?;
1979 let reduce_block = if matches!(self.peek(), Token::LBrace) {
1980 self.parse_block()?
1981 } else {
1982 self.expect(&Token::Comma)?;
1983 self.parse_block_or_bareword_cmp_block()?
1984 };
1985 let placeholder = self.pipe_placeholder_list(stage_line);
1986 let stage = Expr {
1987 kind: ExprKind::PMapReduceExpr {
1988 map_block,
1989 reduce_block,
1990 list: Box::new(placeholder),
1991 progress: None,
1992 },
1993 line: stage_line,
1994 };
1995 result = self.pipe_forward_apply(result, stage, stage_line)?;
1996 } else if matches!(self.peek(), Token::LBrace) {
1998 self.pipe_rhs_depth = self.pipe_rhs_depth.saturating_add(1);
2000 let stage = self.parse_thread_stage_with_block(&func_name, stage_line)?;
2001 self.pipe_rhs_depth = self.pipe_rhs_depth.saturating_sub(1);
2002 result = self.pipe_forward_apply(result, stage, stage_line)?;
2003 } else if matches!(self.peek(), Token::LParen) {
2004 if func_name == "join" {
2007 self.advance(); let separator = self.parse_assign_expr()?;
2009 self.expect(&Token::RParen)?;
2010 let placeholder = self.pipe_placeholder_list(stage_line);
2011 let stage = Expr {
2012 kind: ExprKind::JoinExpr {
2013 separator: Box::new(separator),
2014 list: Box::new(placeholder),
2015 },
2016 line: stage_line,
2017 };
2018 result = self.pipe_forward_apply(result, stage, stage_line)?;
2019 } else if func_name == "split" {
2020 self.advance(); let pattern = self.parse_assign_expr()?;
2022 let limit = if self.eat(&Token::Comma) {
2023 Some(Box::new(self.parse_assign_expr()?))
2024 } else {
2025 None
2026 };
2027 self.expect(&Token::RParen)?;
2028 let placeholder = Expr {
2029 kind: ExprKind::ScalarVar("_".to_string()),
2030 line: stage_line,
2031 };
2032 let stage = Expr {
2033 kind: ExprKind::SplitExpr {
2034 pattern: Box::new(pattern),
2035 string: Box::new(placeholder),
2036 limit,
2037 },
2038 line: stage_line,
2039 };
2040 result = self.pipe_forward_apply(result, stage, stage_line)?;
2041 } else {
2042 self.advance(); let mut call_args = Vec::new();
2053 while !matches!(self.peek(), Token::RParen | Token::Eof) {
2054 call_args.push(self.parse_assign_expr()?);
2055 if !self.eat(&Token::Comma) {
2056 break;
2057 }
2058 }
2059 self.expect(&Token::RParen)?;
2060 if !call_args.iter().any(Self::expr_contains_topic_var) {
2064 let topic = Expr {
2065 kind: ExprKind::ScalarVar("_".to_string()),
2066 line: stage_line,
2067 };
2068 if self.thread_last_mode {
2069 call_args.push(topic);
2070 } else {
2071 call_args.insert(0, topic);
2072 }
2073 }
2074 let call_expr = Expr {
2075 kind: ExprKind::FuncCall {
2076 name: func_name.clone(),
2077 args: call_args,
2078 },
2079 line: stage_line,
2080 };
2081 let code_ref = Expr {
2082 kind: ExprKind::CodeRef {
2083 params: vec![],
2084 body: vec![Statement {
2085 label: None,
2086 kind: StmtKind::Expression(call_expr),
2087 line: stage_line,
2088 }],
2089 },
2090 line: stage_line,
2091 };
2092 result = self.pipe_forward_apply(result, code_ref, stage_line)?;
2093 }
2094 } else {
2095 result = self.thread_apply_bare_func(&func_name, result, stage_line)?;
2097 }
2098 }
2099 Token::Regex(ref pattern, ref flags, delim) => {
2101 let pattern = pattern.clone();
2102 let flags = flags.clone();
2103 self.advance();
2104 result =
2105 self.thread_regex_grep_stage(result, pattern, flags, delim, stage_line);
2106 }
2107 Token::Slash => {
2110 self.advance(); if let Token::Ident(ref ident_s) = self.peek().clone() {
2116 if matches!(ident_s.as_str(), "m" | "s" | "tr" | "y" | "qr")
2117 && matches!(self.peek_at(1), Token::Regex(..))
2118 {
2119 self.advance(); if let Token::Regex(ref misparsed_pattern, ref misparsed_flags, _) =
2126 self.peek().clone()
2127 {
2128 let _ = (misparsed_pattern, misparsed_flags);
2138 }
2139 }
2140 }
2141
2142 let mut pattern = String::new();
2144 loop {
2145 match self.peek().clone() {
2146 Token::Slash => {
2147 self.advance(); break;
2149 }
2150 Token::Eof | Token::Semicolon | Token::Newline => {
2151 return Err(self
2152 .syntax_err("Unterminated regex in thread stage", stage_line));
2153 }
2154 Token::Regex(ref inner_pattern, ref inner_flags, delim) => {
2156 if pattern.is_empty()
2164 || matches!(pattern.as_str(), "m" | "s" | "tr" | "y" | "qr")
2165 {
2166 let _ = (inner_pattern, inner_flags, delim);
2172 }
2173 return Err(self.syntax_err(
2175 "Complex regex in thread stage - use m/pattern/ syntax instead",
2176 stage_line,
2177 ));
2178 }
2179 Token::Ident(ref s) => {
2180 pattern.push_str(s);
2181 self.advance();
2182 }
2183 Token::Integer(n) => {
2184 pattern.push_str(&n.to_string());
2185 self.advance();
2186 }
2187 Token::ScalarVar(ref v) => {
2188 pattern.push('$');
2189 pattern.push_str(v);
2190 self.advance();
2191 }
2192 Token::Dot => {
2193 pattern.push('.');
2194 self.advance();
2195 }
2196 Token::Star => {
2197 pattern.push('*');
2198 self.advance();
2199 }
2200 Token::Plus => {
2201 pattern.push('+');
2202 self.advance();
2203 }
2204 Token::Question => {
2205 pattern.push('?');
2206 self.advance();
2207 }
2208 Token::LParen => {
2209 pattern.push('(');
2210 self.advance();
2211 }
2212 Token::RParen => {
2213 pattern.push(')');
2214 self.advance();
2215 }
2216 Token::LBracket => {
2217 pattern.push('[');
2218 self.advance();
2219 }
2220 Token::RBracket => {
2221 pattern.push(']');
2222 self.advance();
2223 }
2224 Token::Backslash => {
2225 pattern.push('\\');
2226 self.advance();
2227 }
2228 Token::BitOr => {
2229 pattern.push('|');
2230 self.advance();
2231 }
2232 Token::Power => {
2233 pattern.push_str("**");
2234 self.advance();
2235 }
2236 Token::BitXor => {
2237 pattern.push('^');
2238 self.advance();
2239 }
2240 Token::Minus => {
2241 pattern.push('-');
2242 self.advance();
2243 }
2244 _ => {
2245 return Err(self.syntax_err(
2246 format!("Unexpected token in regex pattern: {:?}", self.peek()),
2247 stage_line,
2248 ));
2249 }
2250 }
2251 }
2252 let mut flags = String::new();
2256 if let Token::Ident(ref s) = self.peek().clone() {
2257 let is_flag_only =
2258 s.chars().all(|c| "gimsxecor".contains(c)) && s.len() <= 6;
2259 let followed_by_brace = matches!(self.peek_at(1), Token::LBrace);
2260 if is_flag_only && !followed_by_brace {
2261 flags.push_str(s);
2262 self.advance();
2263 }
2264 }
2265 result = self.thread_regex_grep_stage(result, pattern, flags, '/', stage_line);
2266 }
2267 tok => {
2268 return Err(self.syntax_err(
2269 format!(
2270 "thread: expected stage (ident, fn {{}}, s///, tr///, or /re/), got {:?}",
2271 tok
2272 ),
2273 stage_line,
2274 ));
2275 }
2276 };
2277 last_stage_end_line = self.prev_line();
2278 }
2279
2280 self.thread_last_mode = saved_thread_last;
2282
2283 if pipe_rhs_wrap {
2284 let body_line = result.line;
2287 return Ok(Expr {
2288 kind: ExprKind::CodeRef {
2289 params: vec![],
2290 body: vec![Statement {
2291 label: None,
2292 kind: StmtKind::Expression(result),
2293 line: body_line,
2294 }],
2295 },
2296 line: _line,
2297 });
2298 }
2299 Ok(result)
2300 }
2301
2302 fn thread_regex_grep_stage(
2304 &self,
2305 list: Expr,
2306 pattern: String,
2307 flags: String,
2308 delim: char,
2309 line: usize,
2310 ) -> Expr {
2311 let topic = Expr {
2312 kind: ExprKind::ScalarVar("_".to_string()),
2313 line,
2314 };
2315 let match_expr = Expr {
2316 kind: ExprKind::Match {
2317 expr: Box::new(topic),
2318 pattern,
2319 flags,
2320 scalar_g: false,
2321 delim,
2322 },
2323 line,
2324 };
2325 let block = vec![Statement {
2326 label: None,
2327 kind: StmtKind::Expression(match_expr),
2328 line,
2329 }];
2330 Expr {
2331 kind: ExprKind::GrepExpr {
2332 block,
2333 list: Box::new(list),
2334 keyword: crate::ast::GrepBuiltinKeyword::Grep,
2335 },
2336 line,
2337 }
2338 }
2339
2340 fn expr_contains_topic_var(e: &Expr) -> bool {
2351 format!("{:?}", e).contains("ScalarVar(\"_\")")
2352 }
2353
2354 fn thread_apply_bare_func(&self, name: &str, arg: Expr, line: usize) -> PerlResult<Expr> {
2356 let kind = match name {
2357 "uc" => ExprKind::Uc(Box::new(arg)),
2359 "lc" => ExprKind::Lc(Box::new(arg)),
2360 "ucfirst" | "ufc" => ExprKind::Ucfirst(Box::new(arg)),
2361 "lcfirst" | "lfc" => ExprKind::Lcfirst(Box::new(arg)),
2362 "fc" => ExprKind::Fc(Box::new(arg)),
2363 "chomp" => ExprKind::Chomp(Box::new(arg)),
2364 "chop" => ExprKind::Chop(Box::new(arg)),
2365 "length" => ExprKind::Length(Box::new(arg)),
2366 "len" | "cnt" => ExprKind::FuncCall {
2367 name: "count".to_string(),
2368 args: vec![arg],
2369 },
2370 "quotemeta" | "qm" => ExprKind::FuncCall {
2371 name: "quotemeta".to_string(),
2372 args: vec![arg],
2373 },
2374 "abs" => ExprKind::Abs(Box::new(arg)),
2376 "int" => ExprKind::Int(Box::new(arg)),
2377 "sqrt" | "sq" => ExprKind::Sqrt(Box::new(arg)),
2378 "sin" => ExprKind::Sin(Box::new(arg)),
2379 "cos" => ExprKind::Cos(Box::new(arg)),
2380 "exp" => ExprKind::Exp(Box::new(arg)),
2381 "log" => ExprKind::Log(Box::new(arg)),
2382 "hex" => ExprKind::Hex(Box::new(arg)),
2383 "oct" => ExprKind::Oct(Box::new(arg)),
2384 "chr" => ExprKind::Chr(Box::new(arg)),
2385 "ord" => ExprKind::Ord(Box::new(arg)),
2386 "defined" | "def" => ExprKind::Defined(Box::new(arg)),
2388 "ref" => ExprKind::Ref(Box::new(arg)),
2389 "scalar" => ExprKind::ScalarContext(Box::new(arg)),
2390 "keys" => ExprKind::Keys(Box::new(arg)),
2392 "values" => ExprKind::Values(Box::new(arg)),
2393 "each" => ExprKind::Each(Box::new(arg)),
2394 "pop" => ExprKind::Pop(Box::new(arg)),
2395 "shift" => ExprKind::Shift(Box::new(arg)),
2396 "reverse" => {
2397 if !crate::compat_mode() {
2398 return Err(self.syntax_err(
2399 "stryke uses `rev` instead of `reverse` (this is not Perl 5)",
2400 line,
2401 ));
2402 }
2403 ExprKind::ReverseExpr(Box::new(arg))
2404 }
2405 "reversed" | "rv" | "rev" => ExprKind::Rev(Box::new(arg)),
2406 "sort" | "so" => ExprKind::SortExpr {
2407 cmp: None,
2408 list: Box::new(arg),
2409 },
2410 "uniq" | "distinct" | "uq" => ExprKind::FuncCall {
2411 name: "uniq".to_string(),
2412 args: vec![arg],
2413 },
2414 "trim" | "tm" => ExprKind::FuncCall {
2415 name: "trim".to_string(),
2416 args: vec![arg],
2417 },
2418 "flatten" | "fl" => ExprKind::FuncCall {
2419 name: "flatten".to_string(),
2420 args: vec![arg],
2421 },
2422 "compact" | "cpt" => ExprKind::FuncCall {
2423 name: "compact".to_string(),
2424 args: vec![arg],
2425 },
2426 "shuffle" | "shuf" => ExprKind::FuncCall {
2427 name: "shuffle".to_string(),
2428 args: vec![arg],
2429 },
2430 "frequencies" | "freq" | "frq" => ExprKind::FuncCall {
2431 name: "frequencies".to_string(),
2432 args: vec![arg],
2433 },
2434 "dedup" | "dup" => ExprKind::FuncCall {
2435 name: "dedup".to_string(),
2436 args: vec![arg],
2437 },
2438 "enumerate" | "en" => ExprKind::FuncCall {
2439 name: "enumerate".to_string(),
2440 args: vec![arg],
2441 },
2442 "lines" | "ln" => ExprKind::FuncCall {
2443 name: "lines".to_string(),
2444 args: vec![arg],
2445 },
2446 "words" | "wd" => ExprKind::FuncCall {
2447 name: "words".to_string(),
2448 args: vec![arg],
2449 },
2450 "chars" | "ch" => ExprKind::FuncCall {
2451 name: "chars".to_string(),
2452 args: vec![arg],
2453 },
2454 "digits" | "dg" => ExprKind::FuncCall {
2455 name: "digits".to_string(),
2456 args: vec![arg],
2457 },
2458 "letters" | "lts" => ExprKind::FuncCall {
2459 name: "letters".to_string(),
2460 args: vec![arg],
2461 },
2462 "letters_uc" => ExprKind::FuncCall {
2463 name: "letters_uc".to_string(),
2464 args: vec![arg],
2465 },
2466 "letters_lc" => ExprKind::FuncCall {
2467 name: "letters_lc".to_string(),
2468 args: vec![arg],
2469 },
2470 "punctuation" | "punct" => ExprKind::FuncCall {
2471 name: "punctuation".to_string(),
2472 args: vec![arg],
2473 },
2474 "sentences" | "sents" => ExprKind::FuncCall {
2475 name: "sentences".to_string(),
2476 args: vec![arg],
2477 },
2478 "paragraphs" | "paras" => ExprKind::FuncCall {
2479 name: "paragraphs".to_string(),
2480 args: vec![arg],
2481 },
2482 "sections" | "sects" => ExprKind::FuncCall {
2483 name: "sections".to_string(),
2484 args: vec![arg],
2485 },
2486 "numbers" | "nums" => ExprKind::FuncCall {
2487 name: "numbers".to_string(),
2488 args: vec![arg],
2489 },
2490 "graphemes" | "grs" => ExprKind::FuncCall {
2491 name: "graphemes".to_string(),
2492 args: vec![arg],
2493 },
2494 "columns" | "cols" => ExprKind::FuncCall {
2495 name: "columns".to_string(),
2496 args: vec![arg],
2497 },
2498 "slurp" | "sl" => ExprKind::Slurp(Box::new(arg)),
2500 "chdir" => ExprKind::Chdir(Box::new(arg)),
2501 "stat" => ExprKind::Stat(Box::new(arg)),
2502 "lstat" => ExprKind::Lstat(Box::new(arg)),
2503 "readlink" => ExprKind::Readlink(Box::new(arg)),
2504 "readdir" => ExprKind::Readdir(Box::new(arg)),
2505 "close" => ExprKind::Close(Box::new(arg)),
2506 "basename" | "bn" => ExprKind::FuncCall {
2507 name: "basename".to_string(),
2508 args: vec![arg],
2509 },
2510 "dirname" | "dn" => ExprKind::FuncCall {
2511 name: "dirname".to_string(),
2512 args: vec![arg],
2513 },
2514 "realpath" | "rp" => ExprKind::FuncCall {
2515 name: "realpath".to_string(),
2516 args: vec![arg],
2517 },
2518 "which" | "wh" => ExprKind::FuncCall {
2519 name: "which".to_string(),
2520 args: vec![arg],
2521 },
2522 "eval" => ExprKind::Eval(Box::new(arg)),
2524 "require" => ExprKind::Require(Box::new(arg)),
2525 "study" => ExprKind::Study(Box::new(arg)),
2526 "snake_case" | "sc" => ExprKind::FuncCall {
2528 name: "snake_case".to_string(),
2529 args: vec![arg],
2530 },
2531 "camel_case" | "cc" => ExprKind::FuncCall {
2532 name: "camel_case".to_string(),
2533 args: vec![arg],
2534 },
2535 "kebab_case" | "kc" => ExprKind::FuncCall {
2536 name: "kebab_case".to_string(),
2537 args: vec![arg],
2538 },
2539 "to_json" | "tj" => ExprKind::FuncCall {
2541 name: "to_json".to_string(),
2542 args: vec![arg],
2543 },
2544 "to_yaml" | "ty" => ExprKind::FuncCall {
2545 name: "to_yaml".to_string(),
2546 args: vec![arg],
2547 },
2548 "to_toml" | "tt" => ExprKind::FuncCall {
2549 name: "to_toml".to_string(),
2550 args: vec![arg],
2551 },
2552 "to_csv" | "tc" => ExprKind::FuncCall {
2553 name: "to_csv".to_string(),
2554 args: vec![arg],
2555 },
2556 "to_xml" | "tx" => ExprKind::FuncCall {
2557 name: "to_xml".to_string(),
2558 args: vec![arg],
2559 },
2560 "to_html" | "th" => ExprKind::FuncCall {
2561 name: "to_html".to_string(),
2562 args: vec![arg],
2563 },
2564 "to_markdown" | "to_md" | "tmd" => ExprKind::FuncCall {
2565 name: "to_markdown".to_string(),
2566 args: vec![arg],
2567 },
2568 "xopen" | "xo" => ExprKind::FuncCall {
2569 name: "xopen".to_string(),
2570 args: vec![arg],
2571 },
2572 "clip" | "clipboard" | "pbcopy" => ExprKind::FuncCall {
2573 name: "clip".to_string(),
2574 args: vec![arg],
2575 },
2576 "to_table" | "table" | "tbl" => ExprKind::FuncCall {
2577 name: "to_table".to_string(),
2578 args: vec![arg],
2579 },
2580 "sparkline" | "spark" => ExprKind::FuncCall {
2581 name: "sparkline".to_string(),
2582 args: vec![arg],
2583 },
2584 "bar_chart" | "bars" => ExprKind::FuncCall {
2585 name: "bar_chart".to_string(),
2586 args: vec![arg],
2587 },
2588 "flame" | "flamechart" => ExprKind::FuncCall {
2589 name: "flame".to_string(),
2590 args: vec![arg],
2591 },
2592 "ddump" | "dd" => ExprKind::FuncCall {
2593 name: "ddump".to_string(),
2594 args: vec![arg],
2595 },
2596 "say" => {
2597 if !crate::compat_mode() {
2598 return Err(self.syntax_err(
2599 "stryke uses `p` instead of `say` (this is not Perl 5)",
2600 line,
2601 ));
2602 }
2603 ExprKind::Say {
2604 handle: None,
2605 args: vec![arg],
2606 }
2607 }
2608 "p" => ExprKind::Say {
2609 handle: None,
2610 args: vec![arg],
2611 },
2612 "print" => ExprKind::Print {
2613 handle: None,
2614 args: vec![arg],
2615 },
2616 "warn" => ExprKind::Warn(vec![arg]),
2617 "die" => ExprKind::Die(vec![arg]),
2618 "stringify" | "str" => ExprKind::FuncCall {
2619 name: "stringify".to_string(),
2620 args: vec![arg],
2621 },
2622 "json_decode" | "jd" => ExprKind::FuncCall {
2623 name: "json_decode".to_string(),
2624 args: vec![arg],
2625 },
2626 "yaml_decode" | "yd" => ExprKind::FuncCall {
2627 name: "yaml_decode".to_string(),
2628 args: vec![arg],
2629 },
2630 "toml_decode" | "td" => ExprKind::FuncCall {
2631 name: "toml_decode".to_string(),
2632 args: vec![arg],
2633 },
2634 "xml_decode" | "xd" => ExprKind::FuncCall {
2635 name: "xml_decode".to_string(),
2636 args: vec![arg],
2637 },
2638 "json_encode" | "je" => ExprKind::FuncCall {
2639 name: "json_encode".to_string(),
2640 args: vec![arg],
2641 },
2642 "yaml_encode" | "ye" => ExprKind::FuncCall {
2643 name: "yaml_encode".to_string(),
2644 args: vec![arg],
2645 },
2646 "toml_encode" | "te" => ExprKind::FuncCall {
2647 name: "toml_encode".to_string(),
2648 args: vec![arg],
2649 },
2650 "xml_encode" | "xe" => ExprKind::FuncCall {
2651 name: "xml_encode".to_string(),
2652 args: vec![arg],
2653 },
2654 "base64_encode" | "b64e" => ExprKind::FuncCall {
2656 name: "base64_encode".to_string(),
2657 args: vec![arg],
2658 },
2659 "base64_decode" | "b64d" => ExprKind::FuncCall {
2660 name: "base64_decode".to_string(),
2661 args: vec![arg],
2662 },
2663 "hex_encode" | "hxe" => ExprKind::FuncCall {
2664 name: "hex_encode".to_string(),
2665 args: vec![arg],
2666 },
2667 "hex_decode" | "hxd" => ExprKind::FuncCall {
2668 name: "hex_decode".to_string(),
2669 args: vec![arg],
2670 },
2671 "url_encode" | "uri_escape" | "ue" => ExprKind::FuncCall {
2672 name: "url_encode".to_string(),
2673 args: vec![arg],
2674 },
2675 "url_decode" | "uri_unescape" | "ud" => ExprKind::FuncCall {
2676 name: "url_decode".to_string(),
2677 args: vec![arg],
2678 },
2679 "gzip" | "gz" => ExprKind::FuncCall {
2680 name: "gzip".to_string(),
2681 args: vec![arg],
2682 },
2683 "gunzip" | "ugz" => ExprKind::FuncCall {
2684 name: "gunzip".to_string(),
2685 args: vec![arg],
2686 },
2687 "zstd" | "zst" => ExprKind::FuncCall {
2688 name: "zstd".to_string(),
2689 args: vec![arg],
2690 },
2691 "zstd_decode" | "uzst" => ExprKind::FuncCall {
2692 name: "zstd_decode".to_string(),
2693 args: vec![arg],
2694 },
2695 "sha256" | "s256" => ExprKind::FuncCall {
2697 name: "sha256".to_string(),
2698 args: vec![arg],
2699 },
2700 "sha1" | "s1" => ExprKind::FuncCall {
2701 name: "sha1".to_string(),
2702 args: vec![arg],
2703 },
2704 "md5" | "m5" => ExprKind::FuncCall {
2705 name: "md5".to_string(),
2706 args: vec![arg],
2707 },
2708 "uuid" | "uid" => ExprKind::FuncCall {
2709 name: "uuid".to_string(),
2710 args: vec![arg],
2711 },
2712 "datetime_utc" | "utc" => ExprKind::FuncCall {
2714 name: "datetime_utc".to_string(),
2715 args: vec![arg],
2716 },
2717 "e" | "fore" | "ep" => ExprKind::ForEachExpr {
2720 block: vec![Statement {
2721 label: None,
2722 kind: StmtKind::Expression(Expr {
2723 kind: ExprKind::Say {
2724 handle: None,
2725 args: vec![Expr {
2726 kind: ExprKind::ScalarVar("_".into()),
2727 line,
2728 }],
2729 },
2730 line,
2731 }),
2732 line,
2733 }],
2734 list: Box::new(arg),
2735 },
2736 _ => ExprKind::FuncCall {
2738 name: name.to_string(),
2739 args: vec![arg],
2740 },
2741 };
2742 Ok(Expr { kind, line })
2743 }
2744
2745 fn parse_thread_stage_with_block(&mut self, name: &str, line: usize) -> PerlResult<Expr> {
2748 let block = self.parse_block()?;
2749 let placeholder = self.pipe_placeholder_list(line);
2751
2752 match name {
2753 "map" | "flat_map" | "maps" | "flat_maps" => {
2754 let flatten_array_refs = matches!(name, "flat_map" | "flat_maps");
2755 let stream = matches!(name, "maps" | "flat_maps");
2756 Ok(Expr {
2757 kind: ExprKind::MapExpr {
2758 block,
2759 list: Box::new(placeholder),
2760 flatten_array_refs,
2761 stream,
2762 },
2763 line,
2764 })
2765 }
2766 "grep" | "greps" | "filter" | "fi" | "find_all" | "gr" => {
2767 let keyword = match name {
2768 "grep" | "gr" => crate::ast::GrepBuiltinKeyword::Grep,
2769 "greps" => crate::ast::GrepBuiltinKeyword::Greps,
2770 "filter" | "fi" => crate::ast::GrepBuiltinKeyword::Filter,
2771 "find_all" => crate::ast::GrepBuiltinKeyword::FindAll,
2772 _ => unreachable!(),
2773 };
2774 Ok(Expr {
2775 kind: ExprKind::GrepExpr {
2776 block,
2777 list: Box::new(placeholder),
2778 keyword,
2779 },
2780 line,
2781 })
2782 }
2783 "sort" | "so" => Ok(Expr {
2784 kind: ExprKind::SortExpr {
2785 cmp: Some(SortComparator::Block(block)),
2786 list: Box::new(placeholder),
2787 },
2788 line,
2789 }),
2790 "reduce" | "rd" => Ok(Expr {
2791 kind: ExprKind::ReduceExpr {
2792 block,
2793 list: Box::new(placeholder),
2794 },
2795 line,
2796 }),
2797 "fore" | "e" | "ep" => Ok(Expr {
2798 kind: ExprKind::ForEachExpr {
2799 block,
2800 list: Box::new(placeholder),
2801 },
2802 line,
2803 }),
2804 "pmap" | "pflat_map" | "pmaps" | "pflat_maps" => Ok(Expr {
2805 kind: ExprKind::PMapExpr {
2806 block,
2807 list: Box::new(placeholder),
2808 progress: None,
2809 flat_outputs: name == "pflat_map" || name == "pflat_maps",
2810 on_cluster: None,
2811 stream: name == "pmaps" || name == "pflat_maps",
2812 },
2813 line,
2814 }),
2815 "pgrep" | "pgreps" => Ok(Expr {
2816 kind: ExprKind::PGrepExpr {
2817 block,
2818 list: Box::new(placeholder),
2819 progress: None,
2820 stream: name == "pgreps",
2821 },
2822 line,
2823 }),
2824 "pfor" => Ok(Expr {
2825 kind: ExprKind::PForExpr {
2826 block,
2827 list: Box::new(placeholder),
2828 progress: None,
2829 },
2830 line,
2831 }),
2832 "preduce" => Ok(Expr {
2833 kind: ExprKind::PReduceExpr {
2834 block,
2835 list: Box::new(placeholder),
2836 progress: None,
2837 },
2838 line,
2839 }),
2840 "pcache" => Ok(Expr {
2841 kind: ExprKind::PcacheExpr {
2842 block,
2843 list: Box::new(placeholder),
2844 progress: None,
2845 },
2846 line,
2847 }),
2848 "psort" => Ok(Expr {
2849 kind: ExprKind::PSortExpr {
2850 cmp: Some(block),
2851 list: Box::new(placeholder),
2852 progress: None,
2853 },
2854 line,
2855 }),
2856 _ => {
2857 let code_ref = Expr {
2859 kind: ExprKind::CodeRef {
2860 params: vec![],
2861 body: block,
2862 },
2863 line,
2864 };
2865 Ok(Expr {
2866 kind: ExprKind::FuncCall {
2867 name: name.to_string(),
2868 args: vec![code_ref],
2869 },
2870 line,
2871 })
2872 }
2873 }
2874 }
2875
2876 fn parse_tie_stmt(&mut self) -> PerlResult<Statement> {
2878 let line = self.peek_line();
2879 self.advance(); let target = match self.peek().clone() {
2881 Token::HashVar(h) => {
2882 self.advance();
2883 TieTarget::Hash(h)
2884 }
2885 Token::ArrayVar(a) => {
2886 self.advance();
2887 TieTarget::Array(a)
2888 }
2889 Token::ScalarVar(s) => {
2890 self.advance();
2891 TieTarget::Scalar(s)
2892 }
2893 tok => {
2894 return Err(self.syntax_err(
2895 format!("tie expects $scalar, @array, or %hash, got {:?}", tok),
2896 self.peek_line(),
2897 ));
2898 }
2899 };
2900 self.expect(&Token::Comma)?;
2901 let class = self.parse_assign_expr()?;
2902 let mut args = Vec::new();
2903 while self.eat(&Token::Comma) {
2904 if matches!(self.peek(), Token::Semicolon | Token::RBrace | Token::Eof) {
2905 break;
2906 }
2907 args.push(self.parse_assign_expr()?);
2908 }
2909 self.eat(&Token::Semicolon);
2910 Ok(Statement {
2911 label: None,
2912 kind: StmtKind::Tie {
2913 target,
2914 class,
2915 args,
2916 },
2917 line,
2918 })
2919 }
2920
2921 fn parse_given(&mut self) -> PerlResult<Statement> {
2923 let line = self.peek_line();
2924 self.advance();
2925 self.expect(&Token::LParen)?;
2926 let topic = self.parse_expression()?;
2927 self.expect(&Token::RParen)?;
2928 let body = self.parse_block()?;
2929 self.eat(&Token::Semicolon);
2930 Ok(Statement {
2931 label: None,
2932 kind: StmtKind::Given { topic, body },
2933 line,
2934 })
2935 }
2936
2937 fn parse_when_stmt(&mut self) -> PerlResult<Statement> {
2939 let line = self.peek_line();
2940 self.advance();
2941 self.expect(&Token::LParen)?;
2942 let cond = self.parse_expression()?;
2943 self.expect(&Token::RParen)?;
2944 let body = self.parse_block()?;
2945 self.eat(&Token::Semicolon);
2946 Ok(Statement {
2947 label: None,
2948 kind: StmtKind::When { cond, body },
2949 line,
2950 })
2951 }
2952
2953 fn parse_default_stmt(&mut self) -> PerlResult<Statement> {
2955 let line = self.peek_line();
2956 self.advance();
2957 let body = self.parse_block()?;
2958 self.eat(&Token::Semicolon);
2959 Ok(Statement {
2960 label: None,
2961 kind: StmtKind::DefaultCase { body },
2962 line,
2963 })
2964 }
2965
2966 fn parse_cond_expr(&mut self, line: usize) -> PerlResult<Expr> {
2972 self.expect(&Token::LBrace)?;
2973
2974 let mut arms: Vec<(Expr, Block)> = Vec::new();
2975 let mut else_block: Option<Block> = None;
2976
2977 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
2978 let arm_line = self.peek_line();
2979
2980 let is_default = matches!(self.peek(), Token::Ident(ref s) if s == "default")
2982 && matches!(self.peek_at(1), Token::FatArrow);
2983
2984 if is_default {
2985 self.advance(); self.advance(); let body = if matches!(self.peek(), Token::LBrace) {
2988 self.parse_block()?
2989 } else {
2990 let expr = self.parse_assign_expr()?;
2991 vec![Statement {
2992 label: None,
2993 kind: StmtKind::Expression(expr),
2994 line: arm_line,
2995 }]
2996 };
2997 else_block = Some(body);
2998 self.eat(&Token::Comma);
2999 break; }
3001
3002 let condition = self.parse_assign_expr()?;
3004 self.expect(&Token::FatArrow)?;
3005
3006 let body = if matches!(self.peek(), Token::LBrace) {
3007 self.parse_block()?
3008 } else {
3009 let expr = self.parse_assign_expr()?;
3010 vec![Statement {
3011 label: None,
3012 kind: StmtKind::Expression(expr),
3013 line: arm_line,
3014 }]
3015 };
3016
3017 arms.push((condition, body));
3018 self.eat(&Token::Comma);
3019 }
3020
3021 self.expect(&Token::RBrace)?;
3022
3023 if arms.is_empty() {
3024 return Err(self.syntax_err("cond requires at least one condition arm", line));
3025 }
3026
3027 let (first_cond, first_body) = arms.remove(0);
3029 let elsifs: Vec<(Expr, Block)> = arms;
3030
3031 let if_stmt = Statement {
3033 label: None,
3034 kind: StmtKind::If {
3035 condition: first_cond,
3036 body: first_body,
3037 elsifs,
3038 else_block,
3039 },
3040 line,
3041 };
3042 let inner = Expr {
3043 kind: ExprKind::CodeRef {
3044 params: vec![],
3045 body: vec![if_stmt],
3046 },
3047 line,
3048 };
3049 Ok(Expr {
3050 kind: ExprKind::Do(Box::new(inner)),
3051 line,
3052 })
3053 }
3054
3055 fn parse_algebraic_match_expr(&mut self, line: usize) -> PerlResult<Expr> {
3057 self.expect(&Token::LParen)?;
3058 let subject = self.parse_expression()?;
3059 self.expect(&Token::RParen)?;
3060 self.expect(&Token::LBrace)?;
3061 let mut arms = Vec::new();
3062 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
3063 if self.eat(&Token::Semicolon) {
3064 continue;
3065 }
3066 let pattern = self.parse_match_pattern()?;
3067 let guard = if matches!(self.peek(), Token::Ident(ref s) if s == "if") {
3068 self.advance();
3069 Some(Box::new(self.parse_assign_expr()?))
3072 } else {
3073 None
3074 };
3075 self.expect(&Token::FatArrow)?;
3076 let body = self.parse_assign_expr()?;
3078 arms.push(MatchArm {
3079 pattern,
3080 guard,
3081 body,
3082 });
3083 self.eat(&Token::Comma);
3084 }
3085 self.expect(&Token::RBrace)?;
3086 Ok(Expr {
3087 kind: ExprKind::AlgebraicMatch {
3088 subject: Box::new(subject),
3089 arms,
3090 },
3091 line,
3092 })
3093 }
3094
3095 fn parse_match_pattern(&mut self) -> PerlResult<MatchPattern> {
3096 match self.peek().clone() {
3097 Token::Regex(pattern, flags, _delim) => {
3098 self.advance();
3099 Ok(MatchPattern::Regex { pattern, flags })
3100 }
3101 Token::Ident(ref s) if s == "_" => {
3102 self.advance();
3103 Ok(MatchPattern::Any)
3104 }
3105 Token::Ident(ref s) if s == "Some" => {
3106 self.advance();
3107 self.expect(&Token::LParen)?;
3108 let name = self.parse_scalar_var_name()?;
3109 self.expect(&Token::RParen)?;
3110 Ok(MatchPattern::OptionSome(name))
3111 }
3112 Token::LBracket => self.parse_match_array_pattern(),
3113 Token::LBrace => self.parse_match_hash_pattern(),
3114 Token::LParen => {
3115 self.advance();
3116 let e = self.parse_expression()?;
3117 self.expect(&Token::RParen)?;
3118 Ok(MatchPattern::Value(Box::new(e)))
3119 }
3120 _ => {
3121 let e = self.parse_assign_expr()?;
3122 Ok(MatchPattern::Value(Box::new(e)))
3123 }
3124 }
3125 }
3126
3127 fn parse_match_array_elems_until_rbracket(&mut self) -> PerlResult<Vec<MatchArrayElem>> {
3129 let mut elems = Vec::new();
3130 if self.eat(&Token::RBracket) {
3131 return Ok(vec![]);
3132 }
3133 loop {
3134 if matches!(self.peek(), Token::Star) {
3135 self.advance();
3136 elems.push(MatchArrayElem::Rest);
3137 self.eat(&Token::Comma);
3138 if !matches!(self.peek(), Token::RBracket) {
3139 return Err(self.syntax_err(
3140 "`*` must be the last element in an array match pattern",
3141 self.peek_line(),
3142 ));
3143 }
3144 self.expect(&Token::RBracket)?;
3145 return Ok(elems);
3146 }
3147 if let Token::ArrayVar(name) = self.peek().clone() {
3148 self.advance();
3149 elems.push(MatchArrayElem::RestBind(name));
3150 self.eat(&Token::Comma);
3151 if !matches!(self.peek(), Token::RBracket) {
3152 return Err(self.syntax_err(
3153 "`@name` rest bind must be the last element in an array match pattern",
3154 self.peek_line(),
3155 ));
3156 }
3157 self.expect(&Token::RBracket)?;
3158 return Ok(elems);
3159 }
3160 if let Token::ScalarVar(name) = self.peek().clone() {
3161 self.advance();
3162 elems.push(MatchArrayElem::CaptureScalar(name));
3163 if self.eat(&Token::Comma) {
3164 if matches!(self.peek(), Token::RBracket) {
3165 break;
3166 }
3167 continue;
3168 }
3169 break;
3170 }
3171 let e = self.parse_assign_expr()?;
3172 elems.push(MatchArrayElem::Expr(e));
3173 if self.eat(&Token::Comma) {
3174 if matches!(self.peek(), Token::RBracket) {
3175 break;
3176 }
3177 continue;
3178 }
3179 break;
3180 }
3181 self.expect(&Token::RBracket)?;
3182 Ok(elems)
3183 }
3184
3185 fn parse_match_array_pattern(&mut self) -> PerlResult<MatchPattern> {
3186 self.expect(&Token::LBracket)?;
3187 let elems = self.parse_match_array_elems_until_rbracket()?;
3188 Ok(MatchPattern::Array(elems))
3189 }
3190
3191 fn parse_match_hash_pattern(&mut self) -> PerlResult<MatchPattern> {
3192 self.expect(&Token::LBrace)?;
3193 let mut pairs = Vec::new();
3194 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
3195 if self.eat(&Token::Semicolon) {
3196 continue;
3197 }
3198 let key = self.parse_assign_expr()?;
3199 self.expect(&Token::FatArrow)?;
3200 match self.advance().0 {
3201 Token::Ident(ref s) if s == "_" => {
3202 pairs.push(MatchHashPair::KeyOnly { key });
3203 }
3204 Token::ScalarVar(name) => {
3205 pairs.push(MatchHashPair::Capture { key, name });
3206 }
3207 tok => {
3208 return Err(self.syntax_err(
3209 format!(
3210 "hash match pattern must bind with `=> $name` or `=> _`, got {:?}",
3211 tok
3212 ),
3213 self.peek_line(),
3214 ));
3215 }
3216 }
3217 self.eat(&Token::Comma);
3218 }
3219 self.expect(&Token::RBrace)?;
3220 Ok(MatchPattern::Hash(pairs))
3221 }
3222
3223 fn parse_eval_timeout(&mut self) -> PerlResult<Statement> {
3225 let line = self.peek_line();
3226 self.advance();
3227 let timeout = self.parse_postfix()?;
3228 let body = self.parse_block_or_bareword_block_no_args()?;
3229 self.eat(&Token::Semicolon);
3230 Ok(Statement {
3231 label: None,
3232 kind: StmtKind::EvalTimeout { timeout, body },
3233 line,
3234 })
3235 }
3236
3237 fn mark_match_scalar_g_for_boolean_condition(cond: &mut Expr) {
3238 match &mut cond.kind {
3239 ExprKind::Match {
3240 flags, scalar_g, ..
3241 } if flags.contains('g') => {
3242 *scalar_g = true;
3243 }
3244 ExprKind::UnaryOp {
3245 op: UnaryOp::LogNot,
3246 expr,
3247 } => {
3248 if let ExprKind::Match {
3249 flags, scalar_g, ..
3250 } = &mut expr.kind
3251 {
3252 if flags.contains('g') {
3253 *scalar_g = true;
3254 }
3255 }
3256 }
3257 _ => {}
3258 }
3259 }
3260
3261 fn parse_if(&mut self) -> PerlResult<Statement> {
3262 let line = self.peek_line();
3263 self.advance(); if matches!(self.peek(), Token::Ident(ref s) if s == "let") {
3265 if crate::compat_mode() {
3266 return Err(self.syntax_err(
3267 "`if let` is a stryke extension (disabled by --compat)",
3268 line,
3269 ));
3270 }
3271 return self.parse_if_let(line);
3272 }
3273 self.expect(&Token::LParen)?;
3274 let mut cond = self.parse_expression()?;
3275 Self::mark_match_scalar_g_for_boolean_condition(&mut cond);
3276 self.expect(&Token::RParen)?;
3277 let body = self.parse_block()?;
3278
3279 let mut elsifs = Vec::new();
3280 let mut else_block = None;
3281
3282 loop {
3283 if let Token::Ident(ref kw) = self.peek().clone() {
3284 if kw == "elsif" {
3285 self.advance();
3286 self.expect(&Token::LParen)?;
3287 let mut c = self.parse_expression()?;
3288 Self::mark_match_scalar_g_for_boolean_condition(&mut c);
3289 self.expect(&Token::RParen)?;
3290 let b = self.parse_block()?;
3291 elsifs.push((c, b));
3292 continue;
3293 }
3294 if kw == "else" {
3295 self.advance();
3296 else_block = Some(self.parse_block()?);
3297 }
3298 }
3299 break;
3300 }
3301
3302 Ok(Statement {
3303 label: None,
3304 kind: StmtKind::If {
3305 condition: cond,
3306 body,
3307 elsifs,
3308 else_block,
3309 },
3310 line,
3311 })
3312 }
3313
3314 fn parse_if_let(&mut self, line: usize) -> PerlResult<Statement> {
3316 self.advance(); let pattern = self.parse_match_pattern()?;
3318 self.expect(&Token::Assign)?;
3319 self.suppress_scalar_hash_brace = self.suppress_scalar_hash_brace.saturating_add(1);
3321 let rhs = self.parse_assign_expr();
3322 self.suppress_scalar_hash_brace = self.suppress_scalar_hash_brace.saturating_sub(1);
3323 let rhs = rhs?;
3324 let then_block = self.parse_block()?;
3325 let else_block_opt = match self.peek().clone() {
3326 Token::Ident(ref kw) if kw == "else" => {
3327 self.advance();
3328 Some(self.parse_block()?)
3329 }
3330 Token::Ident(ref kw) if kw == "elsif" => {
3331 return Err(self.syntax_err(
3332 "`if let` does not support `elsif`; use `else { }` or a full `match`",
3333 self.peek_line(),
3334 ));
3335 }
3336 _ => None,
3337 };
3338 let then_expr = Self::expr_do_anon_block(then_block, line);
3339 let else_expr = if let Some(eb) = else_block_opt {
3340 Self::expr_do_anon_block(eb, line)
3341 } else {
3342 Expr {
3343 kind: ExprKind::Undef,
3344 line,
3345 }
3346 };
3347 let arms = vec![
3348 MatchArm {
3349 pattern,
3350 guard: None,
3351 body: then_expr,
3352 },
3353 MatchArm {
3354 pattern: MatchPattern::Any,
3355 guard: None,
3356 body: else_expr,
3357 },
3358 ];
3359 Ok(Statement {
3360 label: None,
3361 kind: StmtKind::Expression(Expr {
3362 kind: ExprKind::AlgebraicMatch {
3363 subject: Box::new(rhs),
3364 arms,
3365 },
3366 line,
3367 }),
3368 line,
3369 })
3370 }
3371
3372 fn expr_do_anon_block(block: Block, outer_line: usize) -> Expr {
3373 let inner_line = block.first().map(|s| s.line).unwrap_or(outer_line);
3374 Expr {
3375 kind: ExprKind::Do(Box::new(Expr {
3376 kind: ExprKind::CodeRef {
3377 params: vec![],
3378 body: block,
3379 },
3380 line: inner_line,
3381 })),
3382 line: outer_line,
3383 }
3384 }
3385
3386 fn parse_unless(&mut self) -> PerlResult<Statement> {
3387 let line = self.peek_line();
3388 self.advance(); self.expect(&Token::LParen)?;
3390 let mut cond = self.parse_expression()?;
3391 Self::mark_match_scalar_g_for_boolean_condition(&mut cond);
3392 self.expect(&Token::RParen)?;
3393 let body = self.parse_block()?;
3394 let else_block = if let Token::Ident(ref kw) = self.peek().clone() {
3395 if kw == "else" {
3396 self.advance();
3397 Some(self.parse_block()?)
3398 } else {
3399 None
3400 }
3401 } else {
3402 None
3403 };
3404 Ok(Statement {
3405 label: None,
3406 kind: StmtKind::Unless {
3407 condition: cond,
3408 body,
3409 else_block,
3410 },
3411 line,
3412 })
3413 }
3414
3415 fn parse_while(&mut self) -> PerlResult<Statement> {
3416 let line = self.peek_line();
3417 self.advance(); if matches!(self.peek(), Token::Ident(ref s) if s == "let") {
3419 if crate::compat_mode() {
3420 return Err(self.syntax_err(
3421 "`while let` is a stryke extension (disabled by --compat)",
3422 line,
3423 ));
3424 }
3425 return self.parse_while_let(line);
3426 }
3427 self.expect(&Token::LParen)?;
3428 let mut cond = self.parse_expression()?;
3429 Self::mark_match_scalar_g_for_boolean_condition(&mut cond);
3430 self.expect(&Token::RParen)?;
3431 let body = self.parse_block()?;
3432 let continue_block = self.parse_optional_continue_block()?;
3433 Ok(Statement {
3434 label: None,
3435 kind: StmtKind::While {
3436 condition: cond,
3437 body,
3438 label: None,
3439 continue_block,
3440 },
3441 line,
3442 })
3443 }
3444
3445 fn parse_while_let(&mut self, line: usize) -> PerlResult<Statement> {
3448 self.advance(); let pattern = self.parse_match_pattern()?;
3450 self.expect(&Token::Assign)?;
3451 self.suppress_scalar_hash_brace = self.suppress_scalar_hash_brace.saturating_add(1);
3452 let rhs = self.parse_assign_expr();
3453 self.suppress_scalar_hash_brace = self.suppress_scalar_hash_brace.saturating_sub(1);
3454 let rhs = rhs?;
3455 let mut user_body = self.parse_block()?;
3456 let continue_block = self.parse_optional_continue_block()?;
3457 user_body.push(Statement::new(
3458 StmtKind::Expression(Expr {
3459 kind: ExprKind::Integer(1),
3460 line,
3461 }),
3462 line,
3463 ));
3464 let tmp = format!("__while_let_{}", self.alloc_desugar_tmp());
3465 let match_expr = Expr {
3466 kind: ExprKind::AlgebraicMatch {
3467 subject: Box::new(rhs),
3468 arms: vec![
3469 MatchArm {
3470 pattern,
3471 guard: None,
3472 body: Self::expr_do_anon_block(user_body, line),
3473 },
3474 MatchArm {
3475 pattern: MatchPattern::Any,
3476 guard: None,
3477 body: Expr {
3478 kind: ExprKind::Integer(0),
3479 line,
3480 },
3481 },
3482 ],
3483 },
3484 line,
3485 };
3486 let my_stmt = Statement::new(
3487 StmtKind::My(vec![VarDecl {
3488 sigil: Sigil::Scalar,
3489 name: tmp.clone(),
3490 initializer: Some(match_expr),
3491 frozen: false,
3492 type_annotation: None,
3493 }]),
3494 line,
3495 );
3496 let unless_last = Statement::new(
3497 StmtKind::Unless {
3498 condition: Expr {
3499 kind: ExprKind::ScalarVar(tmp),
3500 line,
3501 },
3502 body: vec![Statement::new(StmtKind::Last(None), line)],
3503 else_block: None,
3504 },
3505 line,
3506 );
3507 Ok(Statement::new(
3508 StmtKind::While {
3509 condition: Expr {
3510 kind: ExprKind::Integer(1),
3511 line,
3512 },
3513 body: vec![my_stmt, unless_last],
3514 label: None,
3515 continue_block,
3516 },
3517 line,
3518 ))
3519 }
3520
3521 fn parse_until(&mut self) -> PerlResult<Statement> {
3522 let line = self.peek_line();
3523 self.advance(); self.expect(&Token::LParen)?;
3525 let mut cond = self.parse_expression()?;
3526 Self::mark_match_scalar_g_for_boolean_condition(&mut cond);
3527 self.expect(&Token::RParen)?;
3528 let body = self.parse_block()?;
3529 let continue_block = self.parse_optional_continue_block()?;
3530 Ok(Statement {
3531 label: None,
3532 kind: StmtKind::Until {
3533 condition: cond,
3534 body,
3535 label: None,
3536 continue_block,
3537 },
3538 line,
3539 })
3540 }
3541
3542 fn parse_optional_continue_block(&mut self) -> PerlResult<Option<Block>> {
3544 if let Token::Ident(ref kw) = self.peek().clone() {
3545 if kw == "continue" {
3546 self.advance();
3547 return Ok(Some(self.parse_block()?));
3548 }
3549 }
3550 Ok(None)
3551 }
3552
3553 fn parse_for_or_foreach(&mut self) -> PerlResult<Statement> {
3554 let line = self.peek_line();
3555 self.advance(); match self.peek() {
3561 Token::LParen => {
3562 let saved = self.pos;
3567 self.advance(); let mut depth = 1;
3570 let mut has_semi = false;
3571 let mut scan = self.pos;
3572 while scan < self.tokens.len() {
3573 match &self.tokens[scan].0 {
3574 Token::LParen => depth += 1,
3575 Token::RParen => {
3576 depth -= 1;
3577 if depth == 0 {
3578 break;
3579 }
3580 }
3581 Token::Semicolon if depth == 1 => {
3582 has_semi = true;
3583 break;
3584 }
3585 _ => {}
3586 }
3587 scan += 1;
3588 }
3589 self.pos = saved;
3590
3591 if has_semi {
3592 self.parse_c_style_for(line)
3593 } else {
3594 self.expect(&Token::LParen)?;
3596 let list = self.parse_expression()?;
3597 self.expect(&Token::RParen)?;
3598 let body = self.parse_block()?;
3599 let continue_block = self.parse_optional_continue_block()?;
3600 Ok(Statement {
3601 label: None,
3602 kind: StmtKind::Foreach {
3603 var: "_".to_string(),
3604 list,
3605 body,
3606 label: None,
3607 continue_block,
3608 },
3609 line,
3610 })
3611 }
3612 }
3613 Token::Ident(ref kw) if kw == "my" => {
3614 self.advance(); let var = self.parse_scalar_var_name()?;
3616 self.expect(&Token::LParen)?;
3617 let list = self.parse_expression()?;
3618 self.expect(&Token::RParen)?;
3619 let body = self.parse_block()?;
3620 let continue_block = self.parse_optional_continue_block()?;
3621 Ok(Statement {
3622 label: None,
3623 kind: StmtKind::Foreach {
3624 var,
3625 list,
3626 body,
3627 label: None,
3628 continue_block,
3629 },
3630 line,
3631 })
3632 }
3633 Token::ScalarVar(_) => {
3634 let var = self.parse_scalar_var_name()?;
3635 self.expect(&Token::LParen)?;
3636 let list = self.parse_expression()?;
3637 self.expect(&Token::RParen)?;
3638 let body = self.parse_block()?;
3639 let continue_block = self.parse_optional_continue_block()?;
3640 Ok(Statement {
3641 label: None,
3642 kind: StmtKind::Foreach {
3643 var,
3644 list,
3645 body,
3646 label: None,
3647 continue_block,
3648 },
3649 line,
3650 })
3651 }
3652 _ => self.parse_c_style_for(line),
3653 }
3654 }
3655
3656 fn parse_c_style_for(&mut self, line: usize) -> PerlResult<Statement> {
3657 self.expect(&Token::LParen)?;
3658 let init = if self.eat(&Token::Semicolon) {
3659 None
3660 } else {
3661 let s = self.parse_statement()?;
3662 self.eat(&Token::Semicolon);
3663 Some(Box::new(s))
3664 };
3665 let mut condition = if matches!(self.peek(), Token::Semicolon) {
3666 None
3667 } else {
3668 Some(self.parse_expression()?)
3669 };
3670 if let Some(ref mut c) = condition {
3671 Self::mark_match_scalar_g_for_boolean_condition(c);
3672 }
3673 self.expect(&Token::Semicolon)?;
3674 let step = if matches!(self.peek(), Token::RParen) {
3675 None
3676 } else {
3677 Some(self.parse_expression()?)
3678 };
3679 self.expect(&Token::RParen)?;
3680 let body = self.parse_block()?;
3681 let continue_block = self.parse_optional_continue_block()?;
3682 Ok(Statement {
3683 label: None,
3684 kind: StmtKind::For {
3685 init,
3686 condition,
3687 step,
3688 body,
3689 label: None,
3690 continue_block,
3691 },
3692 line,
3693 })
3694 }
3695
3696 fn parse_foreach(&mut self) -> PerlResult<Statement> {
3697 let line = self.peek_line();
3698 self.advance(); let var = match self.peek() {
3700 Token::Ident(ref kw) if kw == "my" => {
3701 self.advance();
3702 self.parse_scalar_var_name()?
3703 }
3704 Token::ScalarVar(_) => self.parse_scalar_var_name()?,
3705 _ => "_".to_string(),
3706 };
3707 self.expect(&Token::LParen)?;
3708 let list = self.parse_expression()?;
3709 self.expect(&Token::RParen)?;
3710 let body = self.parse_block()?;
3711 let continue_block = self.parse_optional_continue_block()?;
3712 Ok(Statement {
3713 label: None,
3714 kind: StmtKind::Foreach {
3715 var,
3716 list,
3717 body,
3718 label: None,
3719 continue_block,
3720 },
3721 line,
3722 })
3723 }
3724
3725 fn parse_scalar_var_name(&mut self) -> PerlResult<String> {
3726 match self.advance() {
3727 (Token::ScalarVar(name), _) => Ok(name),
3728 (tok, line) => {
3729 Err(self.syntax_err(format!("Expected scalar variable, got {:?}", tok), line))
3730 }
3731 }
3732 }
3733
3734 fn parse_legacy_sub_prototype_tail(&mut self) -> PerlResult<String> {
3736 let mut s = String::new();
3737 loop {
3738 match self.peek().clone() {
3739 Token::RParen => {
3740 self.advance();
3741 break;
3742 }
3743 Token::Eof => {
3744 return Err(self.syntax_err(
3745 "Unterminated sub prototype (expected ')' before end of input)",
3746 self.peek_line(),
3747 ));
3748 }
3749 Token::ScalarVar(v) if v == ")" => {
3750 self.advance();
3753 s.push('$');
3754 if matches!(self.peek(), Token::LBrace) {
3755 break;
3756 }
3757 }
3758 Token::Ident(i) => {
3759 let i = i.clone();
3760 self.advance();
3761 s.push_str(&i);
3762 }
3763 Token::Semicolon => {
3764 self.advance();
3765 s.push(';');
3766 }
3767 Token::LParen => {
3768 self.advance();
3769 s.push('(');
3770 }
3771 Token::LBracket => {
3772 self.advance();
3773 s.push('[');
3774 }
3775 Token::RBracket => {
3776 self.advance();
3777 s.push(']');
3778 }
3779 Token::Backslash => {
3780 self.advance();
3781 s.push('\\');
3782 }
3783 Token::Comma => {
3784 self.advance();
3785 s.push(',');
3786 }
3787 Token::ScalarVar(v) => {
3788 let v = v.clone();
3789 self.advance();
3790 s.push('$');
3791 s.push_str(&v);
3792 }
3793 Token::ArrayVar(v) => {
3794 let v = v.clone();
3795 self.advance();
3796 s.push('@');
3797 s.push_str(&v);
3798 }
3799 Token::ArrayAt => {
3801 self.advance();
3802 s.push('@');
3803 }
3804 Token::HashVar(v) => {
3805 let v = v.clone();
3806 self.advance();
3807 s.push('%');
3808 s.push_str(&v);
3809 }
3810 Token::HashPercent => {
3811 self.advance();
3812 s.push('%');
3813 }
3814 Token::Plus => {
3815 self.advance();
3816 s.push('+');
3817 }
3818 Token::Minus => {
3819 self.advance();
3820 s.push('-');
3821 }
3822 Token::BitAnd => {
3823 self.advance();
3824 s.push('&');
3825 }
3826 tok => {
3827 return Err(self.syntax_err(
3828 format!("Unexpected token in sub prototype: {:?}", tok),
3829 self.peek_line(),
3830 ));
3831 }
3832 }
3833 }
3834 Ok(s)
3835 }
3836
3837 fn sub_signature_list_starts_here(&self) -> bool {
3838 match self.peek() {
3839 Token::LBrace | Token::LBracket => true,
3840 Token::ScalarVar(name) if name != "$$" && name != ")" => true,
3841 Token::ArrayVar(_) | Token::HashVar(_) => true,
3842 _ => false,
3843 }
3844 }
3845
3846 fn parse_sub_signature_hash_key(&mut self) -> PerlResult<String> {
3847 let (tok, line) = self.advance();
3848 match tok {
3849 Token::Ident(i) => Ok(i),
3850 Token::SingleString(s) | Token::DoubleString(s) => Ok(s),
3851 tok => Err(self.syntax_err(
3852 format!(
3853 "sub signature: expected hash key (identifier or string), got {:?}",
3854 tok
3855 ),
3856 line,
3857 )),
3858 }
3859 }
3860
3861 fn parse_sub_signature_param_list(&mut self) -> PerlResult<Vec<SubSigParam>> {
3862 let mut params = Vec::new();
3863 loop {
3864 if matches!(self.peek(), Token::RParen) {
3865 break;
3866 }
3867 match self.peek().clone() {
3868 Token::ScalarVar(name) => {
3869 if name == "$$" || name == ")" {
3870 return Err(self.syntax_err(
3871 format!(
3872 "`{name}` cannot start a stryke sub signature (use legacy prototype `($$)` etc.)"
3873 ),
3874 self.peek_line(),
3875 ));
3876 }
3877 self.advance();
3878 let ty = if self.eat(&Token::Colon) {
3879 match self.peek() {
3880 Token::Ident(ref tname) => {
3881 let tname = tname.clone();
3882 self.advance();
3883 Some(match tname.as_str() {
3884 "Int" => PerlTypeName::Int,
3885 "Str" => PerlTypeName::Str,
3886 "Float" => PerlTypeName::Float,
3887 "Bool" => PerlTypeName::Bool,
3888 "Array" => PerlTypeName::Array,
3889 "Hash" => PerlTypeName::Hash,
3890 "Ref" => PerlTypeName::Ref,
3891 "Any" => PerlTypeName::Any,
3892 _ => PerlTypeName::Struct(tname),
3893 })
3894 }
3895 _ => {
3896 return Err(self.syntax_err(
3897 "expected type name after `:` in sub signature",
3898 self.peek_line(),
3899 ));
3900 }
3901 }
3902 } else {
3903 None
3904 };
3905 let default = if self.eat(&Token::Assign) {
3907 Some(Box::new(self.parse_ternary()?))
3908 } else {
3909 None
3910 };
3911 params.push(SubSigParam::Scalar(name, ty, default));
3912 }
3913 Token::ArrayVar(name) => {
3914 self.advance();
3915 let default = if self.eat(&Token::Assign) {
3916 Some(Box::new(self.parse_ternary()?))
3917 } else {
3918 None
3919 };
3920 params.push(SubSigParam::Array(name, default));
3921 }
3922 Token::HashVar(name) => {
3923 self.advance();
3924 let default = if self.eat(&Token::Assign) {
3925 Some(Box::new(self.parse_ternary()?))
3926 } else {
3927 None
3928 };
3929 params.push(SubSigParam::Hash(name, default));
3930 }
3931 Token::LBracket => {
3932 self.advance();
3933 let elems = self.parse_match_array_elems_until_rbracket()?;
3934 params.push(SubSigParam::ArrayDestruct(elems));
3935 }
3936 Token::LBrace => {
3937 self.advance();
3938 let mut pairs = Vec::new();
3939 loop {
3940 if matches!(self.peek(), Token::RBrace | Token::Eof) {
3941 break;
3942 }
3943 if self.eat(&Token::Comma) {
3944 continue;
3945 }
3946 let key = self.parse_sub_signature_hash_key()?;
3947 self.expect(&Token::FatArrow)?;
3948 let bind = self.parse_scalar_var_name()?;
3949 pairs.push((key, bind));
3950 self.eat(&Token::Comma);
3951 }
3952 self.expect(&Token::RBrace)?;
3953 params.push(SubSigParam::HashDestruct(pairs));
3954 }
3955 tok => {
3956 return Err(self.syntax_err(
3957 format!(
3958 "expected `$name`, `[ ... ]`, or `{{ ... }}` in sub signature, got {:?}",
3959 tok
3960 ),
3961 self.peek_line(),
3962 ));
3963 }
3964 }
3965 match self.peek() {
3966 Token::Comma => {
3967 self.advance();
3968 if matches!(self.peek(), Token::RParen) {
3969 return Err(self.syntax_err(
3970 "trailing `,` before `)` in sub signature",
3971 self.peek_line(),
3972 ));
3973 }
3974 }
3975 Token::RParen => break,
3976 _ => {
3977 return Err(self.syntax_err(
3978 format!(
3979 "expected `,` or `)` after sub signature parameter, got {:?}",
3980 self.peek()
3981 ),
3982 self.peek_line(),
3983 ));
3984 }
3985 }
3986 }
3987 Ok(params)
3988 }
3989
3990 fn parse_sub_sig_or_prototype_opt(&mut self) -> PerlResult<(Vec<SubSigParam>, Option<String>)> {
3992 if !matches!(self.peek(), Token::LParen) {
3993 return Ok((vec![], None));
3994 }
3995 self.advance();
3996 if matches!(self.peek(), Token::RParen) {
3997 self.advance();
3998 return Ok((vec![], Some(String::new())));
3999 }
4000 if self.sub_signature_list_starts_here() {
4001 let params = self.parse_sub_signature_param_list()?;
4002 self.expect(&Token::RParen)?;
4003 return Ok((params, None));
4004 }
4005 let proto = self.parse_legacy_sub_prototype_tail()?;
4006 Ok((vec![], Some(proto)))
4007 }
4008
4009 fn parse_sub_attributes(&mut self) -> PerlResult<()> {
4011 while self.eat(&Token::Colon) {
4012 match self.advance() {
4013 (Token::Ident(_), _) => {}
4014 (tok, line) => {
4015 return Err(self.syntax_err(
4016 format!("Expected attribute name after `:`, got {:?}", tok),
4017 line,
4018 ));
4019 }
4020 }
4021 if self.eat(&Token::LParen) {
4022 let mut depth = 1usize;
4023 while depth > 0 {
4024 match self.advance().0 {
4025 Token::LParen => depth += 1,
4026 Token::RParen => {
4027 depth -= 1;
4028 }
4029 Token::Eof => {
4030 return Err(self.syntax_err(
4031 "Unterminated sub attribute argument list",
4032 self.peek_line(),
4033 ));
4034 }
4035 _ => {}
4036 }
4037 }
4038 }
4039 }
4040 Ok(())
4041 }
4042
4043 fn parse_sub_decl(&mut self, is_sub_keyword: bool) -> PerlResult<Statement> {
4044 let line = self.peek_line();
4045 self.advance(); match self.peek().clone() {
4047 Token::Ident(_) => {
4048 let name = self.parse_package_qualified_identifier()?;
4049 if !crate::compat_mode() {
4050 self.check_udf_shadows_builtin(&name, line)?;
4051 }
4052 self.declared_subs.insert(name.clone());
4053 let (params, prototype) = self.parse_sub_sig_or_prototype_opt()?;
4054 self.parse_sub_attributes()?;
4055 let body = self.parse_block()?;
4056 Ok(Statement {
4057 label: None,
4058 kind: StmtKind::SubDecl {
4059 name,
4060 params,
4061 body,
4062 prototype,
4063 },
4064 line,
4065 })
4066 }
4067 Token::LParen | Token::LBrace | Token::Colon => {
4068 if is_sub_keyword && !crate::compat_mode() {
4070 return Err(self.syntax_err(
4071 "stryke uses `fn {}` instead of `fn {}` (this is not Perl 5)",
4072 line,
4073 ));
4074 }
4075 let (params, _prototype) = self.parse_sub_sig_or_prototype_opt()?;
4077 self.parse_sub_attributes()?;
4078 let body = self.parse_block()?;
4079 Ok(Statement {
4080 label: None,
4081 kind: StmtKind::Expression(Expr {
4082 kind: ExprKind::CodeRef { params, body },
4083 line,
4084 }),
4085 line,
4086 })
4087 }
4088 tok => Err(self.syntax_err(
4089 format!("Expected sub name, `(`, `{{`, or `:`, got {:?}", tok),
4090 self.peek_line(),
4091 )),
4092 }
4093 }
4094
4095 fn parse_struct_decl(&mut self) -> PerlResult<Statement> {
4097 let line = self.peek_line();
4098 self.advance(); let name = match self.advance() {
4100 (Token::Ident(n), _) => n,
4101 (tok, err_line) => {
4102 return Err(
4103 self.syntax_err(format!("Expected struct name, got {:?}", tok), err_line)
4104 )
4105 }
4106 };
4107 self.expect(&Token::LBrace)?;
4108 let mut fields = Vec::new();
4109 let mut methods = Vec::new();
4110 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
4111 let is_method = match self.peek() {
4113 Token::Ident(s) => s == "fn" || s == "sub",
4114 _ => false,
4115 };
4116 if is_method {
4117 self.advance(); let method_name = match self.advance() {
4119 (Token::Ident(n), _) => n,
4120 (tok, err_line) => {
4121 return Err(self
4122 .syntax_err(format!("Expected method name, got {:?}", tok), err_line))
4123 }
4124 };
4125 let params = if self.eat(&Token::LParen) {
4127 let p = self.parse_sub_signature_param_list()?;
4128 self.expect(&Token::RParen)?;
4129 p
4130 } else {
4131 Vec::new()
4132 };
4133 let body = self.parse_block()?;
4135 methods.push(crate::ast::StructMethod {
4136 name: method_name,
4137 params,
4138 body,
4139 });
4140 self.eat(&Token::Comma);
4142 self.eat(&Token::Semicolon);
4143 continue;
4144 }
4145
4146 let field_name = match self.advance() {
4147 (Token::Ident(n), _) => n,
4148 (tok, err_line) => {
4149 return Err(
4150 self.syntax_err(format!("Expected field name, got {:?}", tok), err_line)
4151 )
4152 }
4153 };
4154 let ty = if self.eat(&Token::FatArrow) {
4156 self.parse_type_name()?
4157 } else {
4158 crate::ast::PerlTypeName::Any
4159 };
4160 let default = if self.eat(&Token::Assign) {
4161 Some(self.parse_ternary()?)
4163 } else {
4164 None
4165 };
4166 fields.push(StructField {
4167 name: field_name,
4168 ty,
4169 default,
4170 });
4171 if !self.eat(&Token::Comma) {
4172 self.eat(&Token::Semicolon);
4174 }
4175 }
4176 self.expect(&Token::RBrace)?;
4177 self.eat(&Token::Semicolon);
4178 Ok(Statement {
4179 label: None,
4180 kind: StmtKind::StructDecl {
4181 def: StructDef {
4182 name,
4183 fields,
4184 methods,
4185 },
4186 },
4187 line,
4188 })
4189 }
4190
4191 fn parse_enum_decl(&mut self) -> PerlResult<Statement> {
4193 let line = self.peek_line();
4194 self.advance(); let name = match self.advance() {
4196 (Token::Ident(n), _) => n,
4197 (tok, err_line) => {
4198 return Err(self.syntax_err(format!("Expected enum name, got {:?}", tok), err_line))
4199 }
4200 };
4201 self.expect(&Token::LBrace)?;
4202 let mut variants = Vec::new();
4203 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
4204 let variant_name = match self.advance() {
4205 (Token::Ident(n), _) => n,
4206 (tok, err_line) => {
4207 return Err(
4208 self.syntax_err(format!("Expected variant name, got {:?}", tok), err_line)
4209 )
4210 }
4211 };
4212 let ty = if self.eat(&Token::FatArrow) {
4213 Some(self.parse_type_name()?)
4214 } else {
4215 None
4216 };
4217 variants.push(EnumVariant {
4218 name: variant_name,
4219 ty,
4220 });
4221 if !self.eat(&Token::Comma) {
4222 self.eat(&Token::Semicolon);
4223 }
4224 }
4225 self.expect(&Token::RBrace)?;
4226 self.eat(&Token::Semicolon);
4227 Ok(Statement {
4228 label: None,
4229 kind: StmtKind::EnumDecl {
4230 def: EnumDef { name, variants },
4231 },
4232 line,
4233 })
4234 }
4235
4236 fn parse_class_decl(&mut self, is_abstract: bool, is_final: bool) -> PerlResult<Statement> {
4238 use crate::ast::{ClassDef, ClassField, ClassMethod, ClassStaticField, Visibility};
4239 let line = self.peek_line();
4240 self.advance(); let name = match self.advance() {
4242 (Token::Ident(n), _) => n,
4243 (tok, err_line) => {
4244 return Err(self.syntax_err(format!("Expected class name, got {:?}", tok), err_line))
4245 }
4246 };
4247
4248 let mut extends = Vec::new();
4250 if matches!(self.peek(), Token::Ident(ref s) if s == "extends") {
4251 self.advance(); loop {
4253 match self.advance() {
4254 (Token::Ident(parent), _) => extends.push(parent),
4255 (tok, err_line) => {
4256 return Err(self.syntax_err(
4257 format!("Expected parent class name after `extends`, got {:?}", tok),
4258 err_line,
4259 ))
4260 }
4261 }
4262 if !self.eat(&Token::Comma) {
4263 break;
4264 }
4265 }
4266 }
4267
4268 let mut implements = Vec::new();
4270 if matches!(self.peek(), Token::Ident(ref s) if s == "impl") {
4271 self.advance(); loop {
4273 match self.advance() {
4274 (Token::Ident(trait_name), _) => implements.push(trait_name),
4275 (tok, err_line) => {
4276 return Err(self.syntax_err(
4277 format!("Expected trait name after `impl`, got {:?}", tok),
4278 err_line,
4279 ))
4280 }
4281 }
4282 if !self.eat(&Token::Comma) {
4283 break;
4284 }
4285 }
4286 }
4287
4288 self.expect(&Token::LBrace)?;
4289 let mut fields = Vec::new();
4290 let mut methods = Vec::new();
4291 let mut static_fields = Vec::new();
4292
4293 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
4294 let visibility = match self.peek() {
4296 Token::Ident(ref s) if s == "pub" => {
4297 self.advance();
4298 Visibility::Public
4299 }
4300 Token::Ident(ref s) if s == "priv" => {
4301 self.advance();
4302 Visibility::Private
4303 }
4304 Token::Ident(ref s) if s == "prot" => {
4305 self.advance();
4306 Visibility::Protected
4307 }
4308 _ => Visibility::Public, };
4310
4311 if matches!(self.peek(), Token::Ident(ref s) if s == "static") {
4313 self.advance(); if matches!(self.peek(), Token::Ident(ref s) if s == "fn" || s == "sub") {
4317 return Err(self.syntax_err(
4319 "use `fn Self.name` for static methods, not `static fn`",
4320 self.peek_line(),
4321 ));
4322 }
4323
4324 let field_name = match self.advance() {
4325 (Token::Ident(n), _) => n,
4326 (tok, err_line) => {
4327 return Err(self.syntax_err(
4328 format!("Expected static field name, got {:?}", tok),
4329 err_line,
4330 ))
4331 }
4332 };
4333
4334 let ty = if self.eat(&Token::Colon) {
4335 self.parse_type_name()?
4336 } else {
4337 crate::ast::PerlTypeName::Any
4338 };
4339
4340 let default = if self.eat(&Token::Assign) {
4341 Some(self.parse_ternary()?)
4342 } else {
4343 None
4344 };
4345
4346 static_fields.push(ClassStaticField {
4347 name: field_name,
4348 ty,
4349 visibility,
4350 default,
4351 });
4352
4353 if !self.eat(&Token::Comma) {
4354 self.eat(&Token::Semicolon);
4355 }
4356 continue;
4357 }
4358
4359 let method_is_final = matches!(self.peek(), Token::Ident(ref s) if s == "final");
4361 if method_is_final {
4362 self.advance(); }
4364
4365 let is_method = matches!(self.peek(), Token::Ident(ref s) if s == "fn" || s == "sub");
4367 if is_method {
4368 self.advance(); let is_static = matches!(self.peek(), Token::Ident(ref s) if s == "Self");
4372 if is_static {
4373 self.advance(); self.expect(&Token::Dot)?;
4375 }
4376
4377 let method_name = match self.advance() {
4378 (Token::Ident(n), _) => n,
4379 (tok, err_line) => {
4380 return Err(self
4381 .syntax_err(format!("Expected method name, got {:?}", tok), err_line))
4382 }
4383 };
4384
4385 let params = if self.eat(&Token::LParen) {
4387 let p = self.parse_sub_signature_param_list()?;
4388 self.expect(&Token::RParen)?;
4389 p
4390 } else {
4391 Vec::new()
4392 };
4393
4394 let body = if matches!(self.peek(), Token::LBrace) {
4396 Some(self.parse_block()?)
4397 } else {
4398 None
4399 };
4400
4401 methods.push(ClassMethod {
4402 name: method_name,
4403 params,
4404 body,
4405 visibility,
4406 is_static,
4407 is_final: method_is_final,
4408 });
4409 self.eat(&Token::Comma);
4410 self.eat(&Token::Semicolon);
4411 continue;
4412 } else if method_is_final {
4413 return Err(self.syntax_err("`final` must be followed by `fn`", self.peek_line()));
4414 }
4415
4416 let field_name = match self.advance() {
4418 (Token::Ident(n), _) => n,
4419 (tok, err_line) => {
4420 return Err(
4421 self.syntax_err(format!("Expected field name, got {:?}", tok), err_line)
4422 )
4423 }
4424 };
4425
4426 let ty = if self.eat(&Token::Colon) {
4428 self.parse_type_name()?
4429 } else {
4430 crate::ast::PerlTypeName::Any
4431 };
4432
4433 let default = if self.eat(&Token::Assign) {
4435 Some(self.parse_ternary()?)
4436 } else {
4437 None
4438 };
4439
4440 fields.push(ClassField {
4441 name: field_name,
4442 ty,
4443 visibility,
4444 default,
4445 });
4446
4447 if !self.eat(&Token::Comma) {
4448 self.eat(&Token::Semicolon);
4449 }
4450 }
4451
4452 self.expect(&Token::RBrace)?;
4453 self.eat(&Token::Semicolon);
4454
4455 Ok(Statement {
4456 label: None,
4457 kind: StmtKind::ClassDecl {
4458 def: ClassDef {
4459 name,
4460 is_abstract,
4461 is_final,
4462 extends,
4463 implements,
4464 fields,
4465 methods,
4466 static_fields,
4467 },
4468 },
4469 line,
4470 })
4471 }
4472
4473 fn parse_trait_decl(&mut self) -> PerlResult<Statement> {
4475 use crate::ast::{ClassMethod, TraitDef, Visibility};
4476 let line = self.peek_line();
4477 self.advance(); let name = match self.advance() {
4479 (Token::Ident(n), _) => n,
4480 (tok, err_line) => {
4481 return Err(self.syntax_err(format!("Expected trait name, got {:?}", tok), err_line))
4482 }
4483 };
4484
4485 self.expect(&Token::LBrace)?;
4486 let mut methods = Vec::new();
4487
4488 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
4489 let visibility = match self.peek() {
4491 Token::Ident(ref s) if s == "pub" => {
4492 self.advance();
4493 Visibility::Public
4494 }
4495 Token::Ident(ref s) if s == "priv" => {
4496 self.advance();
4497 Visibility::Private
4498 }
4499 Token::Ident(ref s) if s == "prot" => {
4500 self.advance();
4501 Visibility::Protected
4502 }
4503 _ => Visibility::Public,
4504 };
4505
4506 if !matches!(self.peek(), Token::Ident(ref s) if s == "fn" || s == "sub") {
4508 return Err(self.syntax_err("Expected `fn` in trait definition", self.peek_line()));
4509 }
4510 self.advance(); let method_name = match self.advance() {
4513 (Token::Ident(n), _) => n,
4514 (tok, err_line) => {
4515 return Err(
4516 self.syntax_err(format!("Expected method name, got {:?}", tok), err_line)
4517 )
4518 }
4519 };
4520
4521 let params = if self.eat(&Token::LParen) {
4523 let p = self.parse_sub_signature_param_list()?;
4524 self.expect(&Token::RParen)?;
4525 p
4526 } else {
4527 Vec::new()
4528 };
4529
4530 let body = if matches!(self.peek(), Token::LBrace) {
4532 Some(self.parse_block()?)
4533 } else {
4534 None
4535 };
4536
4537 methods.push(ClassMethod {
4538 name: method_name,
4539 params,
4540 body,
4541 visibility,
4542 is_static: false,
4543 is_final: false,
4544 });
4545
4546 self.eat(&Token::Comma);
4547 self.eat(&Token::Semicolon);
4548 }
4549
4550 self.expect(&Token::RBrace)?;
4551 self.eat(&Token::Semicolon);
4552
4553 Ok(Statement {
4554 label: None,
4555 kind: StmtKind::TraitDecl {
4556 def: TraitDef { name, methods },
4557 },
4558 line,
4559 })
4560 }
4561
4562 fn local_simple_target_to_var_decl(target: &Expr) -> Option<VarDecl> {
4563 match &target.kind {
4564 ExprKind::ScalarVar(name) => Some(VarDecl {
4565 sigil: Sigil::Scalar,
4566 name: name.clone(),
4567 initializer: None,
4568 frozen: false,
4569 type_annotation: None,
4570 }),
4571 ExprKind::ArrayVar(name) => Some(VarDecl {
4572 sigil: Sigil::Array,
4573 name: name.clone(),
4574 initializer: None,
4575 frozen: false,
4576 type_annotation: None,
4577 }),
4578 ExprKind::HashVar(name) => Some(VarDecl {
4579 sigil: Sigil::Hash,
4580 name: name.clone(),
4581 initializer: None,
4582 frozen: false,
4583 type_annotation: None,
4584 }),
4585 ExprKind::Typeglob(name) => Some(VarDecl {
4586 sigil: Sigil::Typeglob,
4587 name: name.clone(),
4588 initializer: None,
4589 frozen: false,
4590 type_annotation: None,
4591 }),
4592 _ => None,
4593 }
4594 }
4595
4596 fn parse_decl_array_destructure(
4597 &mut self,
4598 keyword: &str,
4599 line: usize,
4600 ) -> PerlResult<Statement> {
4601 self.expect(&Token::LBracket)?;
4602 let elems = self.parse_match_array_elems_until_rbracket()?;
4603 self.expect(&Token::Assign)?;
4604 self.suppress_scalar_hash_brace += 1;
4605 let rhs = self.parse_expression()?;
4606 self.suppress_scalar_hash_brace -= 1;
4607 let stmt = self.desugar_array_destructure(keyword, line, elems, rhs)?;
4608 self.parse_stmt_postfix_modifier(stmt)
4609 }
4610
4611 fn parse_decl_hash_destructure(&mut self, keyword: &str, line: usize) -> PerlResult<Statement> {
4612 let MatchPattern::Hash(pairs) = self.parse_match_hash_pattern()? else {
4613 unreachable!("parse_match_hash_pattern returns Hash");
4614 };
4615 self.expect(&Token::Assign)?;
4616 self.suppress_scalar_hash_brace += 1;
4617 let rhs = self.parse_expression()?;
4618 self.suppress_scalar_hash_brace -= 1;
4619 let stmt = self.desugar_hash_destructure(keyword, line, pairs, rhs)?;
4620 self.parse_stmt_postfix_modifier(stmt)
4621 }
4622
4623 fn desugar_array_destructure(
4624 &mut self,
4625 keyword: &str,
4626 line: usize,
4627 elems: Vec<MatchArrayElem>,
4628 rhs: Expr,
4629 ) -> PerlResult<Statement> {
4630 let tmp = format!("__stryke_ds_{}", self.alloc_desugar_tmp());
4631 let mut stmts: Vec<Statement> = Vec::new();
4632 stmts.push(destructure_stmt_from_var_decls(
4633 keyword,
4634 vec![VarDecl {
4635 sigil: Sigil::Scalar,
4636 name: tmp.clone(),
4637 initializer: Some(rhs),
4638 frozen: false,
4639 type_annotation: None,
4640 }],
4641 line,
4642 ));
4643
4644 let has_rest = elems
4645 .iter()
4646 .any(|e| matches!(e, MatchArrayElem::Rest | MatchArrayElem::RestBind(_)));
4647 let fixed_slots = elems
4648 .iter()
4649 .filter(|e| {
4650 matches!(
4651 e,
4652 MatchArrayElem::CaptureScalar(_) | MatchArrayElem::Expr(_)
4653 )
4654 })
4655 .count();
4656 if !has_rest {
4657 let cond = Expr {
4658 kind: ExprKind::BinOp {
4659 left: Box::new(destructure_expr_array_len(&tmp, line)),
4660 op: BinOp::NumEq,
4661 right: Box::new(Expr {
4662 kind: ExprKind::Integer(fixed_slots as i64),
4663 line,
4664 }),
4665 },
4666 line,
4667 };
4668 stmts.push(destructure_stmt_unless_die(
4669 line,
4670 cond,
4671 "array destructure: length mismatch",
4672 ));
4673 }
4674
4675 let mut idx: i64 = 0;
4676 for elem in elems {
4677 match elem {
4678 MatchArrayElem::Rest => break,
4679 MatchArrayElem::RestBind(name) => {
4680 let list_source = Expr {
4681 kind: ExprKind::Deref {
4682 expr: Box::new(destructure_expr_scalar_tmp(&tmp, line)),
4683 kind: Sigil::Array,
4684 },
4685 line,
4686 };
4687 let last_ix = Expr {
4688 kind: ExprKind::BinOp {
4689 left: Box::new(destructure_expr_array_len(&tmp, line)),
4690 op: BinOp::Sub,
4691 right: Box::new(Expr {
4692 kind: ExprKind::Integer(1),
4693 line,
4694 }),
4695 },
4696 line,
4697 };
4698 let range = Expr {
4699 kind: ExprKind::Range {
4700 from: Box::new(Expr {
4701 kind: ExprKind::Integer(idx),
4702 line,
4703 }),
4704 to: Box::new(last_ix),
4705 exclusive: false,
4706 step: None,
4707 },
4708 line,
4709 };
4710 let slice = Expr {
4711 kind: ExprKind::AnonymousListSlice {
4712 source: Box::new(list_source),
4713 indices: vec![range],
4714 },
4715 line,
4716 };
4717 stmts.push(destructure_stmt_from_var_decls(
4718 keyword,
4719 vec![VarDecl {
4720 sigil: Sigil::Array,
4721 name,
4722 initializer: Some(slice),
4723 frozen: false,
4724 type_annotation: None,
4725 }],
4726 line,
4727 ));
4728 break;
4729 }
4730 MatchArrayElem::CaptureScalar(name) => {
4731 let arrow = Expr {
4732 kind: ExprKind::ArrowDeref {
4733 expr: Box::new(destructure_expr_scalar_tmp(&tmp, line)),
4734 index: Box::new(Expr {
4735 kind: ExprKind::Integer(idx),
4736 line,
4737 }),
4738 kind: DerefKind::Array,
4739 },
4740 line,
4741 };
4742 stmts.push(destructure_stmt_from_var_decls(
4743 keyword,
4744 vec![VarDecl {
4745 sigil: Sigil::Scalar,
4746 name,
4747 initializer: Some(arrow),
4748 frozen: false,
4749 type_annotation: None,
4750 }],
4751 line,
4752 ));
4753 idx += 1;
4754 }
4755 MatchArrayElem::Expr(e) => {
4756 let elem_subj = Expr {
4757 kind: ExprKind::ArrowDeref {
4758 expr: Box::new(destructure_expr_scalar_tmp(&tmp, line)),
4759 index: Box::new(Expr {
4760 kind: ExprKind::Integer(idx),
4761 line,
4762 }),
4763 kind: DerefKind::Array,
4764 },
4765 line,
4766 };
4767 let match_expr = Expr {
4768 kind: ExprKind::AlgebraicMatch {
4769 subject: Box::new(elem_subj),
4770 arms: vec![
4771 MatchArm {
4772 pattern: MatchPattern::Value(Box::new(e.clone())),
4773 guard: None,
4774 body: Expr {
4775 kind: ExprKind::Integer(0),
4776 line,
4777 },
4778 },
4779 MatchArm {
4780 pattern: MatchPattern::Any,
4781 guard: None,
4782 body: Expr {
4783 kind: ExprKind::Die(vec![Expr {
4784 kind: ExprKind::String(
4785 "array destructure: element pattern mismatch"
4786 .to_string(),
4787 ),
4788 line,
4789 }]),
4790 line,
4791 },
4792 },
4793 ],
4794 },
4795 line,
4796 };
4797 stmts.push(Statement {
4798 label: None,
4799 kind: StmtKind::Expression(match_expr),
4800 line,
4801 });
4802 idx += 1;
4803 }
4804 }
4805 }
4806
4807 Ok(Statement {
4808 label: None,
4809 kind: StmtKind::StmtGroup(stmts),
4810 line,
4811 })
4812 }
4813
4814 fn desugar_hash_destructure(
4815 &mut self,
4816 keyword: &str,
4817 line: usize,
4818 pairs: Vec<MatchHashPair>,
4819 rhs: Expr,
4820 ) -> PerlResult<Statement> {
4821 let tmp = format!("__stryke_ds_{}", self.alloc_desugar_tmp());
4822 let mut stmts: Vec<Statement> = Vec::new();
4823 stmts.push(destructure_stmt_from_var_decls(
4824 keyword,
4825 vec![VarDecl {
4826 sigil: Sigil::Scalar,
4827 name: tmp.clone(),
4828 initializer: Some(rhs),
4829 frozen: false,
4830 type_annotation: None,
4831 }],
4832 line,
4833 ));
4834
4835 for pair in pairs {
4836 match pair {
4837 MatchHashPair::KeyOnly { key } => {
4838 let exists_op = Expr {
4839 kind: ExprKind::Exists(Box::new(Expr {
4840 kind: ExprKind::ArrowDeref {
4841 expr: Box::new(destructure_expr_scalar_tmp(&tmp, line)),
4842 index: Box::new(key),
4843 kind: DerefKind::Hash,
4844 },
4845 line,
4846 })),
4847 line,
4848 };
4849 stmts.push(destructure_stmt_unless_die(
4850 line,
4851 exists_op,
4852 "hash destructure: missing required key",
4853 ));
4854 }
4855 MatchHashPair::Capture { key, name } => {
4856 let init = Expr {
4857 kind: ExprKind::ArrowDeref {
4858 expr: Box::new(destructure_expr_scalar_tmp(&tmp, line)),
4859 index: Box::new(key),
4860 kind: DerefKind::Hash,
4861 },
4862 line,
4863 };
4864 stmts.push(destructure_stmt_from_var_decls(
4865 keyword,
4866 vec![VarDecl {
4867 sigil: Sigil::Scalar,
4868 name,
4869 initializer: Some(init),
4870 frozen: false,
4871 type_annotation: None,
4872 }],
4873 line,
4874 ));
4875 }
4876 }
4877 }
4878
4879 Ok(Statement {
4880 label: None,
4881 kind: StmtKind::StmtGroup(stmts),
4882 line,
4883 })
4884 }
4885
4886 fn parse_my_our_local(
4887 &mut self,
4888 keyword: &str,
4889 allow_type_annotation: bool,
4890 ) -> PerlResult<Statement> {
4891 let line = self.peek_line();
4892 self.advance(); if keyword == "local"
4895 && !matches!(self.peek(), Token::LParen | Token::LBracket | Token::LBrace)
4896 {
4897 let target = self.parse_postfix()?;
4898 let mut initializer: Option<Expr> = None;
4899 if self.eat(&Token::Assign) {
4900 initializer = Some(self.parse_expression()?);
4901 } else if matches!(
4902 self.peek(),
4903 Token::OrAssign | Token::DefinedOrAssign | Token::AndAssign
4904 ) {
4905 if matches!(&target.kind, ExprKind::Typeglob(_)) {
4906 return Err(self.syntax_err(
4907 "compound assignment on typeglob declaration is not supported",
4908 self.peek_line(),
4909 ));
4910 }
4911 let op = match self.peek().clone() {
4912 Token::OrAssign => BinOp::LogOr,
4913 Token::DefinedOrAssign => BinOp::DefinedOr,
4914 Token::AndAssign => BinOp::LogAnd,
4915 _ => unreachable!(),
4916 };
4917 self.advance();
4918 let rhs = self.parse_assign_expr()?;
4919 let tgt_line = target.line;
4920 initializer = Some(Expr {
4921 kind: ExprKind::CompoundAssign {
4922 target: Box::new(target.clone()),
4923 op,
4924 value: Box::new(rhs),
4925 },
4926 line: tgt_line,
4927 });
4928 }
4929
4930 let kind = if let Some(mut decl) = Self::local_simple_target_to_var_decl(&target) {
4931 decl.initializer = initializer;
4932 StmtKind::Local(vec![decl])
4933 } else {
4934 StmtKind::LocalExpr {
4935 target,
4936 initializer,
4937 }
4938 };
4939 let stmt = Statement {
4940 label: None,
4941 kind,
4942 line,
4943 };
4944 return self.parse_stmt_postfix_modifier(stmt);
4945 }
4946
4947 if matches!(self.peek(), Token::LBracket) {
4948 return self.parse_decl_array_destructure(keyword, line);
4949 }
4950 if matches!(self.peek(), Token::LBrace) {
4951 return self.parse_decl_hash_destructure(keyword, line);
4952 }
4953
4954 let mut decls = Vec::new();
4955
4956 if self.eat(&Token::LParen) {
4957 while !matches!(self.peek(), Token::RParen | Token::Eof) {
4959 let decl = self.parse_var_decl(allow_type_annotation)?;
4960 decls.push(decl);
4961 if !self.eat(&Token::Comma) {
4962 break;
4963 }
4964 }
4965 self.expect(&Token::RParen)?;
4966 } else {
4967 decls.push(self.parse_var_decl(allow_type_annotation)?);
4968 }
4969
4970 if self.eat(&Token::Assign) {
4972 if keyword == "our" && decls.len() == 1 {
4973 while matches!(self.peek(), Token::Ident(ref i) if i == "our") {
4974 self.advance();
4975 decls.push(self.parse_var_decl(allow_type_annotation)?);
4976 if !self.eat(&Token::Assign) {
4977 return Err(self.syntax_err(
4978 "expected `=` after `our` in chained our-declaration",
4979 self.peek_line(),
4980 ));
4981 }
4982 }
4983 }
4984 let val = self.parse_expression()?;
4985 if !crate::compat_mode() && decls.len() == 1 {
4988 let decl = &decls[0];
4989 let target_kind = match decl.sigil {
4990 Sigil::Scalar => ExprKind::ScalarVar(decl.name.clone()),
4991 Sigil::Array => ExprKind::ArrayVar(decl.name.clone()),
4992 Sigil::Hash => ExprKind::HashVar(decl.name.clone()),
4993 Sigil::Typeglob => {
4994 if decls.len() == 1 {
4996 decls[0].initializer = Some(val);
4997 } else {
4998 for d in &mut decls {
4999 d.initializer = Some(val.clone());
5000 }
5001 }
5002 return Ok(Statement {
5003 label: None,
5004 kind: match keyword {
5005 "my" => StmtKind::My(decls),
5006 "mysync" => StmtKind::MySync(decls),
5007 "our" => StmtKind::Our(decls),
5008 "local" => StmtKind::Local(decls),
5009 "state" => StmtKind::State(decls),
5010 _ => unreachable!(),
5011 },
5012 line,
5013 });
5014 }
5015 };
5016 let target = Expr {
5017 kind: target_kind,
5018 line,
5019 };
5020 self.validate_assignment(&target, &val, line)?;
5021 }
5022 if decls.len() == 1 {
5023 decls[0].initializer = Some(val);
5024 } else {
5025 for decl in &mut decls {
5026 decl.initializer = Some(val.clone());
5027 }
5028 }
5029 } else if decls.len() == 1 {
5030 let op = match self.peek().clone() {
5032 Token::OrAssign => Some(BinOp::LogOr),
5033 Token::DefinedOrAssign => Some(BinOp::DefinedOr),
5034 Token::AndAssign => Some(BinOp::LogAnd),
5035 _ => None,
5036 };
5037 if let Some(op) = op {
5038 let d = &decls[0];
5039 if matches!(d.sigil, Sigil::Typeglob) {
5040 return Err(self.syntax_err(
5041 "compound assignment on typeglob declaration is not supported",
5042 self.peek_line(),
5043 ));
5044 }
5045 self.advance();
5046 let rhs = self.parse_assign_expr()?;
5047 let target = Expr {
5048 kind: match d.sigil {
5049 Sigil::Scalar => ExprKind::ScalarVar(d.name.clone()),
5050 Sigil::Array => ExprKind::ArrayVar(d.name.clone()),
5051 Sigil::Hash => ExprKind::HashVar(d.name.clone()),
5052 Sigil::Typeglob => unreachable!(),
5053 },
5054 line,
5055 };
5056 decls[0].initializer = Some(Expr {
5057 kind: ExprKind::CompoundAssign {
5058 target: Box::new(target),
5059 op,
5060 value: Box::new(rhs),
5061 },
5062 line,
5063 });
5064 }
5065 }
5066
5067 let kind = match keyword {
5068 "my" => StmtKind::My(decls),
5069 "mysync" => StmtKind::MySync(decls),
5070 "our" => StmtKind::Our(decls),
5071 "local" => StmtKind::Local(decls),
5072 "state" => StmtKind::State(decls),
5073 _ => unreachable!(),
5074 };
5075 let stmt = Statement {
5076 label: None,
5077 kind,
5078 line,
5079 };
5080 self.parse_stmt_postfix_modifier(stmt)
5082 }
5083
5084 fn parse_var_decl(&mut self, allow_type_annotation: bool) -> PerlResult<VarDecl> {
5085 let mut decl = match self.advance() {
5086 (Token::ScalarVar(name), _) => VarDecl {
5087 sigil: Sigil::Scalar,
5088 name,
5089 initializer: None,
5090 frozen: false,
5091 type_annotation: None,
5092 },
5093 (Token::ArrayVar(name), _) => VarDecl {
5094 sigil: Sigil::Array,
5095 name,
5096 initializer: None,
5097 frozen: false,
5098 type_annotation: None,
5099 },
5100 (Token::HashVar(name), line) => {
5101 if !crate::compat_mode() {
5102 self.check_hash_shadows_reserved(&name, line)?;
5103 }
5104 VarDecl {
5105 sigil: Sigil::Hash,
5106 name,
5107 initializer: None,
5108 frozen: false,
5109 type_annotation: None,
5110 }
5111 }
5112 (Token::Star, _line) => {
5113 let name = match self.advance() {
5114 (Token::Ident(n), _) => n,
5115 (tok, l) => {
5116 return Err(self
5117 .syntax_err(format!("Expected identifier after *, got {:?}", tok), l));
5118 }
5119 };
5120 VarDecl {
5121 sigil: Sigil::Typeglob,
5122 name,
5123 initializer: None,
5124 frozen: false,
5125 type_annotation: None,
5126 }
5127 }
5128 (Token::Ident(ref kw), _) if kw == "undef" => VarDecl {
5133 sigil: Sigil::Scalar,
5134 name: format!("__undef_sink_{}", self.pos),
5138 initializer: None,
5139 frozen: false,
5140 type_annotation: None,
5141 },
5142 (tok, line) => {
5143 return Err(self.syntax_err(
5144 format!("Expected variable in declaration, got {:?}", tok),
5145 line,
5146 ));
5147 }
5148 };
5149 if allow_type_annotation && self.eat(&Token::Colon) {
5150 let ty = self.parse_type_name()?;
5151 if decl.sigil != Sigil::Scalar {
5152 return Err(self.syntax_err(
5153 "`: Type` is only valid for scalar declarations (typed my $name : Int)",
5154 self.peek_line(),
5155 ));
5156 }
5157 decl.type_annotation = Some(ty);
5158 }
5159 Ok(decl)
5160 }
5161
5162 fn parse_type_name(&mut self) -> PerlResult<PerlTypeName> {
5163 match self.advance() {
5164 (Token::Ident(name), _) => match name.as_str() {
5165 "Int" => Ok(PerlTypeName::Int),
5166 "Str" => Ok(PerlTypeName::Str),
5167 "Float" => Ok(PerlTypeName::Float),
5168 "Bool" => Ok(PerlTypeName::Bool),
5169 "Array" => Ok(PerlTypeName::Array),
5170 "Hash" => Ok(PerlTypeName::Hash),
5171 "Ref" => Ok(PerlTypeName::Ref),
5172 "Any" => Ok(PerlTypeName::Any),
5173 _ => Ok(PerlTypeName::Struct(name)),
5174 },
5175 (tok, err_line) => Err(self.syntax_err(
5176 format!("Expected type name after `:`, got {:?}", tok),
5177 err_line,
5178 )),
5179 }
5180 }
5181
5182 fn parse_package(&mut self) -> PerlResult<Statement> {
5183 let line = self.peek_line();
5184 self.advance(); let name = match self.advance() {
5186 (Token::Ident(n), _) => n,
5187 (tok, line) => {
5188 return Err(self.syntax_err(format!("Expected package name, got {:?}", tok), line))
5189 }
5190 };
5191 let mut full_name = name;
5193 while self.eat(&Token::PackageSep) {
5194 if let (Token::Ident(part), _) = self.advance() {
5195 full_name = format!("{}::{}", full_name, part);
5196 }
5197 }
5198 self.eat(&Token::Semicolon);
5199 Ok(Statement {
5200 label: None,
5201 kind: StmtKind::Package { name: full_name },
5202 line,
5203 })
5204 }
5205
5206 fn parse_use(&mut self) -> PerlResult<Statement> {
5207 let line = self.peek_line();
5208 self.advance(); let (tok, tok_line) = self.advance();
5210 match tok {
5211 Token::Float(v) => {
5212 self.eat(&Token::Semicolon);
5213 Ok(Statement {
5214 label: None,
5215 kind: StmtKind::UsePerlVersion { version: v },
5216 line,
5217 })
5218 }
5219 Token::Integer(n) => {
5220 if matches!(self.peek(), Token::Semicolon | Token::Eof) {
5221 self.eat(&Token::Semicolon);
5222 Ok(Statement {
5223 label: None,
5224 kind: StmtKind::UsePerlVersion { version: n as f64 },
5225 line,
5226 })
5227 } else {
5228 Err(self.syntax_err(
5229 format!("Expected ';' after use VERSION (got {:?})", self.peek()),
5230 line,
5231 ))
5232 }
5233 }
5234 Token::Ident(n) => {
5235 let mut full_name = n;
5236 while self.eat(&Token::PackageSep) {
5237 if let (Token::Ident(part), _) = self.advance() {
5238 full_name = format!("{}::{}", full_name, part);
5239 }
5240 }
5241 if full_name == "overload" {
5242 let mut pairs = Vec::new();
5243 let mut parse_overload_pairs = |this: &mut Self| -> PerlResult<()> {
5244 loop {
5245 if matches!(this.peek(), Token::RParen | Token::Semicolon | Token::Eof)
5246 {
5247 break;
5248 }
5249 let key_e = this.parse_assign_expr()?;
5250 this.expect(&Token::FatArrow)?;
5251 let val_e = this.parse_assign_expr()?;
5252 let key = this.expr_to_overload_key(&key_e)?;
5253 let val = this.expr_to_overload_sub(&val_e)?;
5254 pairs.push((key, val));
5255 if !this.eat(&Token::Comma) {
5256 break;
5257 }
5258 }
5259 Ok(())
5260 };
5261 if self.eat(&Token::LParen) {
5262 parse_overload_pairs(self)?;
5264 self.expect(&Token::RParen)?;
5265 } else if !matches!(self.peek(), Token::Semicolon | Token::Eof) {
5266 parse_overload_pairs(self)?;
5267 }
5268 self.eat(&Token::Semicolon);
5269 return Ok(Statement {
5270 label: None,
5271 kind: StmtKind::UseOverload { pairs },
5272 line,
5273 });
5274 }
5275 let mut imports = Vec::new();
5276 if !matches!(self.peek(), Token::Semicolon | Token::Eof)
5277 && !self.next_is_new_statement_start(tok_line)
5278 {
5279 loop {
5280 if matches!(self.peek(), Token::Semicolon | Token::Eof) {
5281 break;
5282 }
5283 imports.push(self.parse_expression()?);
5284 if !self.eat(&Token::Comma) {
5285 break;
5286 }
5287 }
5288 }
5289 self.eat(&Token::Semicolon);
5290 Ok(Statement {
5291 label: None,
5292 kind: StmtKind::Use {
5293 module: full_name,
5294 imports,
5295 },
5296 line,
5297 })
5298 }
5299 other => Err(self.syntax_err(
5300 format!("Expected module name or version after use, got {:?}", other),
5301 tok_line,
5302 )),
5303 }
5304 }
5305
5306 fn parse_no(&mut self) -> PerlResult<Statement> {
5307 let line = self.peek_line();
5308 self.advance(); let module = match self.advance() {
5310 (Token::Ident(n), tok_line) => (n, tok_line),
5311 (tok, line) => {
5312 return Err(self.syntax_err(
5313 format!("Expected module name after no, got {:?}", tok),
5314 line,
5315 ))
5316 }
5317 };
5318 let (module_name, tok_line) = module;
5319 let mut full_name = module_name;
5320 while self.eat(&Token::PackageSep) {
5321 if let (Token::Ident(part), _) = self.advance() {
5322 full_name = format!("{}::{}", full_name, part);
5323 }
5324 }
5325 let mut imports = Vec::new();
5326 if !matches!(self.peek(), Token::Semicolon | Token::Eof)
5327 && !self.next_is_new_statement_start(tok_line)
5328 {
5329 loop {
5330 if matches!(self.peek(), Token::Semicolon | Token::Eof) {
5331 break;
5332 }
5333 imports.push(self.parse_expression()?);
5334 if !self.eat(&Token::Comma) {
5335 break;
5336 }
5337 }
5338 }
5339 self.eat(&Token::Semicolon);
5340 Ok(Statement {
5341 label: None,
5342 kind: StmtKind::No {
5343 module: full_name,
5344 imports,
5345 },
5346 line,
5347 })
5348 }
5349
5350 fn parse_return(&mut self) -> PerlResult<Statement> {
5351 let line = self.peek_line();
5352 self.advance(); let val = if matches!(self.peek(), Token::Semicolon | Token::RBrace | Token::Eof) {
5354 None
5355 } else {
5356 Some(self.parse_assign_expr()?)
5358 };
5359 let stmt = Statement {
5361 label: None,
5362 kind: StmtKind::Return(val),
5363 line,
5364 };
5365 if let Token::Ident(ref kw) = self.peek().clone() {
5366 match kw.as_str() {
5367 "if" => {
5368 self.advance();
5369 let cond = self.parse_expression()?;
5370 self.eat(&Token::Semicolon);
5371 return Ok(Statement {
5372 label: None,
5373 kind: StmtKind::If {
5374 condition: cond,
5375 body: vec![stmt],
5376 elsifs: vec![],
5377 else_block: None,
5378 },
5379 line,
5380 });
5381 }
5382 "unless" => {
5383 self.advance();
5384 let cond = self.parse_expression()?;
5385 self.eat(&Token::Semicolon);
5386 return Ok(Statement {
5387 label: None,
5388 kind: StmtKind::Unless {
5389 condition: cond,
5390 body: vec![stmt],
5391 else_block: None,
5392 },
5393 line,
5394 });
5395 }
5396 _ => {}
5397 }
5398 }
5399 self.eat(&Token::Semicolon);
5400 Ok(stmt)
5401 }
5402
5403 fn parse_expression(&mut self) -> PerlResult<Expr> {
5406 self.parse_comma_expr()
5407 }
5408
5409 fn parse_comma_expr(&mut self) -> PerlResult<Expr> {
5410 let expr = self.parse_assign_expr()?;
5411 let mut exprs = vec![expr];
5412 while self.eat(&Token::Comma) || self.eat(&Token::FatArrow) {
5413 if matches!(
5414 self.peek(),
5415 Token::RParen | Token::RBracket | Token::RBrace | Token::Semicolon | Token::Eof
5416 ) {
5417 break; }
5419 exprs.push(self.parse_assign_expr()?);
5420 }
5421 if exprs.len() == 1 {
5422 return Ok(exprs.pop().unwrap());
5423 }
5424 let line = exprs[0].line;
5425 Ok(Expr {
5426 kind: ExprKind::List(exprs),
5427 line,
5428 })
5429 }
5430
5431 fn parse_assign_expr(&mut self) -> PerlResult<Expr> {
5432 let expr = self.parse_ternary()?;
5433 let line = expr.line;
5434
5435 match self.peek().clone() {
5436 Token::Assign => {
5437 self.advance();
5438 let right = self.parse_assign_expr()?;
5439 if let ExprKind::MethodCall { ref args, .. } = expr.kind {
5441 if args.is_empty() {
5442 let ExprKind::MethodCall {
5444 object,
5445 method,
5446 super_call,
5447 ..
5448 } = expr.kind
5449 else {
5450 unreachable!()
5451 };
5452 return Ok(Expr {
5453 kind: ExprKind::MethodCall {
5454 object,
5455 method,
5456 args: vec![right],
5457 super_call,
5458 },
5459 line,
5460 });
5461 }
5462 }
5463 self.validate_assignment(&expr, &right, line)?;
5464 Ok(Expr {
5465 kind: ExprKind::Assign {
5466 target: Box::new(expr),
5467 value: Box::new(right),
5468 },
5469 line,
5470 })
5471 }
5472 Token::PlusAssign => {
5473 self.advance();
5474 let r = self.parse_assign_expr()?;
5475 Ok(Expr {
5476 kind: ExprKind::CompoundAssign {
5477 target: Box::new(expr),
5478 op: BinOp::Add,
5479 value: Box::new(r),
5480 },
5481 line,
5482 })
5483 }
5484 Token::MinusAssign => {
5485 self.advance();
5486 let r = self.parse_assign_expr()?;
5487 Ok(Expr {
5488 kind: ExprKind::CompoundAssign {
5489 target: Box::new(expr),
5490 op: BinOp::Sub,
5491 value: Box::new(r),
5492 },
5493 line,
5494 })
5495 }
5496 Token::MulAssign => {
5497 self.advance();
5498 let r = self.parse_assign_expr()?;
5499 Ok(Expr {
5500 kind: ExprKind::CompoundAssign {
5501 target: Box::new(expr),
5502 op: BinOp::Mul,
5503 value: Box::new(r),
5504 },
5505 line,
5506 })
5507 }
5508 Token::DivAssign => {
5509 self.advance();
5510 let r = self.parse_assign_expr()?;
5511 Ok(Expr {
5512 kind: ExprKind::CompoundAssign {
5513 target: Box::new(expr),
5514 op: BinOp::Div,
5515 value: Box::new(r),
5516 },
5517 line,
5518 })
5519 }
5520 Token::ModAssign => {
5521 self.advance();
5522 let r = self.parse_assign_expr()?;
5523 Ok(Expr {
5524 kind: ExprKind::CompoundAssign {
5525 target: Box::new(expr),
5526 op: BinOp::Mod,
5527 value: Box::new(r),
5528 },
5529 line,
5530 })
5531 }
5532 Token::PowAssign => {
5533 self.advance();
5534 let r = self.parse_assign_expr()?;
5535 Ok(Expr {
5536 kind: ExprKind::CompoundAssign {
5537 target: Box::new(expr),
5538 op: BinOp::Pow,
5539 value: Box::new(r),
5540 },
5541 line,
5542 })
5543 }
5544 Token::DotAssign => {
5545 self.advance();
5546 let r = self.parse_assign_expr()?;
5547 Ok(Expr {
5548 kind: ExprKind::CompoundAssign {
5549 target: Box::new(expr),
5550 op: BinOp::Concat,
5551 value: Box::new(r),
5552 },
5553 line,
5554 })
5555 }
5556 Token::BitAndAssign => {
5557 self.advance();
5558 let r = self.parse_assign_expr()?;
5559 Ok(Expr {
5560 kind: ExprKind::CompoundAssign {
5561 target: Box::new(expr),
5562 op: BinOp::BitAnd,
5563 value: Box::new(r),
5564 },
5565 line,
5566 })
5567 }
5568 Token::BitOrAssign => {
5569 self.advance();
5570 let r = self.parse_assign_expr()?;
5571 Ok(Expr {
5572 kind: ExprKind::CompoundAssign {
5573 target: Box::new(expr),
5574 op: BinOp::BitOr,
5575 value: Box::new(r),
5576 },
5577 line,
5578 })
5579 }
5580 Token::XorAssign => {
5581 self.advance();
5582 let r = self.parse_assign_expr()?;
5583 Ok(Expr {
5584 kind: ExprKind::CompoundAssign {
5585 target: Box::new(expr),
5586 op: BinOp::BitXor,
5587 value: Box::new(r),
5588 },
5589 line,
5590 })
5591 }
5592 Token::ShiftLeftAssign => {
5593 self.advance();
5594 let r = self.parse_assign_expr()?;
5595 Ok(Expr {
5596 kind: ExprKind::CompoundAssign {
5597 target: Box::new(expr),
5598 op: BinOp::ShiftLeft,
5599 value: Box::new(r),
5600 },
5601 line,
5602 })
5603 }
5604 Token::ShiftRightAssign => {
5605 self.advance();
5606 let r = self.parse_assign_expr()?;
5607 Ok(Expr {
5608 kind: ExprKind::CompoundAssign {
5609 target: Box::new(expr),
5610 op: BinOp::ShiftRight,
5611 value: Box::new(r),
5612 },
5613 line,
5614 })
5615 }
5616 Token::OrAssign => {
5617 self.advance();
5618 let r = self.parse_assign_expr()?;
5619 Ok(Expr {
5620 kind: ExprKind::CompoundAssign {
5621 target: Box::new(expr),
5622 op: BinOp::LogOr,
5623 value: Box::new(r),
5624 },
5625 line,
5626 })
5627 }
5628 Token::DefinedOrAssign => {
5629 self.advance();
5630 let r = self.parse_assign_expr()?;
5631 Ok(Expr {
5632 kind: ExprKind::CompoundAssign {
5633 target: Box::new(expr),
5634 op: BinOp::DefinedOr,
5635 value: Box::new(r),
5636 },
5637 line,
5638 })
5639 }
5640 Token::AndAssign => {
5641 self.advance();
5642 let r = self.parse_assign_expr()?;
5643 Ok(Expr {
5644 kind: ExprKind::CompoundAssign {
5645 target: Box::new(expr),
5646 op: BinOp::LogAnd,
5647 value: Box::new(r),
5648 },
5649 line,
5650 })
5651 }
5652 _ => Ok(expr),
5653 }
5654 }
5655
5656 fn parse_ternary(&mut self) -> PerlResult<Expr> {
5657 let expr = self.parse_pipe_forward()?;
5658 if self.eat(&Token::Question) {
5659 let line = expr.line;
5660 self.suppress_colon_range = self.suppress_colon_range.saturating_add(1);
5661 let then_expr = self.parse_assign_expr();
5662 self.suppress_colon_range = self.suppress_colon_range.saturating_sub(1);
5663 let then_expr = then_expr?;
5664 self.expect(&Token::Colon)?;
5665 let else_expr = self.parse_assign_expr()?;
5666 return Ok(Expr {
5667 kind: ExprKind::Ternary {
5668 condition: Box::new(expr),
5669 then_expr: Box::new(then_expr),
5670 else_expr: Box::new(else_expr),
5671 },
5672 line,
5673 });
5674 }
5675 Ok(expr)
5676 }
5677
5678 fn parse_pipe_forward(&mut self) -> PerlResult<Expr> {
5684 let mut left = self.parse_or_word()?;
5685 if self.no_pipe_forward_depth > 0 {
5691 return Ok(left);
5692 }
5693 while matches!(self.peek(), Token::PipeForward) {
5694 if crate::compat_mode() {
5695 return Err(self.syntax_err(
5696 "pipe-forward operator `|>` is a stryke extension (disabled by --compat)",
5697 left.line,
5698 ));
5699 }
5700 let line = left.line;
5701 self.advance();
5702 self.pipe_rhs_depth = self.pipe_rhs_depth.saturating_add(1);
5705 let right_result = self.parse_or_word();
5706 self.pipe_rhs_depth = self.pipe_rhs_depth.saturating_sub(1);
5707 let right = right_result?;
5708 left = self.pipe_forward_apply(left, right, line)?;
5709 }
5710 Ok(left)
5711 }
5712
5713 fn pipe_forward_apply(&self, lhs: Expr, rhs: Expr, line: usize) -> PerlResult<Expr> {
5735 let Expr { kind, line: rline } = rhs;
5736 let new_kind = match kind {
5737 ExprKind::FuncCall { name, mut args } => {
5739 match name.as_str() {
5740 "puniq" | "uniq" | "distinct" | "flatten" | "set" | "list_count"
5741 | "list_size" | "count" | "size" | "cnt" | "len" | "with_index" | "shuffle"
5742 | "shuffled" | "frequencies" | "freq" | "interleave" | "ddump"
5743 | "stringify" | "str" | "lines" | "words" | "chars" | "digits" | "letters"
5744 | "letters_uc" | "letters_lc" | "punctuation" | "numbers" | "graphemes"
5745 | "columns" | "sentences" | "paragraphs" | "sections" | "trim" | "avg"
5746 | "to_json" | "to_csv" | "to_toml" | "to_yaml" | "to_xml" | "to_html"
5747 | "from_json" | "from_csv" | "from_toml" | "from_yaml" | "from_xml"
5748 | "to_markdown" | "to_table" | "xopen" | "clip" | "sparkline" | "bar_chart"
5749 | "flame" | "stddev" | "squared" | "sq" | "square" | "cubed" | "cb"
5750 | "cube" | "normalize" | "snake_case" | "camel_case" | "kebab_case" => {
5751 if args.is_empty() {
5752 args.push(lhs);
5753 } else {
5754 args[0] = lhs;
5755 }
5756 }
5757 "chunked" | "windowed" => {
5758 if args.is_empty() {
5759 return Err(self.syntax_err(
5760 "|>: chunked(N) / windowed(N) needs size — e.g. `@a |> windowed(2)`",
5761 line,
5762 ));
5763 }
5764 args.insert(0, lhs);
5765 }
5766 "List::Util::reduce" | "List::Util::fold" => {
5767 args.push(lhs);
5768 }
5769 "grep_v" | "pluck" | "tee" | "nth" | "chunk" => {
5770 args.push(lhs);
5776 }
5777 "enumerate" | "dedup" => {
5778 args.insert(0, lhs);
5781 }
5782 "clamp" => {
5783 args.push(lhs);
5785 }
5786 "pfirst" | "pany" | "any" | "all" | "none" | "first" | "take_while"
5787 | "drop_while" | "skip_while" | "reject" | "tap" | "peek" | "group_by"
5788 | "chunk_by" | "partition" | "min_by" | "max_by" | "zip_with" | "count_by" => {
5789 if args.len() < 2 {
5790 return Err(self.syntax_err(
5791 format!(
5792 "|>: `{name}` needs {{ BLOCK }}, LIST so the list can receive the pipe"
5793 ),
5794 line,
5795 ));
5796 }
5797 args[1] = lhs;
5798 }
5799 "take" | "head" | "tail" | "drop" | "List::Util::head" | "List::Util::tail" => {
5800 if args.is_empty() {
5801 return Err(self.syntax_err(
5802 "|>: `{name}` needs N last — e.g. `@a |> take(3)` for `take(@a, 3)`",
5803 line,
5804 ));
5805 }
5806 args.insert(0, lhs);
5808 }
5809 _ => {
5810 if self.thread_last_mode {
5811 args.push(lhs);
5812 } else {
5813 args.insert(0, lhs);
5814 }
5815 }
5816 }
5817 ExprKind::FuncCall { name, args }
5818 }
5819 ExprKind::MethodCall {
5820 object,
5821 method,
5822 mut args,
5823 super_call,
5824 } => {
5825 if self.thread_last_mode {
5826 args.push(lhs);
5827 } else {
5828 args.insert(0, lhs);
5829 }
5830 ExprKind::MethodCall {
5831 object,
5832 method,
5833 args,
5834 super_call,
5835 }
5836 }
5837 ExprKind::IndirectCall {
5838 target,
5839 mut args,
5840 ampersand,
5841 pass_caller_arglist: _,
5842 } => {
5843 if self.thread_last_mode {
5844 args.push(lhs);
5845 } else {
5846 args.insert(0, lhs);
5847 }
5848 ExprKind::IndirectCall {
5849 target,
5850 args,
5851 ampersand,
5852 pass_caller_arglist: false,
5855 }
5856 }
5857
5858 ExprKind::Print { handle, mut args } => {
5860 if self.thread_last_mode {
5861 args.push(lhs);
5862 } else {
5863 args.insert(0, lhs);
5864 }
5865 ExprKind::Print { handle, args }
5866 }
5867 ExprKind::Say { handle, mut args } => {
5868 if self.thread_last_mode {
5869 args.push(lhs);
5870 } else {
5871 args.insert(0, lhs);
5872 }
5873 ExprKind::Say { handle, args }
5874 }
5875 ExprKind::Printf { handle, mut args } => {
5876 if self.thread_last_mode {
5877 args.push(lhs);
5878 } else {
5879 args.insert(0, lhs);
5880 }
5881 ExprKind::Printf { handle, args }
5882 }
5883 ExprKind::Die(mut args) => {
5884 if self.thread_last_mode {
5885 args.push(lhs);
5886 } else {
5887 args.insert(0, lhs);
5888 }
5889 ExprKind::Die(args)
5890 }
5891 ExprKind::Warn(mut args) => {
5892 if self.thread_last_mode {
5893 args.push(lhs);
5894 } else {
5895 args.insert(0, lhs);
5896 }
5897 ExprKind::Warn(args)
5898 }
5899
5900 ExprKind::Sprintf { format, mut args } => {
5906 if self.thread_last_mode {
5907 args.push(lhs);
5908 } else {
5909 args.insert(0, lhs);
5910 }
5911 ExprKind::Sprintf { format, args }
5912 }
5913
5914 ExprKind::System(mut args) => {
5916 if self.thread_last_mode {
5917 args.push(lhs);
5918 } else {
5919 args.insert(0, lhs);
5920 }
5921 ExprKind::System(args)
5922 }
5923 ExprKind::Exec(mut args) => {
5924 if self.thread_last_mode {
5925 args.push(lhs);
5926 } else {
5927 args.insert(0, lhs);
5928 }
5929 ExprKind::Exec(args)
5930 }
5931 ExprKind::Unlink(mut args) => {
5932 if self.thread_last_mode {
5933 args.push(lhs);
5934 } else {
5935 args.insert(0, lhs);
5936 }
5937 ExprKind::Unlink(args)
5938 }
5939 ExprKind::Chmod(mut args) => {
5940 if self.thread_last_mode {
5941 args.push(lhs);
5942 } else {
5943 args.insert(0, lhs);
5944 }
5945 ExprKind::Chmod(args)
5946 }
5947 ExprKind::Chown(mut args) => {
5948 if self.thread_last_mode {
5949 args.push(lhs);
5950 } else {
5951 args.insert(0, lhs);
5952 }
5953 ExprKind::Chown(args)
5954 }
5955 ExprKind::Glob(mut args) => {
5956 if self.thread_last_mode {
5957 args.push(lhs);
5958 } else {
5959 args.insert(0, lhs);
5960 }
5961 ExprKind::Glob(args)
5962 }
5963 ExprKind::Files(mut args) => {
5964 if self.thread_last_mode {
5965 args.push(lhs);
5966 } else {
5967 args.insert(0, lhs);
5968 }
5969 ExprKind::Files(args)
5970 }
5971 ExprKind::Filesf(mut args) => {
5972 if self.thread_last_mode {
5973 args.push(lhs);
5974 } else {
5975 args.insert(0, lhs);
5976 }
5977 ExprKind::Filesf(args)
5978 }
5979 ExprKind::FilesfRecursive(mut args) => {
5980 if self.thread_last_mode {
5981 args.push(lhs);
5982 } else {
5983 args.insert(0, lhs);
5984 }
5985 ExprKind::FilesfRecursive(args)
5986 }
5987 ExprKind::Dirs(mut args) => {
5988 if self.thread_last_mode {
5989 args.push(lhs);
5990 } else {
5991 args.insert(0, lhs);
5992 }
5993 ExprKind::Dirs(args)
5994 }
5995 ExprKind::DirsRecursive(mut args) => {
5996 if self.thread_last_mode {
5997 args.push(lhs);
5998 } else {
5999 args.insert(0, lhs);
6000 }
6001 ExprKind::DirsRecursive(args)
6002 }
6003 ExprKind::SymLinks(mut args) => {
6004 if self.thread_last_mode {
6005 args.push(lhs);
6006 } else {
6007 args.insert(0, lhs);
6008 }
6009 ExprKind::SymLinks(args)
6010 }
6011 ExprKind::Sockets(mut args) => {
6012 if self.thread_last_mode {
6013 args.push(lhs);
6014 } else {
6015 args.insert(0, lhs);
6016 }
6017 ExprKind::Sockets(args)
6018 }
6019 ExprKind::Pipes(mut args) => {
6020 if self.thread_last_mode {
6021 args.push(lhs);
6022 } else {
6023 args.insert(0, lhs);
6024 }
6025 ExprKind::Pipes(args)
6026 }
6027 ExprKind::BlockDevices(mut args) => {
6028 if self.thread_last_mode {
6029 args.push(lhs);
6030 } else {
6031 args.insert(0, lhs);
6032 }
6033 ExprKind::BlockDevices(args)
6034 }
6035 ExprKind::CharDevices(mut args) => {
6036 if self.thread_last_mode {
6037 args.push(lhs);
6038 } else {
6039 args.insert(0, lhs);
6040 }
6041 ExprKind::CharDevices(args)
6042 }
6043 ExprKind::GlobPar { mut args, progress } => {
6044 if self.thread_last_mode {
6045 args.push(lhs);
6046 } else {
6047 args.insert(0, lhs);
6048 }
6049 ExprKind::GlobPar { args, progress }
6050 }
6051 ExprKind::ParSed { mut args, progress } => {
6052 if self.thread_last_mode {
6053 args.push(lhs);
6054 } else {
6055 args.insert(0, lhs);
6056 }
6057 ExprKind::ParSed { args, progress }
6058 }
6059
6060 ExprKind::Length(_) => ExprKind::Length(Box::new(lhs)),
6062 ExprKind::Abs(_) => ExprKind::Abs(Box::new(lhs)),
6063 ExprKind::Int(_) => ExprKind::Int(Box::new(lhs)),
6064 ExprKind::Sqrt(_) => ExprKind::Sqrt(Box::new(lhs)),
6065 ExprKind::Sin(_) => ExprKind::Sin(Box::new(lhs)),
6066 ExprKind::Cos(_) => ExprKind::Cos(Box::new(lhs)),
6067 ExprKind::Exp(_) => ExprKind::Exp(Box::new(lhs)),
6068 ExprKind::Log(_) => ExprKind::Log(Box::new(lhs)),
6069 ExprKind::Hex(_) => ExprKind::Hex(Box::new(lhs)),
6070 ExprKind::Oct(_) => ExprKind::Oct(Box::new(lhs)),
6071 ExprKind::Lc(_) => ExprKind::Lc(Box::new(lhs)),
6072 ExprKind::Uc(_) => ExprKind::Uc(Box::new(lhs)),
6073 ExprKind::Lcfirst(_) => ExprKind::Lcfirst(Box::new(lhs)),
6074 ExprKind::Ucfirst(_) => ExprKind::Ucfirst(Box::new(lhs)),
6075 ExprKind::Fc(_) => ExprKind::Fc(Box::new(lhs)),
6076 ExprKind::Chr(_) => ExprKind::Chr(Box::new(lhs)),
6077 ExprKind::Ord(_) => ExprKind::Ord(Box::new(lhs)),
6078 ExprKind::Chomp(_) => ExprKind::Chomp(Box::new(lhs)),
6079 ExprKind::Chop(_) => ExprKind::Chop(Box::new(lhs)),
6080 ExprKind::Defined(_) => ExprKind::Defined(Box::new(lhs)),
6081 ExprKind::Ref(_) => ExprKind::Ref(Box::new(lhs)),
6082 ExprKind::ScalarContext(_) => ExprKind::ScalarContext(Box::new(lhs)),
6083 ExprKind::Keys(_) => ExprKind::Keys(Box::new(lhs)),
6084 ExprKind::Values(_) => ExprKind::Values(Box::new(lhs)),
6085 ExprKind::Each(_) => ExprKind::Each(Box::new(lhs)),
6086 ExprKind::Pop(_) => ExprKind::Pop(Box::new(lhs)),
6087 ExprKind::Shift(_) => ExprKind::Shift(Box::new(lhs)),
6088 ExprKind::Delete(_) => ExprKind::Delete(Box::new(lhs)),
6089 ExprKind::Exists(_) => ExprKind::Exists(Box::new(lhs)),
6090 ExprKind::ReverseExpr(_) => ExprKind::ReverseExpr(Box::new(lhs)),
6091 ExprKind::Rev(_) => ExprKind::Rev(Box::new(lhs)),
6092 ExprKind::Slurp(_) => ExprKind::Slurp(Box::new(lhs)),
6093 ExprKind::Capture(_) => ExprKind::Capture(Box::new(lhs)),
6094 ExprKind::Qx(_) => ExprKind::Qx(Box::new(lhs)),
6095 ExprKind::FetchUrl(_) => ExprKind::FetchUrl(Box::new(lhs)),
6096 ExprKind::Close(_) => ExprKind::Close(Box::new(lhs)),
6097 ExprKind::Chdir(_) => ExprKind::Chdir(Box::new(lhs)),
6098 ExprKind::Readdir(_) => ExprKind::Readdir(Box::new(lhs)),
6099 ExprKind::Closedir(_) => ExprKind::Closedir(Box::new(lhs)),
6100 ExprKind::Rewinddir(_) => ExprKind::Rewinddir(Box::new(lhs)),
6101 ExprKind::Telldir(_) => ExprKind::Telldir(Box::new(lhs)),
6102 ExprKind::Stat(_) => ExprKind::Stat(Box::new(lhs)),
6103 ExprKind::Lstat(_) => ExprKind::Lstat(Box::new(lhs)),
6104 ExprKind::Readlink(_) => ExprKind::Readlink(Box::new(lhs)),
6105 ExprKind::Study(_) => ExprKind::Study(Box::new(lhs)),
6106 ExprKind::Await(_) => ExprKind::Await(Box::new(lhs)),
6107 ExprKind::Eval(_) => ExprKind::Eval(Box::new(lhs)),
6108 ExprKind::Rand(_) => ExprKind::Rand(Some(Box::new(lhs))),
6109 ExprKind::Srand(_) => ExprKind::Srand(Some(Box::new(lhs))),
6110 ExprKind::Pos(_) => ExprKind::Pos(Some(Box::new(lhs))),
6111 ExprKind::Exit(_) => ExprKind::Exit(Some(Box::new(lhs))),
6112
6113 ExprKind::MapExpr {
6115 block,
6116 list: _,
6117 flatten_array_refs,
6118 stream,
6119 } => ExprKind::MapExpr {
6120 block,
6121 list: Box::new(lhs),
6122 flatten_array_refs,
6123 stream,
6124 },
6125 ExprKind::MapExprComma {
6126 expr,
6127 list: _,
6128 flatten_array_refs,
6129 stream,
6130 } => ExprKind::MapExprComma {
6131 expr,
6132 list: Box::new(lhs),
6133 flatten_array_refs,
6134 stream,
6135 },
6136 ExprKind::GrepExpr {
6137 block,
6138 list: _,
6139 keyword,
6140 } => ExprKind::GrepExpr {
6141 block,
6142 list: Box::new(lhs),
6143 keyword,
6144 },
6145 ExprKind::GrepExprComma {
6146 expr,
6147 list: _,
6148 keyword,
6149 } => ExprKind::GrepExprComma {
6150 expr,
6151 list: Box::new(lhs),
6152 keyword,
6153 },
6154 ExprKind::ForEachExpr { block, list: _ } => ExprKind::ForEachExpr {
6155 block,
6156 list: Box::new(lhs),
6157 },
6158 ExprKind::SortExpr { cmp, list: _ } => ExprKind::SortExpr {
6159 cmp,
6160 list: Box::new(lhs),
6161 },
6162 ExprKind::JoinExpr { separator, list: _ } => ExprKind::JoinExpr {
6163 separator,
6164 list: Box::new(lhs),
6165 },
6166 ExprKind::ReduceExpr { block, list: _ } => ExprKind::ReduceExpr {
6167 block,
6168 list: Box::new(lhs),
6169 },
6170 ExprKind::PMapExpr {
6171 block,
6172 list: _,
6173 progress,
6174 flat_outputs,
6175 on_cluster,
6176 stream,
6177 } => ExprKind::PMapExpr {
6178 block,
6179 list: Box::new(lhs),
6180 progress,
6181 flat_outputs,
6182 on_cluster,
6183 stream,
6184 },
6185 ExprKind::PMapChunkedExpr {
6186 chunk_size,
6187 block,
6188 list: _,
6189 progress,
6190 } => ExprKind::PMapChunkedExpr {
6191 chunk_size,
6192 block,
6193 list: Box::new(lhs),
6194 progress,
6195 },
6196 ExprKind::PGrepExpr {
6197 block,
6198 list: _,
6199 progress,
6200 stream,
6201 } => ExprKind::PGrepExpr {
6202 block,
6203 list: Box::new(lhs),
6204 progress,
6205 stream,
6206 },
6207 ExprKind::PForExpr {
6208 block,
6209 list: _,
6210 progress,
6211 } => ExprKind::PForExpr {
6212 block,
6213 list: Box::new(lhs),
6214 progress,
6215 },
6216 ExprKind::PSortExpr {
6217 cmp,
6218 list: _,
6219 progress,
6220 } => ExprKind::PSortExpr {
6221 cmp,
6222 list: Box::new(lhs),
6223 progress,
6224 },
6225 ExprKind::PReduceExpr {
6226 block,
6227 list: _,
6228 progress,
6229 } => ExprKind::PReduceExpr {
6230 block,
6231 list: Box::new(lhs),
6232 progress,
6233 },
6234 ExprKind::PcacheExpr {
6235 block,
6236 list: _,
6237 progress,
6238 } => ExprKind::PcacheExpr {
6239 block,
6240 list: Box::new(lhs),
6241 progress,
6242 },
6243 ExprKind::PReduceInitExpr {
6244 init,
6245 block,
6246 list: _,
6247 progress,
6248 } => ExprKind::PReduceInitExpr {
6249 init,
6250 block,
6251 list: Box::new(lhs),
6252 progress,
6253 },
6254 ExprKind::PMapReduceExpr {
6255 map_block,
6256 reduce_block,
6257 list: _,
6258 progress,
6259 } => ExprKind::PMapReduceExpr {
6260 map_block,
6261 reduce_block,
6262 list: Box::new(lhs),
6263 progress,
6264 },
6265
6266 ExprKind::Push { array, mut values } => {
6271 values.insert(0, lhs);
6272 ExprKind::Push { array, values }
6273 }
6274 ExprKind::Unshift { array, mut values } => {
6275 values.insert(0, lhs);
6276 ExprKind::Unshift { array, values }
6277 }
6278
6279 ExprKind::SplitExpr {
6281 pattern,
6282 string: _,
6283 limit,
6284 } => ExprKind::SplitExpr {
6285 pattern,
6286 string: Box::new(lhs),
6287 limit,
6288 },
6289
6290 ExprKind::Substitution {
6294 pattern,
6295 replacement,
6296 mut flags,
6297 expr: _,
6298 delim,
6299 } => {
6300 if !flags.contains('r') {
6301 flags.push('r');
6302 }
6303 ExprKind::Substitution {
6304 expr: Box::new(lhs),
6305 pattern,
6306 replacement,
6307 flags,
6308 delim,
6309 }
6310 }
6311 ExprKind::Transliterate {
6312 from,
6313 to,
6314 mut flags,
6315 expr: _,
6316 delim,
6317 } => {
6318 if !flags.contains('r') {
6319 flags.push('r');
6320 }
6321 ExprKind::Transliterate {
6322 expr: Box::new(lhs),
6323 from,
6324 to,
6325 flags,
6326 delim,
6327 }
6328 }
6329 ExprKind::Match {
6330 pattern,
6331 flags,
6332 scalar_g,
6333 expr: _,
6334 delim,
6335 } => ExprKind::Match {
6336 expr: Box::new(lhs),
6337 pattern,
6338 flags,
6339 scalar_g,
6340 delim,
6341 },
6342 ExprKind::Regex(pattern, flags) => ExprKind::Match {
6344 expr: Box::new(lhs),
6345 pattern,
6346 flags,
6347 scalar_g: false,
6348 delim: '/',
6349 },
6350
6351 ExprKind::Bareword(name) => match name.as_str() {
6353 "reverse" => {
6354 if !crate::compat_mode() {
6355 return Err(self.syntax_err(
6356 "stryke uses `rev` instead of `reverse` (this is not Perl 5)",
6357 line,
6358 ));
6359 }
6360 ExprKind::ReverseExpr(Box::new(lhs))
6361 }
6362 "rv" | "reversed" | "rev" => ExprKind::Rev(Box::new(lhs)),
6363 "uq" | "uniq" | "distinct" => ExprKind::FuncCall {
6364 name: "uniq".to_string(),
6365 args: vec![lhs],
6366 },
6367 "fl" | "flatten" => ExprKind::FuncCall {
6368 name: "flatten".to_string(),
6369 args: vec![lhs],
6370 },
6371 _ => ExprKind::FuncCall {
6372 name,
6373 args: vec![lhs],
6374 },
6375 },
6376
6377 kind @ (ExprKind::ScalarVar(_)
6379 | ExprKind::ArrayElement { .. }
6380 | ExprKind::HashElement { .. }
6381 | ExprKind::Deref { .. }
6382 | ExprKind::ArrowDeref { .. }
6383 | ExprKind::CodeRef { .. }
6384 | ExprKind::SubroutineRef(_)
6385 | ExprKind::SubroutineCodeRef(_)
6386 | ExprKind::DynamicSubCodeRef(_)) => ExprKind::IndirectCall {
6387 target: Box::new(Expr { kind, line: rline }),
6388 args: vec![lhs],
6389 ampersand: false,
6390 pass_caller_arglist: false,
6391 },
6392
6393 ExprKind::Do(inner) if matches!(inner.kind, ExprKind::CodeRef { .. }) => {
6397 ExprKind::IndirectCall {
6398 target: inner,
6399 args: vec![lhs],
6400 ampersand: false,
6401 pass_caller_arglist: false,
6402 }
6403 }
6404
6405 other => {
6406 return Err(self.syntax_err(
6407 format!(
6408 "right-hand side of `|>` must be a call, builtin, or coderef \
6409 expression (got {})",
6410 Self::expr_kind_name(&other)
6411 ),
6412 line,
6413 ));
6414 }
6415 };
6416 Ok(Expr {
6417 kind: new_kind,
6418 line,
6419 })
6420 }
6421
6422 fn expr_kind_name(kind: &ExprKind) -> &'static str {
6424 match kind {
6425 ExprKind::Integer(_) | ExprKind::Float(_) => "numeric literal",
6426 ExprKind::String(_) | ExprKind::InterpolatedString(_) => "string literal",
6427 ExprKind::BinOp { .. } => "binary expression",
6428 ExprKind::UnaryOp { .. } => "unary expression",
6429 ExprKind::Ternary { .. } => "ternary expression",
6430 ExprKind::Assign { .. } | ExprKind::CompoundAssign { .. } => "assignment",
6431 ExprKind::List(_) => "list expression",
6432 ExprKind::Range { .. } => "range expression",
6433 _ => "expression",
6434 }
6435 }
6436
6437 fn parse_or_word(&mut self) -> PerlResult<Expr> {
6439 let mut left = self.parse_and_word()?;
6440 while matches!(self.peek(), Token::LogOrWord) {
6441 let line = left.line;
6442 self.advance();
6443 let right = self.parse_and_word()?;
6444 left = Expr {
6445 kind: ExprKind::BinOp {
6446 left: Box::new(left),
6447 op: BinOp::LogOrWord,
6448 right: Box::new(right),
6449 },
6450 line,
6451 };
6452 }
6453 Ok(left)
6454 }
6455
6456 fn parse_and_word(&mut self) -> PerlResult<Expr> {
6457 let mut left = self.parse_not_word()?;
6458 while matches!(self.peek(), Token::LogAndWord) {
6459 let line = left.line;
6460 self.advance();
6461 let right = self.parse_not_word()?;
6462 left = Expr {
6463 kind: ExprKind::BinOp {
6464 left: Box::new(left),
6465 op: BinOp::LogAndWord,
6466 right: Box::new(right),
6467 },
6468 line,
6469 };
6470 }
6471 Ok(left)
6472 }
6473
6474 fn parse_not_word(&mut self) -> PerlResult<Expr> {
6475 if matches!(self.peek(), Token::LogNotWord) {
6476 let line = self.peek_line();
6477 self.advance();
6478 let expr = self.parse_not_word()?;
6479 return Ok(Expr {
6480 kind: ExprKind::UnaryOp {
6481 op: UnaryOp::LogNotWord,
6482 expr: Box::new(expr),
6483 },
6484 line,
6485 });
6486 }
6487 self.parse_range()
6488 }
6489
6490 fn parse_log_or(&mut self) -> PerlResult<Expr> {
6491 let mut left = self.parse_log_and()?;
6492 loop {
6493 let op = match self.peek() {
6494 Token::LogOr => BinOp::LogOr,
6495 Token::DefinedOr => BinOp::DefinedOr,
6496 _ => break,
6497 };
6498 let line = left.line;
6499 self.advance();
6500 let right = self.parse_log_and()?;
6501 left = Expr {
6502 kind: ExprKind::BinOp {
6503 left: Box::new(left),
6504 op,
6505 right: Box::new(right),
6506 },
6507 line,
6508 };
6509 }
6510 Ok(left)
6511 }
6512
6513 fn parse_log_and(&mut self) -> PerlResult<Expr> {
6514 let mut left = self.parse_bit_or()?;
6515 while matches!(self.peek(), Token::LogAnd) {
6516 let line = left.line;
6517 self.advance();
6518 let right = self.parse_bit_or()?;
6519 left = Expr {
6520 kind: ExprKind::BinOp {
6521 left: Box::new(left),
6522 op: BinOp::LogAnd,
6523 right: Box::new(right),
6524 },
6525 line,
6526 };
6527 }
6528 Ok(left)
6529 }
6530
6531 fn parse_bit_or(&mut self) -> PerlResult<Expr> {
6532 let mut left = self.parse_bit_xor()?;
6533 while matches!(self.peek(), Token::BitOr) {
6534 let line = left.line;
6535 self.advance();
6536 let right = self.parse_bit_xor()?;
6537 left = Expr {
6538 kind: ExprKind::BinOp {
6539 left: Box::new(left),
6540 op: BinOp::BitOr,
6541 right: Box::new(right),
6542 },
6543 line,
6544 };
6545 }
6546 Ok(left)
6547 }
6548
6549 fn parse_bit_xor(&mut self) -> PerlResult<Expr> {
6550 let mut left = self.parse_bit_and()?;
6551 while matches!(self.peek(), Token::BitXor) {
6552 let line = left.line;
6553 self.advance();
6554 let right = self.parse_bit_and()?;
6555 left = Expr {
6556 kind: ExprKind::BinOp {
6557 left: Box::new(left),
6558 op: BinOp::BitXor,
6559 right: Box::new(right),
6560 },
6561 line,
6562 };
6563 }
6564 Ok(left)
6565 }
6566
6567 fn parse_bit_and(&mut self) -> PerlResult<Expr> {
6568 let mut left = self.parse_equality()?;
6569 while matches!(self.peek(), Token::BitAnd) {
6570 let line = left.line;
6571 self.advance();
6572 let right = self.parse_equality()?;
6573 left = Expr {
6574 kind: ExprKind::BinOp {
6575 left: Box::new(left),
6576 op: BinOp::BitAnd,
6577 right: Box::new(right),
6578 },
6579 line,
6580 };
6581 }
6582 Ok(left)
6583 }
6584
6585 fn parse_equality(&mut self) -> PerlResult<Expr> {
6586 let mut left = self.parse_comparison()?;
6587 loop {
6588 let op = match self.peek() {
6589 Token::NumEq => BinOp::NumEq,
6590 Token::NumNe => BinOp::NumNe,
6591 Token::StrEq => BinOp::StrEq,
6592 Token::StrNe => BinOp::StrNe,
6593 Token::Spaceship => BinOp::Spaceship,
6594 Token::StrCmp => BinOp::StrCmp,
6595 _ => break,
6596 };
6597 let line = left.line;
6598 self.advance();
6599 let right = self.parse_comparison()?;
6600 left = Expr {
6601 kind: ExprKind::BinOp {
6602 left: Box::new(left),
6603 op,
6604 right: Box::new(right),
6605 },
6606 line,
6607 };
6608 }
6609 Ok(left)
6610 }
6611
6612 fn parse_comparison(&mut self) -> PerlResult<Expr> {
6613 let left = self.parse_shift()?;
6614 let first_op = match self.peek() {
6615 Token::NumLt => BinOp::NumLt,
6616 Token::NumGt => BinOp::NumGt,
6617 Token::NumLe => BinOp::NumLe,
6618 Token::NumGe => BinOp::NumGe,
6619 Token::StrLt => BinOp::StrLt,
6620 Token::StrGt => BinOp::StrGt,
6621 Token::StrLe => BinOp::StrLe,
6622 Token::StrGe => BinOp::StrGe,
6623 _ => return Ok(left),
6624 };
6625 let line = left.line;
6626 self.advance();
6627 let middle = self.parse_shift()?;
6628
6629 let second_op = match self.peek() {
6630 Token::NumLt => Some(BinOp::NumLt),
6631 Token::NumGt => Some(BinOp::NumGt),
6632 Token::NumLe => Some(BinOp::NumLe),
6633 Token::NumGe => Some(BinOp::NumGe),
6634 Token::StrLt => Some(BinOp::StrLt),
6635 Token::StrGt => Some(BinOp::StrGt),
6636 Token::StrLe => Some(BinOp::StrLe),
6637 Token::StrGe => Some(BinOp::StrGe),
6638 _ => None,
6639 };
6640
6641 if second_op.is_none() {
6642 return Ok(Expr {
6643 kind: ExprKind::BinOp {
6644 left: Box::new(left),
6645 op: first_op,
6646 right: Box::new(middle),
6647 },
6648 line,
6649 });
6650 }
6651
6652 let mut operands = vec![left, middle];
6655 let mut ops = vec![first_op];
6656
6657 loop {
6658 let op = match self.peek() {
6659 Token::NumLt => BinOp::NumLt,
6660 Token::NumGt => BinOp::NumGt,
6661 Token::NumLe => BinOp::NumLe,
6662 Token::NumGe => BinOp::NumGe,
6663 Token::StrLt => BinOp::StrLt,
6664 Token::StrGt => BinOp::StrGt,
6665 Token::StrLe => BinOp::StrLe,
6666 Token::StrGe => BinOp::StrGe,
6667 _ => break,
6668 };
6669 self.advance();
6670 ops.push(op);
6671 operands.push(self.parse_shift()?);
6672 }
6673
6674 let mut result = Expr {
6676 kind: ExprKind::BinOp {
6677 left: Box::new(operands[0].clone()),
6678 op: ops[0],
6679 right: Box::new(operands[1].clone()),
6680 },
6681 line,
6682 };
6683
6684 for i in 1..ops.len() {
6685 let cmp = Expr {
6686 kind: ExprKind::BinOp {
6687 left: Box::new(operands[i].clone()),
6688 op: ops[i],
6689 right: Box::new(operands[i + 1].clone()),
6690 },
6691 line,
6692 };
6693 result = Expr {
6694 kind: ExprKind::BinOp {
6695 left: Box::new(result),
6696 op: BinOp::LogAnd,
6697 right: Box::new(cmp),
6698 },
6699 line,
6700 };
6701 }
6702
6703 Ok(result)
6704 }
6705
6706 fn parse_shift(&mut self) -> PerlResult<Expr> {
6707 let mut left = self.parse_addition()?;
6708 loop {
6709 let op = match self.peek() {
6710 Token::ShiftLeft => BinOp::ShiftLeft,
6711 Token::ShiftRight => BinOp::ShiftRight,
6712 _ => break,
6713 };
6714 let line = left.line;
6715 self.advance();
6716 let right = self.parse_addition()?;
6717 left = Expr {
6718 kind: ExprKind::BinOp {
6719 left: Box::new(left),
6720 op,
6721 right: Box::new(right),
6722 },
6723 line,
6724 };
6725 }
6726 Ok(left)
6727 }
6728
6729 fn parse_addition(&mut self) -> PerlResult<Expr> {
6730 let mut left = self.parse_multiplication()?;
6731 loop {
6732 let op = match self.peek() {
6735 Token::Plus if self.peek_line() == self.prev_line() => BinOp::Add,
6736 Token::Minus if self.peek_line() == self.prev_line() => BinOp::Sub,
6737 Token::Dot => BinOp::Concat,
6738 _ => break,
6739 };
6740 let line = left.line;
6741 self.advance();
6742 let right = self.parse_multiplication()?;
6743 left = Expr {
6744 kind: ExprKind::BinOp {
6745 left: Box::new(left),
6746 op,
6747 right: Box::new(right),
6748 },
6749 line,
6750 };
6751 }
6752 Ok(left)
6753 }
6754
6755 fn parse_multiplication(&mut self) -> PerlResult<Expr> {
6756 let mut left = self.parse_regex_bind()?;
6757 loop {
6758 let op = match self.peek() {
6759 Token::Star => BinOp::Mul,
6760 Token::Slash if self.suppress_slash_as_div == 0 => BinOp::Div,
6761 Token::Percent if self.peek_line() == self.prev_line() => BinOp::Mod,
6764 Token::X => {
6765 let line = left.line;
6766 self.advance();
6767 let right = self.parse_regex_bind()?;
6768 left = Expr {
6769 kind: ExprKind::Repeat {
6770 expr: Box::new(left),
6771 count: Box::new(right),
6772 },
6773 line,
6774 };
6775 continue;
6776 }
6777 _ => break,
6778 };
6779 let line = left.line;
6780 self.advance();
6781 let right = self.parse_regex_bind()?;
6782 left = Expr {
6783 kind: ExprKind::BinOp {
6784 left: Box::new(left),
6785 op,
6786 right: Box::new(right),
6787 },
6788 line,
6789 };
6790 }
6791 Ok(left)
6792 }
6793
6794 fn parse_regex_bind(&mut self) -> PerlResult<Expr> {
6795 let left = self.parse_unary()?;
6796 match self.peek() {
6797 Token::BindMatch => {
6798 let line = left.line;
6799 self.advance();
6800 match self.peek().clone() {
6801 Token::Regex(pattern, flags, delim) => {
6802 self.advance();
6803 Ok(Expr {
6804 kind: ExprKind::Match {
6805 expr: Box::new(left),
6806 pattern,
6807 flags,
6808 scalar_g: false,
6809 delim,
6810 },
6811 line,
6812 })
6813 }
6814 Token::Ident(ref s) if s.starts_with('\x00') => {
6815 let (Token::Ident(encoded), _) = self.advance() else {
6816 unreachable!()
6817 };
6818 let parts: Vec<&str> = encoded.split('\x00').collect();
6819 if parts.len() >= 4 && parts[1] == "s" {
6820 let delim = parts.get(5).and_then(|s| s.chars().next()).unwrap_or('/');
6821 Ok(Expr {
6822 kind: ExprKind::Substitution {
6823 expr: Box::new(left),
6824 pattern: parts[2].to_string(),
6825 replacement: parts[3].to_string(),
6826 flags: parts.get(4).unwrap_or(&"").to_string(),
6827 delim,
6828 },
6829 line,
6830 })
6831 } else if parts.len() >= 4 && parts[1] == "tr" {
6832 let delim = parts.get(5).and_then(|s| s.chars().next()).unwrap_or('/');
6833 Ok(Expr {
6834 kind: ExprKind::Transliterate {
6835 expr: Box::new(left),
6836 from: parts[2].to_string(),
6837 to: parts[3].to_string(),
6838 flags: parts.get(4).unwrap_or(&"").to_string(),
6839 delim,
6840 },
6841 line,
6842 })
6843 } else {
6844 Err(self.syntax_err("Invalid regex binding", line))
6845 }
6846 }
6847 _ => {
6848 let rhs = self.parse_unary()?;
6849 Ok(Expr {
6850 kind: ExprKind::BinOp {
6851 left: Box::new(left),
6852 op: BinOp::BindMatch,
6853 right: Box::new(rhs),
6854 },
6855 line,
6856 })
6857 }
6858 }
6859 }
6860 Token::BindNotMatch => {
6861 let line = left.line;
6862 self.advance();
6863 match self.peek().clone() {
6864 Token::Regex(pattern, flags, delim) => {
6865 self.advance();
6866 Ok(Expr {
6867 kind: ExprKind::UnaryOp {
6868 op: UnaryOp::LogNot,
6869 expr: Box::new(Expr {
6870 kind: ExprKind::Match {
6871 expr: Box::new(left),
6872 pattern,
6873 flags,
6874 scalar_g: false,
6875 delim,
6876 },
6877 line,
6878 }),
6879 },
6880 line,
6881 })
6882 }
6883 Token::Ident(ref s) if s.starts_with('\x00') => {
6884 let (Token::Ident(encoded), _) = self.advance() else {
6885 unreachable!()
6886 };
6887 let parts: Vec<&str> = encoded.split('\x00').collect();
6888 if parts.len() >= 4 && parts[1] == "s" {
6889 let delim = parts.get(5).and_then(|s| s.chars().next()).unwrap_or('/');
6890 Ok(Expr {
6891 kind: ExprKind::UnaryOp {
6892 op: UnaryOp::LogNot,
6893 expr: Box::new(Expr {
6894 kind: ExprKind::Substitution {
6895 expr: Box::new(left),
6896 pattern: parts[2].to_string(),
6897 replacement: parts[3].to_string(),
6898 flags: parts.get(4).unwrap_or(&"").to_string(),
6899 delim,
6900 },
6901 line,
6902 }),
6903 },
6904 line,
6905 })
6906 } else if parts.len() >= 4 && parts[1] == "tr" {
6907 let delim = parts.get(5).and_then(|s| s.chars().next()).unwrap_or('/');
6908 Ok(Expr {
6909 kind: ExprKind::UnaryOp {
6910 op: UnaryOp::LogNot,
6911 expr: Box::new(Expr {
6912 kind: ExprKind::Transliterate {
6913 expr: Box::new(left),
6914 from: parts[2].to_string(),
6915 to: parts[3].to_string(),
6916 flags: parts.get(4).unwrap_or(&"").to_string(),
6917 delim,
6918 },
6919 line,
6920 }),
6921 },
6922 line,
6923 })
6924 } else {
6925 Err(self.syntax_err("Invalid regex binding after !~", line))
6926 }
6927 }
6928 _ => {
6929 let rhs = self.parse_unary()?;
6930 Ok(Expr {
6931 kind: ExprKind::BinOp {
6932 left: Box::new(left),
6933 op: BinOp::BindNotMatch,
6934 right: Box::new(rhs),
6935 },
6936 line,
6937 })
6938 }
6939 }
6940 }
6941 _ => Ok(left),
6942 }
6943 }
6944
6945 fn parse_thread_input(&mut self) -> PerlResult<Expr> {
6948 self.suppress_slash_as_div = self.suppress_slash_as_div.saturating_add(1);
6949 let result = self.parse_range();
6950 self.suppress_slash_as_div = self.suppress_slash_as_div.saturating_sub(1);
6951 result
6952 }
6953
6954 fn parse_range(&mut self) -> PerlResult<Expr> {
6961 let left = self.parse_log_or()?;
6962 let line = left.line;
6963 let (exclusive, _colon_style) = if self.eat(&Token::RangeExclusive) {
6965 (true, false)
6966 } else if self.eat(&Token::Range) {
6967 (false, false)
6968 } else if self.suppress_colon_range == 0 && self.eat(&Token::Colon) {
6969 (false, true)
6972 } else {
6973 return Ok(left);
6974 };
6975 let right = self.parse_log_or()?;
6976 let step = if self.eat(&Token::Colon) {
6978 Some(Box::new(self.parse_unary()?))
6979 } else {
6980 None
6981 };
6982 Ok(Expr {
6983 kind: ExprKind::Range {
6984 from: Box::new(left),
6985 to: Box::new(right),
6986 exclusive,
6987 step,
6988 },
6989 line,
6990 })
6991 }
6992
6993 fn parse_package_qualified_identifier(&mut self) -> PerlResult<String> {
6995 let mut name = match self.advance() {
6996 (Token::Ident(n), _) => n,
6997 (tok, l) => {
6998 return Err(self.syntax_err(format!("Expected identifier, got {:?}", tok), l));
6999 }
7000 };
7001 while self.eat(&Token::PackageSep) {
7002 match self.advance() {
7003 (Token::Ident(part), _) => {
7004 name.push_str("::");
7005 name.push_str(&part);
7006 }
7007 (tok, l) => {
7008 return Err(self
7009 .syntax_err(format!("Expected identifier after `::`, got {:?}", tok), l));
7010 }
7011 }
7012 }
7013 Ok(name)
7014 }
7015
7016 fn parse_qualified_subroutine_name(&mut self) -> PerlResult<String> {
7018 self.parse_package_qualified_identifier()
7019 }
7020
7021 fn parse_unary(&mut self) -> PerlResult<Expr> {
7022 let line = self.peek_line();
7023 match self.peek().clone() {
7024 Token::Minus => {
7025 self.advance();
7026 let expr = self.parse_power()?;
7027 Ok(Expr {
7028 kind: ExprKind::UnaryOp {
7029 op: UnaryOp::Negate,
7030 expr: Box::new(expr),
7031 },
7032 line,
7033 })
7034 }
7035 Token::Plus => {
7038 self.advance();
7039 self.parse_unary()
7040 }
7041 Token::LogNot => {
7042 self.advance();
7043 let expr = self.parse_unary()?;
7044 Ok(Expr {
7045 kind: ExprKind::UnaryOp {
7046 op: UnaryOp::LogNot,
7047 expr: Box::new(expr),
7048 },
7049 line,
7050 })
7051 }
7052 Token::BitNot => {
7053 self.advance();
7054 let expr = self.parse_unary()?;
7055 Ok(Expr {
7056 kind: ExprKind::UnaryOp {
7057 op: UnaryOp::BitNot,
7058 expr: Box::new(expr),
7059 },
7060 line,
7061 })
7062 }
7063 Token::Increment => {
7064 self.advance();
7065 let expr = self.parse_postfix()?;
7066 Ok(Expr {
7067 kind: ExprKind::UnaryOp {
7068 op: UnaryOp::PreIncrement,
7069 expr: Box::new(expr),
7070 },
7071 line,
7072 })
7073 }
7074 Token::Decrement => {
7075 self.advance();
7076 let expr = self.parse_postfix()?;
7077 Ok(Expr {
7078 kind: ExprKind::UnaryOp {
7079 op: UnaryOp::PreDecrement,
7080 expr: Box::new(expr),
7081 },
7082 line,
7083 })
7084 }
7085 Token::BitAnd => {
7086 self.advance();
7089 if matches!(self.peek(), Token::LBrace) {
7090 self.advance();
7091 let inner = self.parse_expression()?;
7092 self.expect(&Token::RBrace)?;
7093 return Ok(Expr {
7094 kind: ExprKind::DynamicSubCodeRef(Box::new(inner)),
7095 line,
7096 });
7097 }
7098 if matches!(self.peek(), Token::Ident(_)) {
7099 let name = self.parse_qualified_subroutine_name()?;
7100 return Ok(Expr {
7101 kind: ExprKind::SubroutineRef(name),
7102 line,
7103 });
7104 }
7105 let target = self.parse_primary()?;
7106 if matches!(self.peek(), Token::LParen) {
7107 self.advance();
7108 let args = self.parse_arg_list()?;
7109 self.expect(&Token::RParen)?;
7110 return Ok(Expr {
7111 kind: ExprKind::IndirectCall {
7112 target: Box::new(target),
7113 args,
7114 ampersand: true,
7115 pass_caller_arglist: false,
7116 },
7117 line,
7118 });
7119 }
7120 Ok(Expr {
7122 kind: ExprKind::IndirectCall {
7123 target: Box::new(target),
7124 args: vec![],
7125 ampersand: true,
7126 pass_caller_arglist: true,
7127 },
7128 line,
7129 })
7130 }
7131 Token::Backslash => {
7132 self.advance();
7133 let expr = self.parse_unary()?;
7134 if let ExprKind::SubroutineRef(name) = expr.kind {
7135 return Ok(Expr {
7136 kind: ExprKind::SubroutineCodeRef(name),
7137 line,
7138 });
7139 }
7140 if matches!(expr.kind, ExprKind::DynamicSubCodeRef(_)) {
7141 return Ok(expr);
7142 }
7143 Ok(Expr {
7145 kind: ExprKind::ScalarRef(Box::new(expr)),
7146 line,
7147 })
7148 }
7149 Token::FileTest(op) => {
7150 self.advance();
7151 let expr = if Self::filetest_allows_implicit_topic(self.peek()) {
7153 Expr {
7154 kind: ExprKind::ScalarVar("_".into()),
7155 line: self.peek_line(),
7156 }
7157 } else {
7158 self.parse_unary()?
7159 };
7160 Ok(Expr {
7161 kind: ExprKind::FileTest {
7162 op,
7163 expr: Box::new(expr),
7164 },
7165 line,
7166 })
7167 }
7168 _ => self.parse_power(),
7169 }
7170 }
7171
7172 fn parse_power(&mut self) -> PerlResult<Expr> {
7173 let left = self.parse_postfix()?;
7174 if matches!(self.peek(), Token::Power) {
7175 let line = left.line;
7176 self.advance();
7177 let right = self.parse_unary()?; return Ok(Expr {
7179 kind: ExprKind::BinOp {
7180 left: Box::new(left),
7181 op: BinOp::Pow,
7182 right: Box::new(right),
7183 },
7184 line,
7185 });
7186 }
7187 Ok(left)
7188 }
7189
7190 fn parse_postfix(&mut self) -> PerlResult<Expr> {
7191 let mut expr = self.parse_primary()?;
7192 loop {
7193 match self.peek().clone() {
7194 Token::Increment => {
7195 if self.peek_line() > self.prev_line() {
7198 break;
7199 }
7200 let line = expr.line;
7201 self.advance();
7202 expr = Expr {
7203 kind: ExprKind::PostfixOp {
7204 expr: Box::new(expr),
7205 op: PostfixOp::Increment,
7206 },
7207 line,
7208 };
7209 }
7210 Token::Decrement => {
7211 if self.peek_line() > self.prev_line() {
7214 break;
7215 }
7216 let line = expr.line;
7217 self.advance();
7218 expr = Expr {
7219 kind: ExprKind::PostfixOp {
7220 expr: Box::new(expr),
7221 op: PostfixOp::Decrement,
7222 },
7223 line,
7224 };
7225 }
7226 Token::LParen => {
7227 if self.suppress_indirect_paren_call > 0 {
7228 break;
7229 }
7230 if self.peek_line() > self.prev_line() {
7234 break;
7235 }
7236 let line = expr.line;
7237 self.advance();
7238 let args = self.parse_arg_list()?;
7239 self.expect(&Token::RParen)?;
7240 expr = Expr {
7241 kind: ExprKind::IndirectCall {
7242 target: Box::new(expr),
7243 args,
7244 ampersand: false,
7245 pass_caller_arglist: false,
7246 },
7247 line,
7248 };
7249 }
7250 Token::Arrow => {
7251 let line = expr.line;
7252 self.advance();
7253 match self.peek().clone() {
7254 Token::LBracket => {
7255 self.advance();
7256 let index = self.parse_expression()?;
7257 self.expect(&Token::RBracket)?;
7258 expr = Expr {
7259 kind: ExprKind::ArrowDeref {
7260 expr: Box::new(expr),
7261 index: Box::new(index),
7262 kind: DerefKind::Array,
7263 },
7264 line,
7265 };
7266 }
7267 Token::LBrace => {
7268 self.advance();
7269 let key = self.parse_hash_subscript_key()?;
7270 self.expect(&Token::RBrace)?;
7271 expr = Expr {
7272 kind: ExprKind::ArrowDeref {
7273 expr: Box::new(expr),
7274 index: Box::new(key),
7275 kind: DerefKind::Hash,
7276 },
7277 line,
7278 };
7279 }
7280 Token::LParen => {
7281 self.advance();
7282 let args = self.parse_arg_list()?;
7283 self.expect(&Token::RParen)?;
7284 expr = Expr {
7285 kind: ExprKind::ArrowDeref {
7286 expr: Box::new(expr),
7287 index: Box::new(Expr {
7288 kind: ExprKind::List(args),
7289 line,
7290 }),
7291 kind: DerefKind::Call,
7292 },
7293 line,
7294 };
7295 }
7296 Token::Ident(method) => {
7297 self.advance();
7298 if method == "SUPER" {
7299 self.expect(&Token::PackageSep)?;
7300 let real_method = match self.advance() {
7301 (Token::Ident(n), _) => n,
7302 (tok, l) => {
7303 return Err(self.syntax_err(
7304 format!(
7305 "Expected method name after SUPER::, got {:?}",
7306 tok
7307 ),
7308 l,
7309 ));
7310 }
7311 };
7312 let args = if self.eat(&Token::LParen) {
7313 let a = self.parse_arg_list()?;
7314 self.expect(&Token::RParen)?;
7315 a
7316 } else {
7317 self.parse_method_arg_list_no_paren()?
7318 };
7319 expr = Expr {
7320 kind: ExprKind::MethodCall {
7321 object: Box::new(expr),
7322 method: real_method,
7323 args,
7324 super_call: true,
7325 },
7326 line,
7327 };
7328 } else {
7329 let mut method_name = method;
7330 while self.eat(&Token::PackageSep) {
7331 match self.advance() {
7332 (Token::Ident(part), _) => {
7333 method_name.push_str("::");
7334 method_name.push_str(&part);
7335 }
7336 (tok, l) => {
7337 return Err(self.syntax_err(
7338 format!(
7339 "Expected identifier after :: in method name, got {:?}",
7340 tok
7341 ),
7342 l,
7343 ));
7344 }
7345 }
7346 }
7347 let args = if self.eat(&Token::LParen) {
7348 let a = self.parse_arg_list()?;
7349 self.expect(&Token::RParen)?;
7350 a
7351 } else {
7352 self.parse_method_arg_list_no_paren()?
7353 };
7354 expr = Expr {
7355 kind: ExprKind::MethodCall {
7356 object: Box::new(expr),
7357 method: method_name,
7358 args,
7359 super_call: false,
7360 },
7361 line,
7362 };
7363 }
7364 }
7365 Token::ArrayAt => {
7371 self.advance(); match self.peek().clone() {
7373 Token::Star => {
7374 self.advance();
7375 expr = Expr {
7376 kind: ExprKind::Deref {
7377 expr: Box::new(expr),
7378 kind: Sigil::Array,
7379 },
7380 line,
7381 };
7382 }
7383 Token::LBracket => {
7384 self.advance();
7385 let mut indices = Vec::new();
7386 while !matches!(self.peek(), Token::RBracket | Token::Eof) {
7387 indices.push(self.parse_assign_expr()?);
7388 if !self.eat(&Token::Comma) {
7389 break;
7390 }
7391 }
7392 self.expect(&Token::RBracket)?;
7393 let source = Expr {
7394 kind: ExprKind::Deref {
7395 expr: Box::new(expr),
7396 kind: Sigil::Array,
7397 },
7398 line,
7399 };
7400 expr = Expr {
7401 kind: ExprKind::AnonymousListSlice {
7402 source: Box::new(source),
7403 indices,
7404 },
7405 line,
7406 };
7407 }
7408 Token::LBrace => {
7409 self.advance();
7410 let mut keys = Vec::new();
7411 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
7412 keys.push(self.parse_assign_expr()?);
7413 if !self.eat(&Token::Comma) {
7414 break;
7415 }
7416 }
7417 self.expect(&Token::RBrace)?;
7418 expr = Expr {
7419 kind: ExprKind::HashSliceDeref {
7420 container: Box::new(expr),
7421 keys,
7422 },
7423 line,
7424 };
7425 }
7426 tok => {
7427 return Err(self.syntax_err(
7428 format!(
7429 "Expected `*`, `[…]`, or `{{…}}` after `->@`, got {:?}",
7430 tok
7431 ),
7432 line,
7433 ));
7434 }
7435 }
7436 }
7437 Token::HashPercent => {
7438 self.advance(); match self.peek().clone() {
7440 Token::Star => {
7441 self.advance();
7442 expr = Expr {
7443 kind: ExprKind::Deref {
7444 expr: Box::new(expr),
7445 kind: Sigil::Hash,
7446 },
7447 line,
7448 };
7449 }
7450 tok => {
7451 return Err(self.syntax_err(
7452 format!("Expected `*` after `->%`, got {:?}", tok),
7453 line,
7454 ));
7455 }
7456 }
7457 }
7458 Token::X => {
7460 self.advance();
7461 let args = if self.eat(&Token::LParen) {
7462 let a = self.parse_arg_list()?;
7463 self.expect(&Token::RParen)?;
7464 a
7465 } else {
7466 self.parse_method_arg_list_no_paren()?
7467 };
7468 expr = Expr {
7469 kind: ExprKind::MethodCall {
7470 object: Box::new(expr),
7471 method: "x".to_string(),
7472 args,
7473 super_call: false,
7474 },
7475 line,
7476 };
7477 }
7478 _ => break,
7479 }
7480 }
7481 Token::LBracket => {
7482 let line = expr.line;
7484 if matches!(expr.kind, ExprKind::ScalarVar(_)) {
7485 if let ExprKind::ScalarVar(ref name) = expr.kind {
7486 let name = name.clone();
7487 self.advance();
7488 let index = self.parse_expression()?;
7489 self.expect(&Token::RBracket)?;
7490 expr = Expr {
7491 kind: ExprKind::ArrayElement {
7492 array: name,
7493 index: Box::new(index),
7494 },
7495 line,
7496 };
7497 }
7498 } else if postfix_lbracket_is_arrow_container(&expr) {
7499 self.advance();
7500 let indices = self.parse_arg_list()?;
7501 self.expect(&Token::RBracket)?;
7502 expr = Expr {
7503 kind: ExprKind::ArrowDeref {
7504 expr: Box::new(expr),
7505 index: Box::new(Expr {
7506 kind: ExprKind::List(indices),
7507 line,
7508 }),
7509 kind: DerefKind::Array,
7510 },
7511 line,
7512 };
7513 } else {
7514 self.advance();
7515 let indices = self.parse_arg_list()?;
7516 self.expect(&Token::RBracket)?;
7517 expr = Expr {
7518 kind: ExprKind::AnonymousListSlice {
7519 source: Box::new(expr),
7520 indices,
7521 },
7522 line,
7523 };
7524 }
7525 }
7526 Token::LBrace => {
7527 if self.suppress_scalar_hash_brace > 0 {
7528 break;
7529 }
7530 if self.peek_line() > self.prev_line() {
7533 break;
7534 }
7535 let line = expr.line;
7538 let is_scalar_named_hash = matches!(expr.kind, ExprKind::ScalarVar(_));
7539 let is_chainable_hash_subscript = is_scalar_named_hash
7540 || matches!(
7541 expr.kind,
7542 ExprKind::HashElement { .. }
7543 | ExprKind::ArrayElement { .. }
7544 | ExprKind::ArrowDeref { .. }
7545 | ExprKind::Deref {
7546 kind: Sigil::Scalar,
7547 ..
7548 }
7549 );
7550 if !is_chainable_hash_subscript {
7551 break;
7552 }
7553 self.advance();
7554 let key = self.parse_hash_subscript_key()?;
7555 self.expect(&Token::RBrace)?;
7556 expr = if is_scalar_named_hash {
7557 if let ExprKind::ScalarVar(ref name) = expr.kind {
7558 let name = name.clone();
7559 if name == "_" {
7561 Expr {
7562 kind: ExprKind::ArrowDeref {
7563 expr: Box::new(Expr {
7564 kind: ExprKind::ScalarVar("_".into()),
7565 line,
7566 }),
7567 index: Box::new(key),
7568 kind: DerefKind::Hash,
7569 },
7570 line,
7571 }
7572 } else {
7573 Expr {
7574 kind: ExprKind::HashElement {
7575 hash: name,
7576 key: Box::new(key),
7577 },
7578 line,
7579 }
7580 }
7581 } else {
7582 unreachable!("is_scalar_named_hash implies ScalarVar");
7583 }
7584 } else {
7585 Expr {
7586 kind: ExprKind::ArrowDeref {
7587 expr: Box::new(expr),
7588 index: Box::new(key),
7589 kind: DerefKind::Hash,
7590 },
7591 line,
7592 }
7593 };
7594 }
7595 _ => break,
7596 }
7597 }
7598 Ok(expr)
7599 }
7600
7601 fn parse_primary(&mut self) -> PerlResult<Expr> {
7602 let line = self.peek_line();
7603 if let Token::Ident(ref kw) = self.peek().clone() {
7608 if matches!(kw.as_str(), "my" | "our" | "state" | "local") {
7609 let kw_owned = kw.clone();
7610 let saved_pos = self.pos;
7615 let stmt = self.parse_my_our_local(&kw_owned, false)?;
7616 let decls = match stmt.kind {
7617 StmtKind::My(d)
7618 | StmtKind::Our(d)
7619 | StmtKind::State(d)
7620 | StmtKind::Local(d) => d,
7621 _ => {
7622 self.pos = saved_pos;
7627 return Err(self.syntax_err(
7628 "`my`/`our`/`local` in expression must declare variables",
7629 line,
7630 ));
7631 }
7632 };
7633 return Ok(Expr {
7634 kind: ExprKind::MyExpr {
7635 keyword: kw_owned,
7636 decls,
7637 },
7638 line,
7639 });
7640 }
7641 }
7642 match self.peek().clone() {
7643 Token::Integer(n) => {
7644 self.advance();
7645 Ok(Expr {
7646 kind: ExprKind::Integer(n),
7647 line,
7648 })
7649 }
7650 Token::Float(f) => {
7651 self.advance();
7652 Ok(Expr {
7653 kind: ExprKind::Float(f),
7654 line,
7655 })
7656 }
7657 Token::ArrowBrace => {
7663 self.advance();
7664 let mut stmts = Vec::new();
7665 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
7666 if self.eat(&Token::Semicolon) {
7667 continue;
7668 }
7669 stmts.push(self.parse_statement()?);
7670 }
7671 self.expect(&Token::RBrace)?;
7672 let inner_line = stmts.first().map(|s| s.line).unwrap_or(line);
7673 let inner = Expr {
7674 kind: ExprKind::CodeRef {
7675 params: vec![],
7676 body: stmts,
7677 },
7678 line: inner_line,
7679 };
7680 Ok(Expr {
7681 kind: ExprKind::Do(Box::new(inner)),
7682 line,
7683 })
7684 }
7685 Token::Star => {
7686 self.advance();
7687 if matches!(self.peek(), Token::LBrace) {
7688 self.advance();
7689 let inner = self.parse_expression()?;
7690 self.expect(&Token::RBrace)?;
7691 return Ok(Expr {
7692 kind: ExprKind::Deref {
7693 expr: Box::new(inner),
7694 kind: Sigil::Typeglob,
7695 },
7696 line,
7697 });
7698 }
7699 if matches!(
7701 self.peek(),
7702 Token::ScalarVar(_)
7703 | Token::ArrayVar(_)
7704 | Token::HashVar(_)
7705 | Token::DerefScalarVar(_)
7706 | Token::HashPercent
7707 ) {
7708 let inner = self.parse_postfix()?;
7709 return Ok(Expr {
7710 kind: ExprKind::TypeglobExpr(Box::new(inner)),
7711 line,
7712 });
7713 }
7714 let mut full_name = match self.advance() {
7716 (Token::Ident(n), _) => n,
7717 (Token::X, _) => "x".to_string(),
7718 (tok, l) => {
7719 return Err(self
7720 .syntax_err(format!("Expected identifier after *, got {:?}", tok), l));
7721 }
7722 };
7723 while self.eat(&Token::PackageSep) {
7724 match self.advance() {
7725 (Token::Ident(part), _) => {
7726 full_name = format!("{}::{}", full_name, part);
7727 }
7728 (Token::X, _) => {
7729 full_name = format!("{}::x", full_name);
7730 }
7731 (tok, l) => {
7732 return Err(self.syntax_err(
7733 format!("Expected identifier after :: in typeglob, got {:?}", tok),
7734 l,
7735 ));
7736 }
7737 }
7738 }
7739 Ok(Expr {
7740 kind: ExprKind::Typeglob(full_name),
7741 line,
7742 })
7743 }
7744 Token::SingleString(s) => {
7745 self.advance();
7746 Ok(Expr {
7747 kind: ExprKind::String(s),
7748 line,
7749 })
7750 }
7751 Token::DoubleString(s) => {
7752 self.advance();
7753 self.parse_interpolated_string(&s, line)
7754 }
7755 Token::BacktickString(s) => {
7756 self.advance();
7757 let inner = self.parse_interpolated_string(&s, line)?;
7758 Ok(Expr {
7759 kind: ExprKind::Qx(Box::new(inner)),
7760 line,
7761 })
7762 }
7763 Token::HereDoc(_, body, interpolate) => {
7764 self.advance();
7765 if interpolate {
7766 self.parse_interpolated_string(&body, line)
7767 } else {
7768 Ok(Expr {
7769 kind: ExprKind::String(body),
7770 line,
7771 })
7772 }
7773 }
7774 Token::Regex(pattern, flags, _delim) => {
7775 self.advance();
7776 Ok(Expr {
7777 kind: ExprKind::Regex(pattern, flags),
7778 line,
7779 })
7780 }
7781 Token::QW(words) => {
7782 self.advance();
7783 Ok(Expr {
7784 kind: ExprKind::QW(words),
7785 line,
7786 })
7787 }
7788 Token::DerefScalarVar(name) => {
7789 self.advance();
7790 Ok(Expr {
7791 kind: ExprKind::Deref {
7792 expr: Box::new(Expr {
7793 kind: ExprKind::ScalarVar(name),
7794 line,
7795 }),
7796 kind: Sigil::Scalar,
7797 },
7798 line,
7799 })
7800 }
7801 Token::ScalarVar(name) => {
7802 self.advance();
7803 Ok(Expr {
7804 kind: ExprKind::ScalarVar(name),
7805 line,
7806 })
7807 }
7808 Token::ArrayVar(name) => {
7809 self.advance();
7810 match self.peek() {
7812 Token::LBracket => {
7813 self.advance();
7814 let indices = self.parse_arg_list()?;
7815 self.expect(&Token::RBracket)?;
7816 Ok(Expr {
7817 kind: ExprKind::ArraySlice {
7818 array: name,
7819 indices,
7820 },
7821 line,
7822 })
7823 }
7824 Token::LBrace if self.suppress_scalar_hash_brace == 0 => {
7825 self.advance();
7826 let keys = self.parse_arg_list()?;
7827 self.expect(&Token::RBrace)?;
7828 Ok(Expr {
7829 kind: ExprKind::HashSlice { hash: name, keys },
7830 line,
7831 })
7832 }
7833 _ => Ok(Expr {
7834 kind: ExprKind::ArrayVar(name),
7835 line,
7836 }),
7837 }
7838 }
7839 Token::HashVar(name) => {
7840 self.advance();
7841 Ok(Expr {
7842 kind: ExprKind::HashVar(name),
7843 line,
7844 })
7845 }
7846 Token::HashPercent => {
7847 self.advance();
7849 if matches!(self.peek(), Token::ScalarVar(_)) {
7850 let n = match self.advance() {
7851 (Token::ScalarVar(n), _) => n,
7852 (tok, l) => {
7853 return Err(self.syntax_err(
7854 format!("Expected scalar variable after %%, got {:?}", tok),
7855 l,
7856 ));
7857 }
7858 };
7859 return Ok(Expr {
7860 kind: ExprKind::Deref {
7861 expr: Box::new(Expr {
7862 kind: ExprKind::ScalarVar(n),
7863 line,
7864 }),
7865 kind: Sigil::Hash,
7866 },
7867 line,
7868 });
7869 }
7870 if matches!(self.peek(), Token::LBracket) {
7875 self.advance();
7876 let pairs = self.parse_hashref_pairs_until(&Token::RBracket)?;
7877 self.expect(&Token::RBracket)?;
7878 let href = Expr {
7879 kind: ExprKind::HashRef(pairs),
7880 line,
7881 };
7882 return Ok(Expr {
7883 kind: ExprKind::Deref {
7884 expr: Box::new(href),
7885 kind: Sigil::Hash,
7886 },
7887 line,
7888 });
7889 }
7890 self.expect(&Token::LBrace)?;
7891 let looks_like_pair = matches!(
7897 self.peek(),
7898 Token::Ident(_) | Token::SingleString(_) | Token::DoubleString(_)
7899 ) && matches!(self.peek_at(1), Token::FatArrow);
7900 let inner = if looks_like_pair {
7901 let pairs = self.parse_hashref_pairs_until(&Token::RBrace)?;
7902 Expr {
7903 kind: ExprKind::HashRef(pairs),
7904 line,
7905 }
7906 } else {
7907 self.parse_expression()?
7908 };
7909 self.expect(&Token::RBrace)?;
7910 Ok(Expr {
7911 kind: ExprKind::Deref {
7912 expr: Box::new(inner),
7913 kind: Sigil::Hash,
7914 },
7915 line,
7916 })
7917 }
7918 Token::ArrayAt => {
7919 self.advance();
7920 if matches!(self.peek(), Token::LBrace) {
7922 self.advance();
7923 let inner = self.parse_expression()?;
7924 self.expect(&Token::RBrace)?;
7925 return Ok(Expr {
7926 kind: ExprKind::Deref {
7927 expr: Box::new(inner),
7928 kind: Sigil::Array,
7929 },
7930 line,
7931 });
7932 }
7933 if matches!(self.peek(), Token::LBracket) {
7937 self.advance();
7938 let mut elems = Vec::new();
7939 if !matches!(self.peek(), Token::RBracket) {
7940 elems.push(self.parse_assign_expr()?);
7941 while self.eat(&Token::Comma) {
7942 if matches!(self.peek(), Token::RBracket) {
7943 break;
7944 }
7945 elems.push(self.parse_assign_expr()?);
7946 }
7947 }
7948 self.expect(&Token::RBracket)?;
7949 let aref = Expr {
7950 kind: ExprKind::ArrayRef(elems),
7951 line,
7952 };
7953 return Ok(Expr {
7954 kind: ExprKind::Deref {
7955 expr: Box::new(aref),
7956 kind: Sigil::Array,
7957 },
7958 line,
7959 });
7960 }
7961 let container = match self.peek().clone() {
7963 Token::ScalarVar(n) => {
7964 self.advance();
7965 Expr {
7966 kind: ExprKind::ScalarVar(n),
7967 line,
7968 }
7969 }
7970 _ => {
7971 return Err(self.syntax_err(
7972 "Expected `$name`, `{`, or `[` after `@` (e.g. `@$aref`, `@{expr}`, `@[1,2,3]`, or `@$href{keys}`)",
7973 line,
7974 ));
7975 }
7976 };
7977 if matches!(self.peek(), Token::LBrace) {
7978 self.advance();
7979 let keys = self.parse_arg_list()?;
7980 self.expect(&Token::RBrace)?;
7981 return Ok(Expr {
7982 kind: ExprKind::HashSliceDeref {
7983 container: Box::new(container),
7984 keys,
7985 },
7986 line,
7987 });
7988 }
7989 Ok(Expr {
7990 kind: ExprKind::Deref {
7991 expr: Box::new(container),
7992 kind: Sigil::Array,
7993 },
7994 line,
7995 })
7996 }
7997 Token::LParen => {
7998 self.advance();
7999 if matches!(self.peek(), Token::RParen) {
8000 self.advance();
8001 return Ok(Expr {
8002 kind: ExprKind::List(vec![]),
8003 line,
8004 });
8005 }
8006 let saved_no_pipe = self.no_pipe_forward_depth;
8009 self.no_pipe_forward_depth = 0;
8010 let expr = self.parse_expression();
8011 self.no_pipe_forward_depth = saved_no_pipe;
8012 let expr = expr?;
8013 self.expect(&Token::RParen)?;
8014 Ok(expr)
8015 }
8016 Token::LBracket => {
8017 self.advance();
8018 let elems = self.parse_arg_list()?;
8019 self.expect(&Token::RBracket)?;
8020 Ok(Expr {
8021 kind: ExprKind::ArrayRef(elems),
8022 line,
8023 })
8024 }
8025 Token::LBrace => {
8026 self.advance();
8028 let saved = self.pos;
8030 match self.try_parse_hash_ref() {
8031 Ok(pairs) => Ok(Expr {
8032 kind: ExprKind::HashRef(pairs),
8033 line,
8034 }),
8035 Err(_) => {
8036 self.pos = saved;
8037 let mut stmts = Vec::new();
8039 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
8040 if self.eat(&Token::Semicolon) {
8041 continue;
8042 }
8043 stmts.push(self.parse_statement()?);
8044 }
8045 self.expect(&Token::RBrace)?;
8046 Ok(Expr {
8047 kind: ExprKind::CodeRef {
8048 params: vec![],
8049 body: stmts,
8050 },
8051 line,
8052 })
8053 }
8054 }
8055 }
8056 Token::Diamond => {
8057 self.advance();
8058 Ok(Expr {
8059 kind: ExprKind::ReadLine(None),
8060 line,
8061 })
8062 }
8063 Token::ReadLine(handle) => {
8064 self.advance();
8065 Ok(Expr {
8066 kind: ExprKind::ReadLine(Some(handle)),
8067 line,
8068 })
8069 }
8070
8071 Token::ThreadArrow => {
8073 self.advance();
8074 self.parse_thread_macro(line, false)
8075 }
8076 Token::ThreadArrowLast => {
8077 self.advance();
8078 self.parse_thread_macro(line, true)
8079 }
8080 Token::Ident(ref name) => {
8081 let name = name.clone();
8082 if name.starts_with('\x00') {
8084 self.advance();
8085 let parts: Vec<&str> = name.split('\x00').collect();
8086 if parts.len() >= 4 && parts[1] == "s" {
8087 let delim = parts.get(5).and_then(|s| s.chars().next()).unwrap_or('/');
8088 return Ok(Expr {
8089 kind: ExprKind::Substitution {
8090 expr: Box::new(Expr {
8091 kind: ExprKind::ScalarVar("_".into()),
8092 line,
8093 }),
8094 pattern: parts[2].to_string(),
8095 replacement: parts[3].to_string(),
8096 flags: parts.get(4).unwrap_or(&"").to_string(),
8097 delim,
8098 },
8099 line,
8100 });
8101 }
8102 if parts.len() >= 4 && parts[1] == "tr" {
8103 let delim = parts.get(5).and_then(|s| s.chars().next()).unwrap_or('/');
8104 return Ok(Expr {
8105 kind: ExprKind::Transliterate {
8106 expr: Box::new(Expr {
8107 kind: ExprKind::ScalarVar("_".into()),
8108 line,
8109 }),
8110 from: parts[2].to_string(),
8111 to: parts[3].to_string(),
8112 flags: parts.get(4).unwrap_or(&"").to_string(),
8113 delim,
8114 },
8115 line,
8116 });
8117 }
8118 return Err(self.syntax_err("Unexpected encoded token", line));
8119 }
8120 self.parse_named_expr(name)
8121 }
8122
8123 Token::Percent => {
8126 self.advance();
8127 match self.peek().clone() {
8128 Token::Ident(name) => {
8129 self.advance();
8130 Ok(Expr {
8131 kind: ExprKind::HashVar(name),
8132 line,
8133 })
8134 }
8135 Token::ScalarVar(n) => {
8136 self.advance();
8137 Ok(Expr {
8138 kind: ExprKind::Deref {
8139 expr: Box::new(Expr {
8140 kind: ExprKind::ScalarVar(n),
8141 line,
8142 }),
8143 kind: Sigil::Hash,
8144 },
8145 line,
8146 })
8147 }
8148 Token::LBrace => {
8149 self.advance();
8150 let looks_like_pair = matches!(
8151 self.peek(),
8152 Token::Ident(_) | Token::SingleString(_) | Token::DoubleString(_)
8153 ) && matches!(self.peek_at(1), Token::FatArrow);
8154 let inner = if looks_like_pair {
8155 let pairs = self.parse_hashref_pairs_until(&Token::RBrace)?;
8156 Expr {
8157 kind: ExprKind::HashRef(pairs),
8158 line,
8159 }
8160 } else {
8161 self.parse_expression()?
8162 };
8163 self.expect(&Token::RBrace)?;
8164 Ok(Expr {
8165 kind: ExprKind::Deref {
8166 expr: Box::new(inner),
8167 kind: Sigil::Hash,
8168 },
8169 line,
8170 })
8171 }
8172 Token::LBracket => {
8173 self.advance();
8174 let pairs = self.parse_hashref_pairs_until(&Token::RBracket)?;
8175 self.expect(&Token::RBracket)?;
8176 let href = Expr {
8177 kind: ExprKind::HashRef(pairs),
8178 line,
8179 };
8180 Ok(Expr {
8181 kind: ExprKind::Deref {
8182 expr: Box::new(href),
8183 kind: Sigil::Hash,
8184 },
8185 line,
8186 })
8187 }
8188 tok => Err(self.syntax_err(
8189 format!(
8190 "Expected identifier, `$`, `{{`, or `[` after `%`, got {:?}",
8191 tok
8192 ),
8193 line,
8194 )),
8195 }
8196 }
8197
8198 tok => Err(self.syntax_err(format!("Unexpected token {:?}", tok), line)),
8199 }
8200 }
8201
8202 fn parse_named_expr(&mut self, mut name: String) -> PerlResult<Expr> {
8203 let line = self.peek_line();
8204 self.advance(); while self.eat(&Token::PackageSep) {
8206 match self.advance() {
8207 (Token::Ident(part), _) => {
8208 name = format!("{}::{}", name, part);
8209 }
8210 (tok, err_line) => {
8211 return Err(self.syntax_err(
8212 format!("Expected identifier after `::`, got {:?}", tok),
8213 err_line,
8214 ));
8215 }
8216 }
8217 }
8218
8219 if matches!(self.peek(), Token::FatArrow) {
8223 return Ok(Expr {
8224 kind: ExprKind::String(name),
8225 line,
8226 });
8227 }
8228
8229 if crate::compat_mode() {
8230 if let Some(ext) = Self::stryke_extension_name(&name) {
8231 if !self.declared_subs.contains(&name) {
8232 return Err(self.syntax_err(
8233 format!("`{ext}` is a stryke extension (disabled by --compat)"),
8234 line,
8235 ));
8236 }
8237 }
8238 }
8239
8240 match name.as_str() {
8241 "__FILE__" => Ok(Expr {
8242 kind: ExprKind::MagicConst(MagicConstKind::File),
8243 line,
8244 }),
8245 "__LINE__" => Ok(Expr {
8246 kind: ExprKind::MagicConst(MagicConstKind::Line),
8247 line,
8248 }),
8249 "__SUB__" => Ok(Expr {
8250 kind: ExprKind::MagicConst(MagicConstKind::Sub),
8251 line,
8252 }),
8253 "stdin" => Ok(Expr {
8254 kind: ExprKind::FuncCall {
8255 name: "stdin".into(),
8256 args: vec![],
8257 },
8258 line,
8259 }),
8260 "range" => {
8261 let args = self.parse_builtin_args()?;
8262 Ok(Expr {
8263 kind: ExprKind::FuncCall {
8264 name: "range".into(),
8265 args,
8266 },
8267 line,
8268 })
8269 }
8270 "print" | "pr" => self.parse_print_like(|h, a| ExprKind::Print { handle: h, args: a }),
8271 "say" => {
8272 if !crate::compat_mode() {
8273 return Err(self.syntax_err(
8274 "stryke uses `p` instead of `say` (this is not Perl 5)",
8275 line,
8276 ));
8277 }
8278 self.parse_print_like(|h, a| ExprKind::Say { handle: h, args: a })
8279 }
8280 "p" => self.parse_print_like(|h, a| ExprKind::Say { handle: h, args: a }),
8281 "printf" => self.parse_print_like(|h, a| ExprKind::Printf { handle: h, args: a }),
8282 "die" => {
8283 let args = self.parse_list_until_terminator()?;
8284 Ok(Expr {
8285 kind: ExprKind::Die(args),
8286 line,
8287 })
8288 }
8289 "warn" => {
8290 let args = self.parse_list_until_terminator()?;
8291 Ok(Expr {
8292 kind: ExprKind::Warn(args),
8293 line,
8294 })
8295 }
8296 "croak" | "confess" => {
8301 let args = self.parse_list_until_terminator()?;
8302 Ok(Expr {
8303 kind: ExprKind::Die(args),
8304 line,
8305 })
8306 }
8307 "carp" | "cluck" => {
8309 let args = self.parse_list_until_terminator()?;
8310 Ok(Expr {
8311 kind: ExprKind::Warn(args),
8312 line,
8313 })
8314 }
8315 "chomp" => {
8316 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8317 return Ok(e);
8318 }
8319 let a = self.parse_one_arg_or_default()?;
8320 Ok(Expr {
8321 kind: ExprKind::Chomp(Box::new(a)),
8322 line,
8323 })
8324 }
8325 "chop" => {
8326 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8327 return Ok(e);
8328 }
8329 let a = self.parse_one_arg_or_default()?;
8330 Ok(Expr {
8331 kind: ExprKind::Chop(Box::new(a)),
8332 line,
8333 })
8334 }
8335 "length" => {
8336 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8337 return Ok(e);
8338 }
8339 let a = self.parse_one_arg_or_default()?;
8340 Ok(Expr {
8341 kind: ExprKind::Length(Box::new(a)),
8342 line,
8343 })
8344 }
8345 "defined" => {
8346 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8347 return Ok(e);
8348 }
8349 let a = self.parse_one_arg_or_default()?;
8350 Ok(Expr {
8351 kind: ExprKind::Defined(Box::new(a)),
8352 line,
8353 })
8354 }
8355 "ref" => {
8356 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8357 return Ok(e);
8358 }
8359 let a = self.parse_one_arg_or_default()?;
8360 Ok(Expr {
8361 kind: ExprKind::Ref(Box::new(a)),
8362 line,
8363 })
8364 }
8365 "undef" => {
8366 if self.peek_line() == self.prev_line()
8369 && matches!(
8370 self.peek(),
8371 Token::ScalarVar(_) | Token::ArrayVar(_) | Token::HashVar(_)
8372 )
8373 {
8374 let target = self.parse_primary()?;
8375 return Ok(Expr {
8376 kind: ExprKind::Assign {
8377 target: Box::new(target),
8378 value: Box::new(Expr {
8379 kind: ExprKind::Undef,
8380 line,
8381 }),
8382 },
8383 line,
8384 });
8385 }
8386 Ok(Expr {
8387 kind: ExprKind::Undef,
8388 line,
8389 })
8390 }
8391 "scalar" => {
8392 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8393 return Ok(e);
8394 }
8395 let a = self.parse_one_arg_or_default()?;
8396 Ok(Expr {
8397 kind: ExprKind::ScalarContext(Box::new(a)),
8398 line,
8399 })
8400 }
8401 "abs" => {
8402 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8403 return Ok(e);
8404 }
8405 let a = self.parse_one_arg_or_default()?;
8406 Ok(Expr {
8407 kind: ExprKind::Abs(Box::new(a)),
8408 line,
8409 })
8410 }
8411 "inc" | "dec" => {
8416 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8417 return Ok(e);
8418 }
8419 let a = self.parse_one_arg_or_default()?;
8420 Ok(Expr {
8421 kind: ExprKind::FuncCall {
8422 name,
8423 args: vec![a],
8424 },
8425 line,
8426 })
8427 }
8428 "int" => {
8429 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8430 return Ok(e);
8431 }
8432 let a = self.parse_one_arg_or_default()?;
8433 Ok(Expr {
8434 kind: ExprKind::Int(Box::new(a)),
8435 line,
8436 })
8437 }
8438 "sqrt" => {
8439 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8440 return Ok(e);
8441 }
8442 let a = self.parse_one_arg_or_default()?;
8443 Ok(Expr {
8444 kind: ExprKind::Sqrt(Box::new(a)),
8445 line,
8446 })
8447 }
8448 "sin" => {
8449 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8450 return Ok(e);
8451 }
8452 let a = self.parse_one_arg_or_default()?;
8453 Ok(Expr {
8454 kind: ExprKind::Sin(Box::new(a)),
8455 line,
8456 })
8457 }
8458 "cos" => {
8459 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8460 return Ok(e);
8461 }
8462 let a = self.parse_one_arg_or_default()?;
8463 Ok(Expr {
8464 kind: ExprKind::Cos(Box::new(a)),
8465 line,
8466 })
8467 }
8468 "atan2" => {
8469 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8470 return Ok(e);
8471 }
8472 let args = self.parse_builtin_args()?;
8473 if args.len() != 2 {
8474 return Err(self.syntax_err("atan2 requires two arguments", line));
8475 }
8476 Ok(Expr {
8477 kind: ExprKind::Atan2 {
8478 y: Box::new(args[0].clone()),
8479 x: Box::new(args[1].clone()),
8480 },
8481 line,
8482 })
8483 }
8484 "exp" => {
8485 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8486 return Ok(e);
8487 }
8488 let a = self.parse_one_arg_or_default()?;
8489 Ok(Expr {
8490 kind: ExprKind::Exp(Box::new(a)),
8491 line,
8492 })
8493 }
8494 "log" => {
8495 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8496 return Ok(e);
8497 }
8498 let a = self.parse_one_arg_or_default()?;
8499 Ok(Expr {
8500 kind: ExprKind::Log(Box::new(a)),
8501 line,
8502 })
8503 }
8504 "input" => {
8505 let args = if matches!(
8506 self.peek(),
8507 Token::Semicolon
8508 | Token::RBrace
8509 | Token::RParen
8510 | Token::Eof
8511 | Token::Comma
8512 | Token::PipeForward
8513 ) {
8514 vec![]
8515 } else if matches!(self.peek(), Token::LParen) {
8516 self.advance();
8517 if matches!(self.peek(), Token::RParen) {
8518 self.advance();
8519 vec![]
8520 } else {
8521 let a = self.parse_expression()?;
8522 self.expect(&Token::RParen)?;
8523 vec![a]
8524 }
8525 } else {
8526 let a = self.parse_one_arg()?;
8527 vec![a]
8528 };
8529 Ok(Expr {
8530 kind: ExprKind::FuncCall {
8531 name: "input".to_string(),
8532 args,
8533 },
8534 line,
8535 })
8536 }
8537 "rand" => {
8538 if matches!(
8539 self.peek(),
8540 Token::Semicolon
8541 | Token::RBrace
8542 | Token::RParen
8543 | Token::Eof
8544 | Token::Comma
8545 | Token::PipeForward
8546 ) {
8547 Ok(Expr {
8548 kind: ExprKind::Rand(None),
8549 line,
8550 })
8551 } else if matches!(self.peek(), Token::LParen) {
8552 self.advance();
8553 if matches!(self.peek(), Token::RParen) {
8554 self.advance();
8555 Ok(Expr {
8556 kind: ExprKind::Rand(None),
8557 line,
8558 })
8559 } else {
8560 let a = self.parse_expression()?;
8561 self.expect(&Token::RParen)?;
8562 Ok(Expr {
8563 kind: ExprKind::Rand(Some(Box::new(a))),
8564 line,
8565 })
8566 }
8567 } else {
8568 let a = self.parse_one_arg()?;
8569 Ok(Expr {
8570 kind: ExprKind::Rand(Some(Box::new(a))),
8571 line,
8572 })
8573 }
8574 }
8575 "srand" => {
8576 if matches!(
8577 self.peek(),
8578 Token::Semicolon
8579 | Token::RBrace
8580 | Token::RParen
8581 | Token::Eof
8582 | Token::Comma
8583 | Token::PipeForward
8584 ) {
8585 Ok(Expr {
8586 kind: ExprKind::Srand(None),
8587 line,
8588 })
8589 } else if matches!(self.peek(), Token::LParen) {
8590 self.advance();
8591 if matches!(self.peek(), Token::RParen) {
8592 self.advance();
8593 Ok(Expr {
8594 kind: ExprKind::Srand(None),
8595 line,
8596 })
8597 } else {
8598 let a = self.parse_expression()?;
8599 self.expect(&Token::RParen)?;
8600 Ok(Expr {
8601 kind: ExprKind::Srand(Some(Box::new(a))),
8602 line,
8603 })
8604 }
8605 } else {
8606 let a = self.parse_one_arg()?;
8607 Ok(Expr {
8608 kind: ExprKind::Srand(Some(Box::new(a))),
8609 line,
8610 })
8611 }
8612 }
8613 "hex" => {
8614 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8615 return Ok(e);
8616 }
8617 let a = self.parse_one_arg_or_default()?;
8618 Ok(Expr {
8619 kind: ExprKind::Hex(Box::new(a)),
8620 line,
8621 })
8622 }
8623 "oct" => {
8624 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8625 return Ok(e);
8626 }
8627 let a = self.parse_one_arg_or_default()?;
8628 Ok(Expr {
8629 kind: ExprKind::Oct(Box::new(a)),
8630 line,
8631 })
8632 }
8633 "chr" => {
8634 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8635 return Ok(e);
8636 }
8637 let a = self.parse_one_arg_or_default()?;
8638 Ok(Expr {
8639 kind: ExprKind::Chr(Box::new(a)),
8640 line,
8641 })
8642 }
8643 "ord" => {
8644 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8645 return Ok(e);
8646 }
8647 let a = self.parse_one_arg_or_default()?;
8648 Ok(Expr {
8649 kind: ExprKind::Ord(Box::new(a)),
8650 line,
8651 })
8652 }
8653 "lc" => {
8654 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8655 return Ok(e);
8656 }
8657 let a = self.parse_one_arg_or_default()?;
8658 Ok(Expr {
8659 kind: ExprKind::Lc(Box::new(a)),
8660 line,
8661 })
8662 }
8663 "uc" => {
8664 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8665 return Ok(e);
8666 }
8667 let a = self.parse_one_arg_or_default()?;
8668 Ok(Expr {
8669 kind: ExprKind::Uc(Box::new(a)),
8670 line,
8671 })
8672 }
8673 "lcfirst" => {
8674 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8675 return Ok(e);
8676 }
8677 let a = self.parse_one_arg_or_default()?;
8678 Ok(Expr {
8679 kind: ExprKind::Lcfirst(Box::new(a)),
8680 line,
8681 })
8682 }
8683 "ucfirst" => {
8684 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8685 return Ok(e);
8686 }
8687 let a = self.parse_one_arg_or_default()?;
8688 Ok(Expr {
8689 kind: ExprKind::Ucfirst(Box::new(a)),
8690 line,
8691 })
8692 }
8693 "fc" => {
8694 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8695 return Ok(e);
8696 }
8697 let a = self.parse_one_arg_or_default()?;
8698 Ok(Expr {
8699 kind: ExprKind::Fc(Box::new(a)),
8700 line,
8701 })
8702 }
8703 "crypt" => {
8704 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8705 return Ok(e);
8706 }
8707 let args = self.parse_builtin_args()?;
8708 if args.len() != 2 {
8709 return Err(self.syntax_err("crypt requires two arguments", line));
8710 }
8711 Ok(Expr {
8712 kind: ExprKind::Crypt {
8713 plaintext: Box::new(args[0].clone()),
8714 salt: Box::new(args[1].clone()),
8715 },
8716 line,
8717 })
8718 }
8719 "pos" => {
8720 if matches!(
8721 self.peek(),
8722 Token::Semicolon
8723 | Token::RBrace
8724 | Token::RParen
8725 | Token::Eof
8726 | Token::Comma
8727 | Token::PipeForward
8728 ) {
8729 Ok(Expr {
8730 kind: ExprKind::Pos(None),
8731 line,
8732 })
8733 } else if matches!(self.peek(), Token::Assign) {
8734 self.advance();
8736 let rhs = self.parse_assign_expr()?;
8737 Ok(Expr {
8738 kind: ExprKind::Assign {
8739 target: Box::new(Expr {
8740 kind: ExprKind::Pos(Some(Box::new(Expr {
8741 kind: ExprKind::ScalarVar("_".into()),
8742 line,
8743 }))),
8744 line,
8745 }),
8746 value: Box::new(rhs),
8747 },
8748 line,
8749 })
8750 } else if matches!(self.peek(), Token::LParen) {
8751 self.advance();
8752 if matches!(self.peek(), Token::RParen) {
8753 self.advance();
8754 Ok(Expr {
8755 kind: ExprKind::Pos(None),
8756 line,
8757 })
8758 } else {
8759 let a = self.parse_expression()?;
8760 self.expect(&Token::RParen)?;
8761 Ok(Expr {
8762 kind: ExprKind::Pos(Some(Box::new(a))),
8763 line,
8764 })
8765 }
8766 } else {
8767 let saved = self.pos;
8768 let subj = self.parse_unary()?;
8769 if matches!(self.peek(), Token::Assign) {
8770 self.advance();
8771 let rhs = self.parse_assign_expr()?;
8772 Ok(Expr {
8773 kind: ExprKind::Assign {
8774 target: Box::new(Expr {
8775 kind: ExprKind::Pos(Some(Box::new(subj))),
8776 line,
8777 }),
8778 value: Box::new(rhs),
8779 },
8780 line,
8781 })
8782 } else {
8783 self.pos = saved;
8784 let a = self.parse_one_arg()?;
8785 Ok(Expr {
8786 kind: ExprKind::Pos(Some(Box::new(a))),
8787 line,
8788 })
8789 }
8790 }
8791 }
8792 "study" => {
8793 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8794 return Ok(e);
8795 }
8796 let a = self.parse_one_arg_or_default()?;
8797 Ok(Expr {
8798 kind: ExprKind::Study(Box::new(a)),
8799 line,
8800 })
8801 }
8802 "push" => {
8803 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8804 return Ok(e);
8805 }
8806 let args = self.parse_builtin_args()?;
8807 let (first, rest) = args
8808 .split_first()
8809 .ok_or_else(|| self.syntax_err("push requires arguments", line))?;
8810 Ok(Expr {
8811 kind: ExprKind::Push {
8812 array: Box::new(first.clone()),
8813 values: rest.to_vec(),
8814 },
8815 line,
8816 })
8817 }
8818 "pop" => {
8819 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8820 return Ok(e);
8821 }
8822 let a = self.parse_one_arg_or_argv()?;
8823 Ok(Expr {
8824 kind: ExprKind::Pop(Box::new(a)),
8825 line,
8826 })
8827 }
8828 "shift" => {
8829 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8830 return Ok(e);
8831 }
8832 let a = self.parse_one_arg_or_argv()?;
8833 Ok(Expr {
8834 kind: ExprKind::Shift(Box::new(a)),
8835 line,
8836 })
8837 }
8838 "unshift" => {
8839 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8840 return Ok(e);
8841 }
8842 let args = self.parse_builtin_args()?;
8843 let (first, rest) = args
8844 .split_first()
8845 .ok_or_else(|| self.syntax_err("unshift requires arguments", line))?;
8846 Ok(Expr {
8847 kind: ExprKind::Unshift {
8848 array: Box::new(first.clone()),
8849 values: rest.to_vec(),
8850 },
8851 line,
8852 })
8853 }
8854 "splice" => {
8855 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8856 return Ok(e);
8857 }
8858 let args = self.parse_builtin_args()?;
8859 let mut iter = args.into_iter();
8860 let array = Box::new(
8861 iter.next()
8862 .ok_or_else(|| self.syntax_err("splice requires arguments", line))?,
8863 );
8864 let offset = iter.next().map(Box::new);
8865 let length = iter.next().map(Box::new);
8866 let replacement: Vec<Expr> = iter.collect();
8867 Ok(Expr {
8868 kind: ExprKind::Splice {
8869 array,
8870 offset,
8871 length,
8872 replacement,
8873 },
8874 line,
8875 })
8876 }
8877 "delete" => {
8878 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8879 return Ok(e);
8880 }
8881 let a = self.parse_postfix()?;
8882 Ok(Expr {
8883 kind: ExprKind::Delete(Box::new(a)),
8884 line,
8885 })
8886 }
8887 "exists" => {
8888 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8889 return Ok(e);
8890 }
8891 let a = self.parse_postfix()?;
8892 Ok(Expr {
8893 kind: ExprKind::Exists(Box::new(a)),
8894 line,
8895 })
8896 }
8897 "keys" => {
8898 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8899 return Ok(e);
8900 }
8901 let a = self.parse_one_arg_or_default()?;
8902 Ok(Expr {
8903 kind: ExprKind::Keys(Box::new(a)),
8904 line,
8905 })
8906 }
8907 "values" => {
8908 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8909 return Ok(e);
8910 }
8911 let a = self.parse_one_arg_or_default()?;
8912 Ok(Expr {
8913 kind: ExprKind::Values(Box::new(a)),
8914 line,
8915 })
8916 }
8917 "each" => {
8918 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8919 return Ok(e);
8920 }
8921 let a = self.parse_one_arg_or_default()?;
8922 Ok(Expr {
8923 kind: ExprKind::Each(Box::new(a)),
8924 line,
8925 })
8926 }
8927 "fore" | "e" | "ep" => {
8928 if matches!(self.peek(), Token::LBrace) {
8930 let (block, list) = self.parse_block_list()?;
8931 Ok(Expr {
8932 kind: ExprKind::ForEachExpr {
8933 block,
8934 list: Box::new(list),
8935 },
8936 line,
8937 })
8938 } else if self.in_pipe_rhs() {
8939 let is_terminal = matches!(
8942 self.peek(),
8943 Token::Semicolon
8944 | Token::RParen
8945 | Token::Eof
8946 | Token::PipeForward
8947 | Token::RBrace
8948 );
8949 let block = if name == "ep" && is_terminal {
8950 vec![Statement {
8951 label: None,
8952 kind: StmtKind::Expression(Expr {
8953 kind: ExprKind::Say {
8954 handle: None,
8955 args: vec![Expr {
8956 kind: ExprKind::ScalarVar("_".into()),
8957 line,
8958 }],
8959 },
8960 line,
8961 }),
8962 line,
8963 }]
8964 } else {
8965 let expr = self.parse_assign_expr_stop_at_pipe()?;
8966 let expr = Self::lift_bareword_to_topic_call(expr);
8967 vec![Statement {
8968 label: None,
8969 kind: StmtKind::Expression(expr),
8970 line,
8971 }]
8972 };
8973 let list = self.pipe_placeholder_list(line);
8974 Ok(Expr {
8975 kind: ExprKind::ForEachExpr {
8976 block,
8977 list: Box::new(list),
8978 },
8979 line,
8980 })
8981 } else {
8982 let expr = self.parse_assign_expr()?;
8984 let expr = Self::lift_bareword_to_topic_call(expr);
8985 self.expect(&Token::Comma)?;
8986 let list_parts = self.parse_list_until_terminator()?;
8987 let list_expr = if list_parts.len() == 1 {
8988 list_parts.into_iter().next().unwrap()
8989 } else {
8990 Expr {
8991 kind: ExprKind::List(list_parts),
8992 line,
8993 }
8994 };
8995 let block = vec![Statement {
8996 label: None,
8997 kind: StmtKind::Expression(expr),
8998 line,
8999 }];
9000 Ok(Expr {
9001 kind: ExprKind::ForEachExpr {
9002 block,
9003 list: Box::new(list_expr),
9004 },
9005 line,
9006 })
9007 }
9008 }
9009 "rev" => {
9010 let a = if self.in_pipe_rhs()
9015 && matches!(
9016 self.peek(),
9017 Token::Semicolon | Token::RParen | Token::Eof | Token::PipeForward
9018 ) {
9019 self.pipe_placeholder_list(line)
9020 } else {
9021 self.parse_one_arg_or_default()?
9022 };
9023 Ok(Expr {
9024 kind: ExprKind::Rev(Box::new(a)),
9025 line,
9026 })
9027 }
9028 "reverse" => {
9029 if !crate::compat_mode() {
9030 return Err(self.syntax_err(
9031 "stryke uses `rev` instead of `reverse` (this is not Perl 5)",
9032 line,
9033 ));
9034 }
9035 let a = if self.in_pipe_rhs()
9037 && matches!(
9038 self.peek(),
9039 Token::Semicolon
9040 | Token::RBrace
9041 | Token::RParen
9042 | Token::Eof
9043 | Token::PipeForward
9044 ) {
9045 self.pipe_placeholder_list(line)
9046 } else {
9047 self.parse_one_arg()?
9048 };
9049 Ok(Expr {
9050 kind: ExprKind::ReverseExpr(Box::new(a)),
9051 line,
9052 })
9053 }
9054 "reversed" | "rv" => {
9055 let a = if self.in_pipe_rhs()
9057 && matches!(
9058 self.peek(),
9059 Token::Semicolon
9060 | Token::RBrace
9061 | Token::RParen
9062 | Token::Eof
9063 | Token::PipeForward
9064 ) {
9065 self.pipe_placeholder_list(line)
9066 } else {
9067 self.parse_one_arg()?
9068 };
9069 Ok(Expr {
9070 kind: ExprKind::Rev(Box::new(a)),
9071 line,
9072 })
9073 }
9074 "join" => {
9075 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9076 return Ok(e);
9077 }
9078 let args = self.parse_builtin_args()?;
9079 if args.is_empty() {
9080 return Err(self.syntax_err("join requires separator and list", line));
9081 }
9082 if args.len() < 2 && !self.in_pipe_rhs() {
9084 return Err(self.syntax_err("join requires separator and list", line));
9085 }
9086 Ok(Expr {
9087 kind: ExprKind::JoinExpr {
9088 separator: Box::new(args[0].clone()),
9089 list: Box::new(Expr {
9090 kind: ExprKind::List(args[1..].to_vec()),
9091 line,
9092 }),
9093 },
9094 line,
9095 })
9096 }
9097 "split" => {
9098 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9099 return Ok(e);
9100 }
9101 let args = self.parse_builtin_args()?;
9102 let pattern = args.first().cloned().unwrap_or(Expr {
9103 kind: ExprKind::String(" ".into()),
9104 line,
9105 });
9106 let string = args.get(1).cloned().unwrap_or(Expr {
9107 kind: ExprKind::ScalarVar("_".into()),
9108 line,
9109 });
9110 let limit = args.get(2).cloned().map(Box::new);
9111 Ok(Expr {
9112 kind: ExprKind::SplitExpr {
9113 pattern: Box::new(pattern),
9114 string: Box::new(string),
9115 limit,
9116 },
9117 line,
9118 })
9119 }
9120 "substr" => {
9121 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9122 return Ok(e);
9123 }
9124 let args = self.parse_builtin_args()?;
9125 Ok(Expr {
9126 kind: ExprKind::Substr {
9127 string: Box::new(args[0].clone()),
9128 offset: Box::new(args[1].clone()),
9129 length: args.get(2).cloned().map(Box::new),
9130 replacement: args.get(3).cloned().map(Box::new),
9131 },
9132 line,
9133 })
9134 }
9135 "index" => {
9136 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9137 return Ok(e);
9138 }
9139 let args = self.parse_builtin_args()?;
9140 Ok(Expr {
9141 kind: ExprKind::Index {
9142 string: Box::new(args[0].clone()),
9143 substr: Box::new(args[1].clone()),
9144 position: args.get(2).cloned().map(Box::new),
9145 },
9146 line,
9147 })
9148 }
9149 "rindex" => {
9150 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9151 return Ok(e);
9152 }
9153 let args = self.parse_builtin_args()?;
9154 Ok(Expr {
9155 kind: ExprKind::Rindex {
9156 string: Box::new(args[0].clone()),
9157 substr: Box::new(args[1].clone()),
9158 position: args.get(2).cloned().map(Box::new),
9159 },
9160 line,
9161 })
9162 }
9163 "sprintf" => {
9164 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9165 return Ok(e);
9166 }
9167 let args = self.parse_builtin_args()?;
9168 let (first, rest) = args
9169 .split_first()
9170 .ok_or_else(|| self.syntax_err("sprintf requires format", line))?;
9171 Ok(Expr {
9172 kind: ExprKind::Sprintf {
9173 format: Box::new(first.clone()),
9174 args: rest.to_vec(),
9175 },
9176 line,
9177 })
9178 }
9179 "map" | "flat_map" | "maps" | "flat_maps" => {
9180 let flatten_array_refs = matches!(name.as_str(), "flat_map" | "flat_maps");
9181 let stream = matches!(name.as_str(), "maps" | "flat_maps");
9182 if matches!(self.peek(), Token::LBrace) {
9183 let (block, list) = self.parse_block_list()?;
9184 Ok(Expr {
9185 kind: ExprKind::MapExpr {
9186 block,
9187 list: Box::new(list),
9188 flatten_array_refs,
9189 stream,
9190 },
9191 line,
9192 })
9193 } else {
9194 let expr = self.parse_assign_expr_stop_at_pipe()?;
9195 let expr = Self::lift_bareword_to_topic_call(expr);
9198 let list_expr = if self.in_pipe_rhs()
9199 && matches!(
9200 self.peek(),
9201 Token::Semicolon
9202 | Token::RBrace
9203 | Token::RParen
9204 | Token::Eof
9205 | Token::PipeForward
9206 ) {
9207 self.pipe_placeholder_list(line)
9208 } else {
9209 self.expect(&Token::Comma)?;
9210 let list_parts = self.parse_list_until_terminator()?;
9211 if list_parts.len() == 1 {
9212 list_parts.into_iter().next().unwrap()
9213 } else {
9214 Expr {
9215 kind: ExprKind::List(list_parts),
9216 line,
9217 }
9218 }
9219 };
9220 Ok(Expr {
9221 kind: ExprKind::MapExprComma {
9222 expr: Box::new(expr),
9223 list: Box::new(list_expr),
9224 flatten_array_refs,
9225 stream,
9226 },
9227 line,
9228 })
9229 }
9230 }
9231 "cond" => {
9232 if crate::compat_mode() {
9233 return Err(self
9234 .syntax_err("`cond` is a stryke extension (disabled by --compat)", line));
9235 }
9236 self.parse_cond_expr(line)
9237 }
9238 "match" => {
9239 if crate::compat_mode() {
9240 return Err(self.syntax_err(
9241 "algebraic `match` is a stryke extension (disabled by --compat)",
9242 line,
9243 ));
9244 }
9245 self.parse_algebraic_match_expr(line)
9246 }
9247 "grep" | "greps" | "filter" | "fi" | "find_all" => {
9248 let keyword = match name.as_str() {
9249 "grep" => crate::ast::GrepBuiltinKeyword::Grep,
9250 "greps" => crate::ast::GrepBuiltinKeyword::Greps,
9251 "filter" | "fi" => crate::ast::GrepBuiltinKeyword::Filter,
9252 "find_all" => crate::ast::GrepBuiltinKeyword::FindAll,
9253 _ => unreachable!(),
9254 };
9255 if matches!(self.peek(), Token::LBrace) {
9256 let (block, list) = self.parse_block_list()?;
9257 Ok(Expr {
9258 kind: ExprKind::GrepExpr {
9259 block,
9260 list: Box::new(list),
9261 keyword,
9262 },
9263 line,
9264 })
9265 } else {
9266 let expr = self.parse_assign_expr_stop_at_pipe()?;
9267 if self.in_pipe_rhs()
9268 && matches!(
9269 self.peek(),
9270 Token::Semicolon
9271 | Token::RBrace
9272 | Token::RParen
9273 | Token::Eof
9274 | Token::PipeForward
9275 )
9276 {
9277 let list = self.pipe_placeholder_list(line);
9282 let topic = Expr {
9283 kind: ExprKind::ScalarVar("_".into()),
9284 line,
9285 };
9286 let test = match &expr.kind {
9287 ExprKind::Integer(_) | ExprKind::Float(_) => Expr {
9288 kind: ExprKind::BinOp {
9289 op: BinOp::NumEq,
9290 left: Box::new(topic),
9291 right: Box::new(expr),
9292 },
9293 line,
9294 },
9295 ExprKind::String(_) | ExprKind::InterpolatedString(_) => Expr {
9296 kind: ExprKind::BinOp {
9297 op: BinOp::StrEq,
9298 left: Box::new(topic),
9299 right: Box::new(expr),
9300 },
9301 line,
9302 },
9303 ExprKind::Regex { .. } => Expr {
9304 kind: ExprKind::BinOp {
9305 op: BinOp::BindMatch,
9306 left: Box::new(topic),
9307 right: Box::new(expr),
9308 },
9309 line,
9310 },
9311 _ => {
9312 Self::lift_bareword_to_topic_call(expr)
9314 }
9315 };
9316 let block = vec![Statement {
9317 label: None,
9318 kind: StmtKind::Expression(test),
9319 line,
9320 }];
9321 Ok(Expr {
9322 kind: ExprKind::GrepExpr {
9323 block,
9324 list: Box::new(list),
9325 keyword,
9326 },
9327 line,
9328 })
9329 } else {
9330 let expr = Self::lift_bareword_to_topic_call(expr);
9331 self.expect(&Token::Comma)?;
9332 let list_parts = self.parse_list_until_terminator()?;
9333 let list_expr = if list_parts.len() == 1 {
9334 list_parts.into_iter().next().unwrap()
9335 } else {
9336 Expr {
9337 kind: ExprKind::List(list_parts),
9338 line,
9339 }
9340 };
9341 Ok(Expr {
9342 kind: ExprKind::GrepExprComma {
9343 expr: Box::new(expr),
9344 list: Box::new(list_expr),
9345 keyword,
9346 },
9347 line,
9348 })
9349 }
9350 }
9351 }
9352 "sort" => {
9353 use crate::ast::SortComparator;
9354 if matches!(self.peek(), Token::LBrace) {
9355 let block = self.parse_block()?;
9356 let _ = self.eat(&Token::Comma);
9357 let list = if self.in_pipe_rhs()
9358 && matches!(
9359 self.peek(),
9360 Token::Semicolon
9361 | Token::RBrace
9362 | Token::RParen
9363 | Token::Eof
9364 | Token::PipeForward
9365 ) {
9366 self.pipe_placeholder_list(line)
9367 } else {
9368 self.parse_expression()?
9369 };
9370 Ok(Expr {
9371 kind: ExprKind::SortExpr {
9372 cmp: Some(SortComparator::Block(block)),
9373 list: Box::new(list),
9374 },
9375 line,
9376 })
9377 } else if matches!(self.peek(), Token::ScalarVar(ref v) if v == "a" || v == "b") {
9378 let block = self.parse_block_or_bareword_cmp_block()?;
9380 let _ = self.eat(&Token::Comma);
9381 let list = if self.in_pipe_rhs()
9382 && matches!(
9383 self.peek(),
9384 Token::Semicolon
9385 | Token::RBrace
9386 | Token::RParen
9387 | Token::Eof
9388 | Token::PipeForward
9389 ) {
9390 self.pipe_placeholder_list(line)
9391 } else {
9392 self.parse_expression()?
9393 };
9394 Ok(Expr {
9395 kind: ExprKind::SortExpr {
9396 cmp: Some(SortComparator::Block(block)),
9397 list: Box::new(list),
9398 },
9399 line,
9400 })
9401 } else if matches!(self.peek(), Token::ScalarVar(_)) {
9402 self.suppress_indirect_paren_call =
9404 self.suppress_indirect_paren_call.saturating_add(1);
9405 let code = self.parse_assign_expr()?;
9406 self.suppress_indirect_paren_call =
9407 self.suppress_indirect_paren_call.saturating_sub(1);
9408 let list = if matches!(self.peek(), Token::LParen) {
9409 self.advance();
9410 let e = self.parse_expression()?;
9411 self.expect(&Token::RParen)?;
9412 e
9413 } else {
9414 self.parse_expression()?
9415 };
9416 Ok(Expr {
9417 kind: ExprKind::SortExpr {
9418 cmp: Some(SortComparator::Code(Box::new(code))),
9419 list: Box::new(list),
9420 },
9421 line,
9422 })
9423 } else if matches!(self.peek(), Token::Ident(ref name) if !Self::is_known_bareword(name))
9424 {
9425 let block = self.parse_block_or_bareword_cmp_block()?;
9427 let _ = self.eat(&Token::Comma);
9428 let list = if self.in_pipe_rhs()
9429 && matches!(
9430 self.peek(),
9431 Token::Semicolon
9432 | Token::RBrace
9433 | Token::RParen
9434 | Token::Eof
9435 | Token::PipeForward
9436 ) {
9437 self.pipe_placeholder_list(line)
9438 } else {
9439 self.parse_expression()?
9440 };
9441 Ok(Expr {
9442 kind: ExprKind::SortExpr {
9443 cmp: Some(SortComparator::Block(block)),
9444 list: Box::new(list),
9445 },
9446 line,
9447 })
9448 } else {
9449 let list = if self.in_pipe_rhs()
9452 && matches!(
9453 self.peek(),
9454 Token::Semicolon
9455 | Token::RBrace
9456 | Token::RParen
9457 | Token::Eof
9458 | Token::PipeForward
9459 ) {
9460 self.pipe_placeholder_list(line)
9461 } else {
9462 self.parse_expression()?
9463 };
9464 Ok(Expr {
9465 kind: ExprKind::SortExpr {
9466 cmp: None,
9467 list: Box::new(list),
9468 },
9469 line,
9470 })
9471 }
9472 }
9473 "reduce" | "fold" | "inject" => {
9474 let (block, list) = self.parse_block_list()?;
9475 Ok(Expr {
9476 kind: ExprKind::ReduceExpr {
9477 block,
9478 list: Box::new(list),
9479 },
9480 line,
9481 })
9482 }
9483 "pmap" => {
9485 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
9486 Ok(Expr {
9487 kind: ExprKind::PMapExpr {
9488 block,
9489 list: Box::new(list),
9490 progress: progress.map(Box::new),
9491 flat_outputs: false,
9492 on_cluster: None,
9493 stream: false,
9494 },
9495 line,
9496 })
9497 }
9498 "pmap_on" => {
9499 let (cluster, block, list, progress) =
9500 self.parse_cluster_block_then_list_optional_progress()?;
9501 Ok(Expr {
9502 kind: ExprKind::PMapExpr {
9503 block,
9504 list: Box::new(list),
9505 progress: progress.map(Box::new),
9506 flat_outputs: false,
9507 on_cluster: Some(Box::new(cluster)),
9508 stream: false,
9509 },
9510 line,
9511 })
9512 }
9513 "pflat_map" => {
9514 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
9515 Ok(Expr {
9516 kind: ExprKind::PMapExpr {
9517 block,
9518 list: Box::new(list),
9519 progress: progress.map(Box::new),
9520 flat_outputs: true,
9521 on_cluster: None,
9522 stream: false,
9523 },
9524 line,
9525 })
9526 }
9527 "pflat_map_on" => {
9528 let (cluster, block, list, progress) =
9529 self.parse_cluster_block_then_list_optional_progress()?;
9530 Ok(Expr {
9531 kind: ExprKind::PMapExpr {
9532 block,
9533 list: Box::new(list),
9534 progress: progress.map(Box::new),
9535 flat_outputs: true,
9536 on_cluster: Some(Box::new(cluster)),
9537 stream: false,
9538 },
9539 line,
9540 })
9541 }
9542 "pmaps" => {
9543 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
9544 Ok(Expr {
9545 kind: ExprKind::PMapExpr {
9546 block,
9547 list: Box::new(list),
9548 progress: progress.map(Box::new),
9549 flat_outputs: false,
9550 on_cluster: None,
9551 stream: true,
9552 },
9553 line,
9554 })
9555 }
9556 "pflat_maps" => {
9557 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
9558 Ok(Expr {
9559 kind: ExprKind::PMapExpr {
9560 block,
9561 list: Box::new(list),
9562 progress: progress.map(Box::new),
9563 flat_outputs: true,
9564 on_cluster: None,
9565 stream: true,
9566 },
9567 line,
9568 })
9569 }
9570 "pgreps" => {
9571 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
9572 Ok(Expr {
9573 kind: ExprKind::PGrepExpr {
9574 block,
9575 list: Box::new(list),
9576 progress: progress.map(Box::new),
9577 stream: true,
9578 },
9579 line,
9580 })
9581 }
9582 "pmap_chunked" => {
9583 let chunk_size = self.parse_assign_expr()?;
9584 let block = self.parse_block_or_bareword_block()?;
9585 self.eat(&Token::Comma);
9586 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
9587 Ok(Expr {
9588 kind: ExprKind::PMapChunkedExpr {
9589 chunk_size: Box::new(chunk_size),
9590 block,
9591 list: Box::new(list),
9592 progress: progress.map(Box::new),
9593 },
9594 line,
9595 })
9596 }
9597 "pgrep" => {
9598 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
9599 Ok(Expr {
9600 kind: ExprKind::PGrepExpr {
9601 block,
9602 list: Box::new(list),
9603 progress: progress.map(Box::new),
9604 stream: false,
9605 },
9606 line,
9607 })
9608 }
9609 "pfor" => {
9610 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
9611 Ok(Expr {
9612 kind: ExprKind::PForExpr {
9613 block,
9614 list: Box::new(list),
9615 progress: progress.map(Box::new),
9616 },
9617 line,
9618 })
9619 }
9620 "par_lines" | "par_walk" => {
9621 let args = self.parse_builtin_args()?;
9622 if args.len() < 2 {
9623 return Err(
9624 self.syntax_err(format!("{} requires at least two arguments", name), line)
9625 );
9626 }
9627
9628 if name == "par_lines" {
9629 Ok(Expr {
9630 kind: ExprKind::ParLinesExpr {
9631 path: Box::new(args[0].clone()),
9632 callback: Box::new(args[1].clone()),
9633 progress: None,
9634 },
9635 line,
9636 })
9637 } else {
9638 Ok(Expr {
9639 kind: ExprKind::ParWalkExpr {
9640 path: Box::new(args[0].clone()),
9641 callback: Box::new(args[1].clone()),
9642 progress: None,
9643 },
9644 line,
9645 })
9646 }
9647 }
9648 "pwatch" | "watch" => {
9649 let args = self.parse_builtin_args()?;
9650 if args.len() < 2 {
9651 return Err(
9652 self.syntax_err(format!("{} requires at least two arguments", name), line)
9653 );
9654 }
9655 Ok(Expr {
9656 kind: ExprKind::PwatchExpr {
9657 path: Box::new(args[0].clone()),
9658 callback: Box::new(args[1].clone()),
9659 },
9660 line,
9661 })
9662 }
9663 "fan" => {
9664 let (count, block) = self.parse_fan_count_and_block(line)?;
9670 let progress = self.parse_fan_optional_progress("fan")?;
9671 Ok(Expr {
9672 kind: ExprKind::FanExpr {
9673 count,
9674 block,
9675 progress,
9676 capture: false,
9677 },
9678 line,
9679 })
9680 }
9681 "fan_cap" => {
9682 let (count, block) = self.parse_fan_count_and_block(line)?;
9683 let progress = self.parse_fan_optional_progress("fan_cap")?;
9684 Ok(Expr {
9685 kind: ExprKind::FanExpr {
9686 count,
9687 block,
9688 progress,
9689 capture: true,
9690 },
9691 line,
9692 })
9693 }
9694 "async" => {
9695 if !matches!(self.peek(), Token::LBrace) {
9696 return Err(self.syntax_err("async must be followed by { BLOCK }", line));
9697 }
9698 let block = self.parse_block()?;
9699 Ok(Expr {
9700 kind: ExprKind::AsyncBlock { body: block },
9701 line,
9702 })
9703 }
9704 "spawn" => {
9705 if !matches!(self.peek(), Token::LBrace) {
9706 return Err(self.syntax_err("spawn must be followed by { BLOCK }", line));
9707 }
9708 let block = self.parse_block()?;
9709 Ok(Expr {
9710 kind: ExprKind::SpawnBlock { body: block },
9711 line,
9712 })
9713 }
9714 "trace" => {
9715 if !matches!(self.peek(), Token::LBrace) {
9716 return Err(self.syntax_err("trace must be followed by { BLOCK }", line));
9717 }
9718 let block = self.parse_block()?;
9719 Ok(Expr {
9720 kind: ExprKind::Trace { body: block },
9721 line,
9722 })
9723 }
9724 "timer" => {
9725 let block = self.parse_block_or_bareword_block_no_args()?;
9726 Ok(Expr {
9727 kind: ExprKind::Timer { body: block },
9728 line,
9729 })
9730 }
9731 "bench" => {
9732 let block = self.parse_block_or_bareword_block_no_args()?;
9733 let times = Box::new(self.parse_expression()?);
9734 Ok(Expr {
9735 kind: ExprKind::Bench { body: block, times },
9736 line,
9737 })
9738 }
9739 "spinner" => {
9740 let (message, body) = if matches!(self.peek(), Token::LBrace) {
9742 let body = self.parse_block()?;
9743 (
9744 Box::new(Expr {
9745 kind: ExprKind::String("working".to_string()),
9746 line,
9747 }),
9748 body,
9749 )
9750 } else {
9751 let msg = self.parse_assign_expr()?;
9752 let body = self.parse_block()?;
9753 (Box::new(msg), body)
9754 };
9755 Ok(Expr {
9756 kind: ExprKind::Spinner { message, body },
9757 line,
9758 })
9759 }
9760 "thread" | "t" => {
9761 self.parse_thread_macro(line, false)
9771 }
9772 "retry" => {
9773 let body = if matches!(self.peek(), Token::LBrace) {
9776 self.parse_block()?
9777 } else {
9778 let bw_line = self.peek_line();
9779 let Token::Ident(ref name) = self.peek().clone() else {
9780 return Err(self
9781 .syntax_err("retry: expected block or bareword function name", line));
9782 };
9783 let name = name.clone();
9784 self.advance();
9785 vec![Statement::new(
9786 StmtKind::Expression(Expr {
9787 kind: ExprKind::FuncCall { name, args: vec![] },
9788 line: bw_line,
9789 }),
9790 bw_line,
9791 )]
9792 };
9793 self.eat(&Token::Comma);
9794 match self.peek() {
9795 Token::Ident(ref s) if s == "times" => {
9796 self.advance();
9797 }
9798 _ => {
9799 return Err(self.syntax_err("retry: expected `times =>` after block", line));
9800 }
9801 }
9802 self.expect(&Token::FatArrow)?;
9803 let times = Box::new(self.parse_assign_expr()?);
9804 let mut backoff = RetryBackoff::None;
9805 if self.eat(&Token::Comma) {
9806 match self.peek() {
9807 Token::Ident(ref s) if s == "backoff" => {
9808 self.advance();
9809 }
9810 _ => {
9811 return Err(
9812 self.syntax_err("retry: expected `backoff =>` after comma", line)
9813 );
9814 }
9815 }
9816 self.expect(&Token::FatArrow)?;
9817 let Token::Ident(mode) = self.peek().clone() else {
9818 return Err(self.syntax_err(
9819 "retry: expected backoff mode (none, linear, exponential)",
9820 line,
9821 ));
9822 };
9823 backoff = match mode.as_str() {
9824 "none" => RetryBackoff::None,
9825 "linear" => RetryBackoff::Linear,
9826 "exponential" => RetryBackoff::Exponential,
9827 _ => {
9828 return Err(
9829 self.syntax_err(format!("retry: invalid backoff `{mode}`"), line)
9830 );
9831 }
9832 };
9833 self.advance();
9834 }
9835 Ok(Expr {
9836 kind: ExprKind::RetryBlock {
9837 body,
9838 times,
9839 backoff,
9840 },
9841 line,
9842 })
9843 }
9844 "rate_limit" => {
9845 self.expect(&Token::LParen)?;
9846 let max = Box::new(self.parse_assign_expr()?);
9847 self.expect(&Token::Comma)?;
9848 let window = Box::new(self.parse_assign_expr()?);
9849 self.expect(&Token::RParen)?;
9850 let body = self.parse_block_or_bareword_block_no_args()?;
9851 let slot = self.alloc_rate_limit_slot();
9852 Ok(Expr {
9853 kind: ExprKind::RateLimitBlock {
9854 slot,
9855 max,
9856 window,
9857 body,
9858 },
9859 line,
9860 })
9861 }
9862 "every" => {
9863 let has_paren = self.eat(&Token::LParen);
9866 let interval = Box::new(self.parse_assign_expr()?);
9867 if has_paren {
9868 self.expect(&Token::RParen)?;
9869 }
9870 let body = if matches!(self.peek(), Token::LBrace) {
9871 self.parse_block()?
9872 } else {
9873 let bline = self.peek_line();
9874 let expr = self.parse_assign_expr()?;
9875 vec![Statement::new(StmtKind::Expression(expr), bline)]
9876 };
9877 Ok(Expr {
9878 kind: ExprKind::EveryBlock { interval, body },
9879 line,
9880 })
9881 }
9882 "gen" => {
9883 if !matches!(self.peek(), Token::LBrace) {
9884 return Err(self.syntax_err("gen must be followed by { BLOCK }", line));
9885 }
9886 let body = self.parse_block()?;
9887 Ok(Expr {
9888 kind: ExprKind::GenBlock { body },
9889 line,
9890 })
9891 }
9892 "yield" => {
9893 let e = self.parse_assign_expr()?;
9894 Ok(Expr {
9895 kind: ExprKind::Yield(Box::new(e)),
9896 line,
9897 })
9898 }
9899 "await" => {
9900 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9901 return Ok(e);
9902 }
9903 let a = self.parse_one_arg_or_default()?;
9906 Ok(Expr {
9907 kind: ExprKind::Await(Box::new(a)),
9908 line,
9909 })
9910 }
9911 "slurp" | "cat" | "c" => {
9912 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9913 return Ok(e);
9914 }
9915 let a = self.parse_one_arg_or_default()?;
9916 Ok(Expr {
9917 kind: ExprKind::Slurp(Box::new(a)),
9918 line,
9919 })
9920 }
9921 "capture" => {
9922 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9923 return Ok(e);
9924 }
9925 let a = self.parse_one_arg()?;
9926 Ok(Expr {
9927 kind: ExprKind::Capture(Box::new(a)),
9928 line,
9929 })
9930 }
9931 "fetch_url" => {
9932 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9933 return Ok(e);
9934 }
9935 let a = self.parse_one_arg()?;
9936 Ok(Expr {
9937 kind: ExprKind::FetchUrl(Box::new(a)),
9938 line,
9939 })
9940 }
9941 "pchannel" => {
9942 let capacity = if self.eat(&Token::LParen) {
9943 if matches!(self.peek(), Token::RParen) {
9944 self.advance();
9945 None
9946 } else {
9947 let e = self.parse_expression()?;
9948 self.expect(&Token::RParen)?;
9949 Some(Box::new(e))
9950 }
9951 } else {
9952 None
9953 };
9954 Ok(Expr {
9955 kind: ExprKind::Pchannel { capacity },
9956 line,
9957 })
9958 }
9959 "psort" => {
9960 if matches!(self.peek(), Token::LBrace)
9961 || matches!(self.peek(), Token::ScalarVar(ref v) if v == "a" || v == "b")
9962 || matches!(self.peek(), Token::Ident(ref name) if !Self::is_known_bareword(name))
9963 {
9964 let block = self.parse_block_or_bareword_cmp_block()?;
9965 self.eat(&Token::Comma);
9966 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
9967 Ok(Expr {
9968 kind: ExprKind::PSortExpr {
9969 cmp: Some(block),
9970 list: Box::new(list),
9971 progress: progress.map(Box::new),
9972 },
9973 line,
9974 })
9975 } else {
9976 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
9977 Ok(Expr {
9978 kind: ExprKind::PSortExpr {
9979 cmp: None,
9980 list: Box::new(list),
9981 progress: progress.map(Box::new),
9982 },
9983 line,
9984 })
9985 }
9986 }
9987 "preduce" => {
9988 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
9989 Ok(Expr {
9990 kind: ExprKind::PReduceExpr {
9991 block,
9992 list: Box::new(list),
9993 progress: progress.map(Box::new),
9994 },
9995 line,
9996 })
9997 }
9998 "preduce_init" => {
9999 let (init, block, list, progress) =
10000 self.parse_init_block_then_list_optional_progress()?;
10001 Ok(Expr {
10002 kind: ExprKind::PReduceInitExpr {
10003 init: Box::new(init),
10004 block,
10005 list: Box::new(list),
10006 progress: progress.map(Box::new),
10007 },
10008 line,
10009 })
10010 }
10011 "pmap_reduce" => {
10012 let map_block = self.parse_block_or_bareword_block()?;
10013 let reduce_block = if matches!(self.peek(), Token::LBrace) {
10016 self.parse_block()?
10017 } else {
10018 self.expect(&Token::Comma)?;
10020 self.parse_block_or_bareword_cmp_block()?
10021 };
10022 self.eat(&Token::Comma);
10023 let line = self.peek_line();
10024 if let Token::Ident(ref kw) = self.peek().clone() {
10025 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
10026 self.advance();
10027 self.expect(&Token::FatArrow)?;
10028 let prog = self.parse_assign_expr()?;
10029 return Ok(Expr {
10030 kind: ExprKind::PMapReduceExpr {
10031 map_block,
10032 reduce_block,
10033 list: Box::new(Expr {
10034 kind: ExprKind::List(vec![]),
10035 line,
10036 }),
10037 progress: Some(Box::new(prog)),
10038 },
10039 line,
10040 });
10041 }
10042 }
10043 if matches!(
10044 self.peek(),
10045 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof
10046 ) {
10047 return Ok(Expr {
10048 kind: ExprKind::PMapReduceExpr {
10049 map_block,
10050 reduce_block,
10051 list: Box::new(Expr {
10052 kind: ExprKind::List(vec![]),
10053 line,
10054 }),
10055 progress: None,
10056 },
10057 line,
10058 });
10059 }
10060 let mut parts = vec![self.parse_assign_expr()?];
10061 loop {
10062 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
10063 break;
10064 }
10065 if matches!(
10066 self.peek(),
10067 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof
10068 ) {
10069 break;
10070 }
10071 if let Token::Ident(ref kw) = self.peek().clone() {
10072 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
10073 self.advance();
10074 self.expect(&Token::FatArrow)?;
10075 let prog = self.parse_assign_expr()?;
10076 return Ok(Expr {
10077 kind: ExprKind::PMapReduceExpr {
10078 map_block,
10079 reduce_block,
10080 list: Box::new(merge_expr_list(parts)),
10081 progress: Some(Box::new(prog)),
10082 },
10083 line,
10084 });
10085 }
10086 }
10087 parts.push(self.parse_assign_expr()?);
10088 }
10089 Ok(Expr {
10090 kind: ExprKind::PMapReduceExpr {
10091 map_block,
10092 reduce_block,
10093 list: Box::new(merge_expr_list(parts)),
10094 progress: None,
10095 },
10096 line,
10097 })
10098 }
10099 "puniq" => {
10100 if self.pipe_supplies_slurped_list_operand() {
10101 return Ok(Expr {
10102 kind: ExprKind::FuncCall {
10103 name: "puniq".to_string(),
10104 args: vec![],
10105 },
10106 line,
10107 });
10108 }
10109 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
10110 let mut args = vec![list];
10111 if let Some(p) = progress {
10112 args.push(p);
10113 }
10114 Ok(Expr {
10115 kind: ExprKind::FuncCall {
10116 name: "puniq".to_string(),
10117 args,
10118 },
10119 line,
10120 })
10121 }
10122 "pfirst" => {
10123 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
10124 let cr = Expr {
10125 kind: ExprKind::CodeRef {
10126 params: vec![],
10127 body: block,
10128 },
10129 line,
10130 };
10131 let mut args = vec![cr, list];
10132 if let Some(p) = progress {
10133 args.push(p);
10134 }
10135 Ok(Expr {
10136 kind: ExprKind::FuncCall {
10137 name: "pfirst".to_string(),
10138 args,
10139 },
10140 line,
10141 })
10142 }
10143 "pany" => {
10144 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
10145 let cr = Expr {
10146 kind: ExprKind::CodeRef {
10147 params: vec![],
10148 body: block,
10149 },
10150 line,
10151 };
10152 let mut args = vec![cr, list];
10153 if let Some(p) = progress {
10154 args.push(p);
10155 }
10156 Ok(Expr {
10157 kind: ExprKind::FuncCall {
10158 name: "pany".to_string(),
10159 args,
10160 },
10161 line,
10162 })
10163 }
10164 "uniq" | "distinct" => {
10165 if self.pipe_supplies_slurped_list_operand() {
10166 return Ok(Expr {
10167 kind: ExprKind::FuncCall {
10168 name: name.clone(),
10169 args: vec![],
10170 },
10171 line,
10172 });
10173 }
10174 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
10175 if progress.is_some() {
10176 return Err(self.syntax_err(
10177 "`progress =>` is not supported for uniq (use puniq for parallel + progress)",
10178 line,
10179 ));
10180 }
10181 Ok(Expr {
10182 kind: ExprKind::FuncCall {
10183 name: name.clone(),
10184 args: vec![list],
10185 },
10186 line,
10187 })
10188 }
10189 "flatten" => {
10190 if self.pipe_supplies_slurped_list_operand() {
10191 return Ok(Expr {
10192 kind: ExprKind::FuncCall {
10193 name: "flatten".to_string(),
10194 args: vec![],
10195 },
10196 line,
10197 });
10198 }
10199 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
10200 if progress.is_some() {
10201 return Err(self.syntax_err("`progress =>` is not supported for flatten", line));
10202 }
10203 Ok(Expr {
10204 kind: ExprKind::FuncCall {
10205 name: "flatten".to_string(),
10206 args: vec![list],
10207 },
10208 line,
10209 })
10210 }
10211 "set" => {
10212 if self.pipe_supplies_slurped_list_operand() {
10213 return Ok(Expr {
10214 kind: ExprKind::FuncCall {
10215 name: "set".to_string(),
10216 args: vec![],
10217 },
10218 line,
10219 });
10220 }
10221 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
10222 if progress.is_some() {
10223 return Err(self.syntax_err("`progress =>` is not supported for set", line));
10224 }
10225 Ok(Expr {
10226 kind: ExprKind::FuncCall {
10227 name: "set".to_string(),
10228 args: vec![list],
10229 },
10230 line,
10231 })
10232 }
10233 "size" => {
10237 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10238 return Ok(e);
10239 }
10240 if self.pipe_supplies_slurped_list_operand() {
10241 return Ok(Expr {
10242 kind: ExprKind::FuncCall {
10243 name: "size".to_string(),
10244 args: vec![],
10245 },
10246 line,
10247 });
10248 }
10249 let a = self.parse_one_arg_or_default()?;
10250 Ok(Expr {
10251 kind: ExprKind::FuncCall {
10252 name: "size".to_string(),
10253 args: vec![a],
10254 },
10255 line,
10256 })
10257 }
10258 "list_count" | "list_size" | "count" | "len" | "cnt" => {
10259 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10260 return Ok(e);
10261 }
10262 if self.pipe_supplies_slurped_list_operand() {
10263 return Ok(Expr {
10264 kind: ExprKind::FuncCall {
10265 name: name.clone(),
10266 args: vec![],
10267 },
10268 line,
10269 });
10270 }
10271 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
10272 if progress.is_some() {
10273 return Err(self.syntax_err(
10274 "`progress =>` is not supported for list_count / list_size / count / cnt",
10275 line,
10276 ));
10277 }
10278 Ok(Expr {
10279 kind: ExprKind::FuncCall {
10280 name: name.clone(),
10281 args: vec![list],
10282 },
10283 line,
10284 })
10285 }
10286 "shuffle" | "shuffled" => {
10287 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10288 return Ok(e);
10289 }
10290 if self.pipe_supplies_slurped_list_operand() {
10291 return Ok(Expr {
10292 kind: ExprKind::FuncCall {
10293 name: "shuffle".to_string(),
10294 args: vec![],
10295 },
10296 line,
10297 });
10298 }
10299 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
10300 if progress.is_some() {
10301 return Err(self.syntax_err("`progress =>` is not supported for shuffle", line));
10302 }
10303 Ok(Expr {
10304 kind: ExprKind::FuncCall {
10305 name: "shuffle".to_string(),
10306 args: vec![list],
10307 },
10308 line,
10309 })
10310 }
10311 "chunked" => {
10312 let mut parts = Vec::new();
10313 if self.eat(&Token::LParen) {
10314 if !matches!(self.peek(), Token::RParen) {
10315 parts.push(self.parse_assign_expr()?);
10316 while self.eat(&Token::Comma) {
10317 if matches!(self.peek(), Token::RParen) {
10318 break;
10319 }
10320 parts.push(self.parse_assign_expr()?);
10321 }
10322 }
10323 self.expect(&Token::RParen)?;
10324 } else {
10325 parts.push(self.parse_assign_expr_stop_at_pipe()?);
10329 loop {
10330 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
10331 break;
10332 }
10333 if matches!(
10334 self.peek(),
10335 Token::Semicolon
10336 | Token::RBrace
10337 | Token::RParen
10338 | Token::Eof
10339 | Token::PipeForward
10340 ) {
10341 break;
10342 }
10343 if self.peek_is_postfix_stmt_modifier_keyword() {
10344 break;
10345 }
10346 parts.push(self.parse_assign_expr_stop_at_pipe()?);
10347 }
10348 }
10349 if parts.len() == 1 {
10350 let n = parts.pop().unwrap();
10351 return Ok(Expr {
10352 kind: ExprKind::FuncCall {
10353 name: "chunked".to_string(),
10354 args: vec![n],
10355 },
10356 line,
10357 });
10358 }
10359 if parts.is_empty() {
10360 return Ok(Expr {
10361 kind: ExprKind::FuncCall {
10362 name: "chunked".to_string(),
10363 args: parts,
10364 },
10365 line,
10366 });
10367 }
10368 if parts.len() == 2 {
10369 let n = parts.pop().unwrap();
10370 let list = parts.pop().unwrap();
10371 return Ok(Expr {
10372 kind: ExprKind::FuncCall {
10373 name: "chunked".to_string(),
10374 args: vec![list, n],
10375 },
10376 line,
10377 });
10378 }
10379 Err(self.syntax_err(
10380 "chunked: use LIST |> chunked(N) or chunked((1,2,3), 2)",
10381 line,
10382 ))
10383 }
10384 "windowed" => {
10385 let mut parts = Vec::new();
10386 if self.eat(&Token::LParen) {
10387 if !matches!(self.peek(), Token::RParen) {
10388 parts.push(self.parse_assign_expr()?);
10389 while self.eat(&Token::Comma) {
10390 if matches!(self.peek(), Token::RParen) {
10391 break;
10392 }
10393 parts.push(self.parse_assign_expr()?);
10394 }
10395 }
10396 self.expect(&Token::RParen)?;
10397 } else {
10398 parts.push(self.parse_assign_expr_stop_at_pipe()?);
10401 loop {
10402 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
10403 break;
10404 }
10405 if matches!(
10406 self.peek(),
10407 Token::Semicolon
10408 | Token::RBrace
10409 | Token::RParen
10410 | Token::Eof
10411 | Token::PipeForward
10412 ) {
10413 break;
10414 }
10415 if self.peek_is_postfix_stmt_modifier_keyword() {
10416 break;
10417 }
10418 parts.push(self.parse_assign_expr_stop_at_pipe()?);
10419 }
10420 }
10421 if parts.len() == 1 {
10422 let n = parts.pop().unwrap();
10423 return Ok(Expr {
10424 kind: ExprKind::FuncCall {
10425 name: "windowed".to_string(),
10426 args: vec![n],
10427 },
10428 line,
10429 });
10430 }
10431 if parts.is_empty() {
10432 return Ok(Expr {
10433 kind: ExprKind::FuncCall {
10434 name: "windowed".to_string(),
10435 args: parts,
10436 },
10437 line,
10438 });
10439 }
10440 if parts.len() == 2 {
10441 let n = parts.pop().unwrap();
10442 let list = parts.pop().unwrap();
10443 return Ok(Expr {
10444 kind: ExprKind::FuncCall {
10445 name: "windowed".to_string(),
10446 args: vec![list, n],
10447 },
10448 line,
10449 });
10450 }
10451 Err(self.syntax_err(
10452 "windowed: use LIST |> windowed(N) or windowed((1,2,3), 2)",
10453 line,
10454 ))
10455 }
10456 "any" | "all" | "none" => {
10457 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
10458 if progress.is_some() {
10459 return Err(self.syntax_err(
10460 "`progress =>` is not supported for any/all/none (use pany for parallel + progress)",
10461 line,
10462 ));
10463 }
10464 let cr = Expr {
10465 kind: ExprKind::CodeRef {
10466 params: vec![],
10467 body: block,
10468 },
10469 line,
10470 };
10471 Ok(Expr {
10472 kind: ExprKind::FuncCall {
10473 name: name.clone(),
10474 args: vec![cr, list],
10475 },
10476 line,
10477 })
10478 }
10479 "first" | "detect" | "find" => {
10481 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
10482 if progress.is_some() {
10483 return Err(self.syntax_err(
10484 "`progress =>` is not supported for first/detect/find (use pfirst for parallel + progress)",
10485 line,
10486 ));
10487 }
10488 let cr = Expr {
10489 kind: ExprKind::CodeRef {
10490 params: vec![],
10491 body: block,
10492 },
10493 line,
10494 };
10495 Ok(Expr {
10496 kind: ExprKind::FuncCall {
10497 name: "first".to_string(),
10498 args: vec![cr, list],
10499 },
10500 line,
10501 })
10502 }
10503 "take_while" | "drop_while" | "skip_while" | "reject" | "tap" | "peek"
10504 | "partition" | "min_by" | "max_by" | "zip_with" | "count_by" => {
10505 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
10506 if progress.is_some() {
10507 return Err(
10508 self.syntax_err(format!("`progress =>` is not supported for {name}"), line)
10509 );
10510 }
10511 let cr = Expr {
10512 kind: ExprKind::CodeRef {
10513 params: vec![],
10514 body: block,
10515 },
10516 line,
10517 };
10518 Ok(Expr {
10519 kind: ExprKind::FuncCall {
10520 name: name.to_string(),
10521 args: vec![cr, list],
10522 },
10523 line,
10524 })
10525 }
10526 "group_by" | "chunk_by" => {
10527 if matches!(self.peek(), Token::LBrace) {
10528 let (block, list) = self.parse_block_list()?;
10529 let cr = Expr {
10530 kind: ExprKind::CodeRef {
10531 params: vec![],
10532 body: block,
10533 },
10534 line,
10535 };
10536 Ok(Expr {
10537 kind: ExprKind::FuncCall {
10538 name: name.to_string(),
10539 args: vec![cr, list],
10540 },
10541 line,
10542 })
10543 } else {
10544 let key_expr = self.parse_assign_expr()?;
10545 self.expect(&Token::Comma)?;
10546 let list_parts = self.parse_list_until_terminator()?;
10547 let list_expr = if list_parts.len() == 1 {
10548 list_parts.into_iter().next().unwrap()
10549 } else {
10550 Expr {
10551 kind: ExprKind::List(list_parts),
10552 line,
10553 }
10554 };
10555 Ok(Expr {
10556 kind: ExprKind::FuncCall {
10557 name: name.to_string(),
10558 args: vec![key_expr, list_expr],
10559 },
10560 line,
10561 })
10562 }
10563 }
10564 "with_index" => {
10565 if self.pipe_supplies_slurped_list_operand() {
10566 return Ok(Expr {
10567 kind: ExprKind::FuncCall {
10568 name: "with_index".to_string(),
10569 args: vec![],
10570 },
10571 line,
10572 });
10573 }
10574 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
10575 if progress.is_some() {
10576 return Err(
10577 self.syntax_err("`progress =>` is not supported for with_index", line)
10578 );
10579 }
10580 Ok(Expr {
10581 kind: ExprKind::FuncCall {
10582 name: "with_index".to_string(),
10583 args: vec![list],
10584 },
10585 line,
10586 })
10587 }
10588 "pcache" => {
10589 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
10590 Ok(Expr {
10591 kind: ExprKind::PcacheExpr {
10592 block,
10593 list: Box::new(list),
10594 progress: progress.map(Box::new),
10595 },
10596 line,
10597 })
10598 }
10599 "pselect" => {
10600 let paren = self.eat(&Token::LParen);
10601 let (receivers, timeout) = self.parse_comma_expr_list_with_timeout_tail(paren)?;
10602 if paren {
10603 self.expect(&Token::RParen)?;
10604 }
10605 if receivers.is_empty() {
10606 return Err(self.syntax_err("pselect needs at least one receiver", line));
10607 }
10608 Ok(Expr {
10609 kind: ExprKind::PselectExpr {
10610 receivers,
10611 timeout: timeout.map(Box::new),
10612 },
10613 line,
10614 })
10615 }
10616 "open" => {
10617 let paren = matches!(self.peek(), Token::LParen);
10618 if paren {
10619 self.advance();
10620 }
10621 if matches!(self.peek(), Token::Ident(ref s) if s == "my") {
10622 self.advance();
10623 let name = self.parse_scalar_var_name()?;
10624 self.expect(&Token::Comma)?;
10625 let mode = self.parse_assign_expr()?;
10626 let file = if self.eat(&Token::Comma) {
10627 Some(self.parse_assign_expr()?)
10628 } else {
10629 None
10630 };
10631 if paren {
10632 self.expect(&Token::RParen)?;
10633 }
10634 Ok(Expr {
10635 kind: ExprKind::Open {
10636 handle: Box::new(Expr {
10637 kind: ExprKind::OpenMyHandle { name },
10638 line,
10639 }),
10640 mode: Box::new(mode),
10641 file: file.map(Box::new),
10642 },
10643 line,
10644 })
10645 } else {
10646 let args = if paren {
10647 self.parse_arg_list()?
10648 } else {
10649 self.parse_list_until_terminator()?
10650 };
10651 if paren {
10652 self.expect(&Token::RParen)?;
10653 }
10654 if args.len() < 2 {
10655 return Err(self.syntax_err("open requires at least 2 arguments", line));
10656 }
10657 Ok(Expr {
10658 kind: ExprKind::Open {
10659 handle: Box::new(args[0].clone()),
10660 mode: Box::new(args[1].clone()),
10661 file: args.get(2).cloned().map(Box::new),
10662 },
10663 line,
10664 })
10665 }
10666 }
10667 "close" => {
10668 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10669 return Ok(e);
10670 }
10671 let a = self.parse_one_arg_or_default()?;
10672 Ok(Expr {
10673 kind: ExprKind::Close(Box::new(a)),
10674 line,
10675 })
10676 }
10677 "opendir" => {
10678 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10679 return Ok(e);
10680 }
10681 let args = self.parse_builtin_args()?;
10682 if args.len() != 2 {
10683 return Err(self.syntax_err("opendir requires two arguments", line));
10684 }
10685 Ok(Expr {
10686 kind: ExprKind::Opendir {
10687 handle: Box::new(args[0].clone()),
10688 path: Box::new(args[1].clone()),
10689 },
10690 line,
10691 })
10692 }
10693 "readdir" => {
10694 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10695 return Ok(e);
10696 }
10697 let a = self.parse_one_arg()?;
10698 Ok(Expr {
10699 kind: ExprKind::Readdir(Box::new(a)),
10700 line,
10701 })
10702 }
10703 "closedir" => {
10704 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10705 return Ok(e);
10706 }
10707 let a = self.parse_one_arg()?;
10708 Ok(Expr {
10709 kind: ExprKind::Closedir(Box::new(a)),
10710 line,
10711 })
10712 }
10713 "rewinddir" => {
10714 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10715 return Ok(e);
10716 }
10717 let a = self.parse_one_arg()?;
10718 Ok(Expr {
10719 kind: ExprKind::Rewinddir(Box::new(a)),
10720 line,
10721 })
10722 }
10723 "telldir" => {
10724 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10725 return Ok(e);
10726 }
10727 let a = self.parse_one_arg()?;
10728 Ok(Expr {
10729 kind: ExprKind::Telldir(Box::new(a)),
10730 line,
10731 })
10732 }
10733 "seekdir" => {
10734 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10735 return Ok(e);
10736 }
10737 let args = self.parse_builtin_args()?;
10738 if args.len() != 2 {
10739 return Err(self.syntax_err("seekdir requires two arguments", line));
10740 }
10741 Ok(Expr {
10742 kind: ExprKind::Seekdir {
10743 handle: Box::new(args[0].clone()),
10744 position: Box::new(args[1].clone()),
10745 },
10746 line,
10747 })
10748 }
10749 "eof" => {
10750 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10751 return Ok(e);
10752 }
10753 if matches!(self.peek(), Token::LParen) {
10754 self.advance();
10755 if matches!(self.peek(), Token::RParen) {
10756 self.advance();
10757 Ok(Expr {
10758 kind: ExprKind::Eof(None),
10759 line,
10760 })
10761 } else {
10762 let a = self.parse_expression()?;
10763 self.expect(&Token::RParen)?;
10764 Ok(Expr {
10765 kind: ExprKind::Eof(Some(Box::new(a))),
10766 line,
10767 })
10768 }
10769 } else {
10770 Ok(Expr {
10771 kind: ExprKind::Eof(None),
10772 line,
10773 })
10774 }
10775 }
10776 "system" => {
10777 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10778 return Ok(e);
10779 }
10780 let args = self.parse_builtin_args()?;
10781 Ok(Expr {
10782 kind: ExprKind::System(args),
10783 line,
10784 })
10785 }
10786 "exec" => {
10787 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10788 return Ok(e);
10789 }
10790 let args = self.parse_builtin_args()?;
10791 Ok(Expr {
10792 kind: ExprKind::Exec(args),
10793 line,
10794 })
10795 }
10796 "eval" => {
10797 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10798 return Ok(e);
10799 }
10800 let a = if matches!(self.peek(), Token::LBrace) {
10801 let block = self.parse_block()?;
10802 Expr {
10803 kind: ExprKind::CodeRef {
10804 params: vec![],
10805 body: block,
10806 },
10807 line,
10808 }
10809 } else {
10810 self.parse_one_arg_or_default()?
10811 };
10812 Ok(Expr {
10813 kind: ExprKind::Eval(Box::new(a)),
10814 line,
10815 })
10816 }
10817 "do" => {
10818 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10819 return Ok(e);
10820 }
10821 let a = self.parse_one_arg()?;
10822 Ok(Expr {
10823 kind: ExprKind::Do(Box::new(a)),
10824 line,
10825 })
10826 }
10827 "require" => {
10828 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10829 return Ok(e);
10830 }
10831 let a = self.parse_one_arg()?;
10832 Ok(Expr {
10833 kind: ExprKind::Require(Box::new(a)),
10834 line,
10835 })
10836 }
10837 "exit" => {
10838 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10839 return Ok(e);
10840 }
10841 if matches!(
10842 self.peek(),
10843 Token::Semicolon | Token::RBrace | Token::Eof | Token::PipeForward
10844 ) {
10845 Ok(Expr {
10846 kind: ExprKind::Exit(None),
10847 line,
10848 })
10849 } else {
10850 let a = self.parse_one_arg()?;
10851 Ok(Expr {
10852 kind: ExprKind::Exit(Some(Box::new(a))),
10853 line,
10854 })
10855 }
10856 }
10857 "chdir" => {
10858 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10859 return Ok(e);
10860 }
10861 let a = self.parse_one_arg_or_default()?;
10862 Ok(Expr {
10863 kind: ExprKind::Chdir(Box::new(a)),
10864 line,
10865 })
10866 }
10867 "mkdir" => {
10868 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10869 return Ok(e);
10870 }
10871 let args = self.parse_builtin_args()?;
10872 Ok(Expr {
10873 kind: ExprKind::Mkdir {
10874 path: Box::new(args[0].clone()),
10875 mode: args.get(1).cloned().map(Box::new),
10876 },
10877 line,
10878 })
10879 }
10880 "unlink" | "rm" => {
10881 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10882 return Ok(e);
10883 }
10884 let args = self.parse_builtin_args()?;
10885 Ok(Expr {
10886 kind: ExprKind::Unlink(args),
10887 line,
10888 })
10889 }
10890 "rename" => {
10891 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10892 return Ok(e);
10893 }
10894 let args = self.parse_builtin_args()?;
10895 if args.len() != 2 {
10896 return Err(self.syntax_err("rename requires two arguments", line));
10897 }
10898 Ok(Expr {
10899 kind: ExprKind::Rename {
10900 old: Box::new(args[0].clone()),
10901 new: Box::new(args[1].clone()),
10902 },
10903 line,
10904 })
10905 }
10906 "chmod" => {
10907 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10908 return Ok(e);
10909 }
10910 let args = self.parse_builtin_args()?;
10911 if args.len() < 2 {
10912 return Err(self.syntax_err("chmod requires mode and at least one file", line));
10913 }
10914 Ok(Expr {
10915 kind: ExprKind::Chmod(args),
10916 line,
10917 })
10918 }
10919 "chown" => {
10920 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10921 return Ok(e);
10922 }
10923 let args = self.parse_builtin_args()?;
10924 if args.len() < 3 {
10925 return Err(
10926 self.syntax_err("chown requires uid, gid, and at least one file", line)
10927 );
10928 }
10929 Ok(Expr {
10930 kind: ExprKind::Chown(args),
10931 line,
10932 })
10933 }
10934 "stat" => {
10935 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10936 return Ok(e);
10937 }
10938 let args = self.parse_builtin_args()?;
10939 let arg = if args.len() == 1 {
10940 args[0].clone()
10941 } else if args.is_empty() {
10942 Expr {
10943 kind: ExprKind::ScalarVar("_".into()),
10944 line,
10945 }
10946 } else {
10947 return Err(self.syntax_err("stat requires zero or one argument", line));
10948 };
10949 Ok(Expr {
10950 kind: ExprKind::Stat(Box::new(arg)),
10951 line,
10952 })
10953 }
10954 "lstat" => {
10955 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10956 return Ok(e);
10957 }
10958 let args = self.parse_builtin_args()?;
10959 let arg = if args.len() == 1 {
10960 args[0].clone()
10961 } else if args.is_empty() {
10962 Expr {
10963 kind: ExprKind::ScalarVar("_".into()),
10964 line,
10965 }
10966 } else {
10967 return Err(self.syntax_err("lstat requires zero or one argument", line));
10968 };
10969 Ok(Expr {
10970 kind: ExprKind::Lstat(Box::new(arg)),
10971 line,
10972 })
10973 }
10974 "link" => {
10975 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10976 return Ok(e);
10977 }
10978 let args = self.parse_builtin_args()?;
10979 if args.len() != 2 {
10980 return Err(self.syntax_err("link requires two arguments", line));
10981 }
10982 Ok(Expr {
10983 kind: ExprKind::Link {
10984 old: Box::new(args[0].clone()),
10985 new: Box::new(args[1].clone()),
10986 },
10987 line,
10988 })
10989 }
10990 "symlink" => {
10991 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10992 return Ok(e);
10993 }
10994 let args = self.parse_builtin_args()?;
10995 if args.len() != 2 {
10996 return Err(self.syntax_err("symlink requires two arguments", line));
10997 }
10998 Ok(Expr {
10999 kind: ExprKind::Symlink {
11000 old: Box::new(args[0].clone()),
11001 new: Box::new(args[1].clone()),
11002 },
11003 line,
11004 })
11005 }
11006 "readlink" => {
11007 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11008 return Ok(e);
11009 }
11010 let args = self.parse_builtin_args()?;
11011 let arg = if args.len() == 1 {
11012 args[0].clone()
11013 } else if args.is_empty() {
11014 Expr {
11015 kind: ExprKind::ScalarVar("_".into()),
11016 line,
11017 }
11018 } else {
11019 return Err(self.syntax_err("readlink requires zero or one argument", line));
11020 };
11021 Ok(Expr {
11022 kind: ExprKind::Readlink(Box::new(arg)),
11023 line,
11024 })
11025 }
11026 "files" => {
11027 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11028 return Ok(e);
11029 }
11030 let args = self.parse_builtin_args()?;
11031 Ok(Expr {
11032 kind: ExprKind::Files(args),
11033 line,
11034 })
11035 }
11036 "filesf" | "f" => {
11037 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11038 return Ok(e);
11039 }
11040 let args = self.parse_builtin_args()?;
11041 Ok(Expr {
11042 kind: ExprKind::Filesf(args),
11043 line,
11044 })
11045 }
11046 "fr" => {
11047 let args = self.parse_builtin_args()?;
11048 Ok(Expr {
11049 kind: ExprKind::FilesfRecursive(args),
11050 line,
11051 })
11052 }
11053 "dirs" | "d" => {
11054 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11055 return Ok(e);
11056 }
11057 let args = self.parse_builtin_args()?;
11058 Ok(Expr {
11059 kind: ExprKind::Dirs(args),
11060 line,
11061 })
11062 }
11063 "dr" => {
11064 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11065 return Ok(e);
11066 }
11067 let args = self.parse_builtin_args()?;
11068 Ok(Expr {
11069 kind: ExprKind::DirsRecursive(args),
11070 line,
11071 })
11072 }
11073 "sym_links" => {
11074 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11075 return Ok(e);
11076 }
11077 let args = self.parse_builtin_args()?;
11078 Ok(Expr {
11079 kind: ExprKind::SymLinks(args),
11080 line,
11081 })
11082 }
11083 "sockets" => {
11084 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11085 return Ok(e);
11086 }
11087 let args = self.parse_builtin_args()?;
11088 Ok(Expr {
11089 kind: ExprKind::Sockets(args),
11090 line,
11091 })
11092 }
11093 "pipes" => {
11094 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11095 return Ok(e);
11096 }
11097 let args = self.parse_builtin_args()?;
11098 Ok(Expr {
11099 kind: ExprKind::Pipes(args),
11100 line,
11101 })
11102 }
11103 "block_devices" => {
11104 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11105 return Ok(e);
11106 }
11107 let args = self.parse_builtin_args()?;
11108 Ok(Expr {
11109 kind: ExprKind::BlockDevices(args),
11110 line,
11111 })
11112 }
11113 "char_devices" => {
11114 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11115 return Ok(e);
11116 }
11117 let args = self.parse_builtin_args()?;
11118 Ok(Expr {
11119 kind: ExprKind::CharDevices(args),
11120 line,
11121 })
11122 }
11123 "exe" | "executables" => {
11124 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11125 return Ok(e);
11126 }
11127 let args = self.parse_builtin_args()?;
11128 Ok(Expr {
11129 kind: ExprKind::Executables(args),
11130 line,
11131 })
11132 }
11133 "glob" => {
11134 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11135 return Ok(e);
11136 }
11137 let args = self.parse_builtin_args()?;
11138 Ok(Expr {
11139 kind: ExprKind::Glob(args),
11140 line,
11141 })
11142 }
11143 "glob_par" => {
11144 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11145 return Ok(e);
11146 }
11147 let (args, progress) = self.parse_glob_par_or_par_sed_args()?;
11148 Ok(Expr {
11149 kind: ExprKind::GlobPar { args, progress },
11150 line,
11151 })
11152 }
11153 "par_sed" => {
11154 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11155 return Ok(e);
11156 }
11157 let (args, progress) = self.parse_glob_par_or_par_sed_args()?;
11158 Ok(Expr {
11159 kind: ExprKind::ParSed { args, progress },
11160 line,
11161 })
11162 }
11163 "bless" => {
11164 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11165 return Ok(e);
11166 }
11167 let args = self.parse_builtin_args()?;
11168 Ok(Expr {
11169 kind: ExprKind::Bless {
11170 ref_expr: Box::new(args[0].clone()),
11171 class: args.get(1).cloned().map(Box::new),
11172 },
11173 line,
11174 })
11175 }
11176 "caller" => {
11177 if matches!(self.peek(), Token::LParen) {
11178 self.advance();
11179 if matches!(self.peek(), Token::RParen) {
11180 self.advance();
11181 Ok(Expr {
11182 kind: ExprKind::Caller(None),
11183 line,
11184 })
11185 } else {
11186 let a = self.parse_expression()?;
11187 self.expect(&Token::RParen)?;
11188 Ok(Expr {
11189 kind: ExprKind::Caller(Some(Box::new(a))),
11190 line,
11191 })
11192 }
11193 } else {
11194 Ok(Expr {
11195 kind: ExprKind::Caller(None),
11196 line,
11197 })
11198 }
11199 }
11200 "wantarray" => {
11201 if matches!(self.peek(), Token::LParen) {
11202 self.advance();
11203 self.expect(&Token::RParen)?;
11204 }
11205 Ok(Expr {
11206 kind: ExprKind::Wantarray,
11207 line,
11208 })
11209 }
11210 "sub" => {
11211 if !crate::compat_mode() {
11213 return Err(self.syntax_err(
11214 "stryke uses `fn {}` instead of `fn {}` (this is not Perl 5)",
11215 line,
11216 ));
11217 }
11218 let (params, _prototype) = self.parse_sub_sig_or_prototype_opt()?;
11220 let body = self.parse_block()?;
11221 Ok(Expr {
11222 kind: ExprKind::CodeRef { params, body },
11223 line,
11224 })
11225 }
11226 "fn" => {
11227 let (params, _prototype) = self.parse_sub_sig_or_prototype_opt()?;
11229 let body = self.parse_block()?;
11230 Ok(Expr {
11231 kind: ExprKind::CodeRef { params, body },
11232 line,
11233 })
11234 }
11235 _ => {
11236 if matches!(self.peek(), Token::FatArrow) {
11239 return Ok(Expr {
11240 kind: ExprKind::String(name),
11241 line,
11242 });
11243 }
11244 if name == "_" {
11247 return Ok(Expr {
11248 kind: ExprKind::ScalarVar("_".to_string()),
11249 line,
11250 });
11251 }
11252 if matches!(self.peek(), Token::LParen) {
11254 self.advance();
11255 let args = self.parse_arg_list()?;
11256 self.expect(&Token::RParen)?;
11257 Ok(Expr {
11258 kind: ExprKind::FuncCall { name, args },
11259 line,
11260 })
11261 } else if self.peek().is_term_start()
11262 && !(matches!(self.peek(), Token::Ident(ref kw) if kw == "sub")
11263 && matches!(self.peek_at(1), Token::Ident(_)))
11264 && !(self.suppress_parenless_call > 0 && matches!(self.peek(), Token::Ident(_)))
11265 && !(matches!(self.peek(), Token::LBrace)
11266 && self.peek_line() > self.prev_line())
11267 {
11268 let args = self.parse_list_until_terminator()?;
11278 Ok(Expr {
11279 kind: ExprKind::FuncCall { name, args },
11280 line,
11281 })
11282 } else {
11283 Ok(Expr {
11289 kind: ExprKind::Bareword(name),
11290 line,
11291 })
11292 }
11293 }
11294 }
11295 }
11296
11297 fn parse_print_like(
11298 &mut self,
11299 make: impl FnOnce(Option<String>, Vec<Expr>) -> ExprKind,
11300 ) -> PerlResult<Expr> {
11301 let line = self.peek_line();
11302 let handle = if let Token::Ident(ref h) = self.peek().clone() {
11304 if h.chars().all(|c| c.is_uppercase() || c == '_')
11305 && !matches!(self.peek(), Token::LParen)
11306 {
11307 let h = h.clone();
11308 let saved = self.pos;
11309 self.advance();
11310 if self.peek().is_term_start()
11312 || matches!(
11313 self.peek(),
11314 Token::DoubleString(_) | Token::BacktickString(_) | Token::SingleString(_)
11315 )
11316 {
11317 Some(h)
11318 } else {
11319 self.pos = saved;
11320 None
11321 }
11322 } else {
11323 None
11324 }
11325 } else if let Token::ScalarVar(ref v) = self.peek().clone() {
11326 let v = v.clone();
11336 if v == "_" {
11337 None
11338 } else {
11339 let saved = self.pos;
11340 self.advance();
11341 let next = self.peek().clone();
11342 let is_stmt_modifier = matches!(&next, Token::Ident(kw)
11343 if matches!(kw.as_str(), "if" | "unless" | "while" | "until" | "for" | "foreach"));
11344 if !is_stmt_modifier
11345 && !matches!(next, Token::LBracket | Token::LBrace)
11346 && (next.is_term_start()
11347 || matches!(
11348 next,
11349 Token::DoubleString(_)
11350 | Token::BacktickString(_)
11351 | Token::SingleString(_)
11352 ))
11353 {
11354 Some(format!("${v}"))
11356 } else {
11357 self.pos = saved;
11358 None
11359 }
11360 }
11361 } else {
11362 None
11363 };
11364 let args =
11369 if matches!(self.peek(), Token::LParen) && matches!(self.peek_at(1), Token::RParen) {
11370 let line_topic = self.peek_line();
11371 self.advance(); self.advance(); vec![Expr {
11374 kind: ExprKind::ScalarVar("_".into()),
11375 line: line_topic,
11376 }]
11377 } else {
11378 self.parse_list_until_terminator()?
11379 };
11380 Ok(Expr {
11381 kind: make(handle, args),
11382 line,
11383 })
11384 }
11385
11386 fn parse_block_list(&mut self) -> PerlResult<(Block, Expr)> {
11387 let block = self.parse_block()?;
11388 let block_end_line = self.prev_line();
11389 self.eat(&Token::Comma);
11390 if self.in_pipe_rhs()
11394 && (matches!(
11395 self.peek(),
11396 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof | Token::PipeForward
11397 ) || self.peek_line() > block_end_line)
11398 {
11399 let line = self.peek_line();
11400 return Ok((block, self.pipe_placeholder_list(line)));
11401 }
11402 let list = self.parse_expression()?;
11403 Ok((block, list))
11404 }
11405
11406 fn parse_comma_expr_list_with_timeout_tail(
11409 &mut self,
11410 paren: bool,
11411 ) -> PerlResult<(Vec<Expr>, Option<Expr>)> {
11412 let mut parts = vec![self.parse_assign_expr()?];
11413 loop {
11414 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
11415 break;
11416 }
11417 if paren && matches!(self.peek(), Token::RParen) {
11418 break;
11419 }
11420 if matches!(
11421 self.peek(),
11422 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof
11423 ) {
11424 break;
11425 }
11426 if self.peek_is_postfix_stmt_modifier_keyword() {
11427 break;
11428 }
11429 if let Token::Ident(ref kw) = self.peek().clone() {
11430 if kw == "timeout" && matches!(self.peek_at(1), Token::FatArrow) {
11431 self.advance();
11432 self.expect(&Token::FatArrow)?;
11433 let t = self.parse_assign_expr()?;
11434 return Ok((parts, Some(t)));
11435 }
11436 }
11437 parts.push(self.parse_assign_expr()?);
11438 }
11439 Ok((parts, None))
11440 }
11441
11442 fn parse_init_block_then_list_optional_progress(
11444 &mut self,
11445 ) -> PerlResult<(Expr, Block, Expr, Option<Expr>)> {
11446 let init = self.parse_assign_expr()?;
11447 self.expect(&Token::Comma)?;
11448 let block = self.parse_block_or_bareword_block()?;
11449 self.eat(&Token::Comma);
11450 let line = self.peek_line();
11451 if let Token::Ident(ref kw) = self.peek().clone() {
11452 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
11453 self.advance();
11454 self.expect(&Token::FatArrow)?;
11455 let prog = self.parse_assign_expr()?;
11456 return Ok((
11457 init,
11458 block,
11459 Expr {
11460 kind: ExprKind::List(vec![]),
11461 line,
11462 },
11463 Some(prog),
11464 ));
11465 }
11466 }
11467 if matches!(
11468 self.peek(),
11469 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof
11470 ) {
11471 return Ok((
11472 init,
11473 block,
11474 Expr {
11475 kind: ExprKind::List(vec![]),
11476 line,
11477 },
11478 None,
11479 ));
11480 }
11481 let mut parts = vec![self.parse_assign_expr()?];
11482 loop {
11483 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
11484 break;
11485 }
11486 if matches!(
11487 self.peek(),
11488 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof
11489 ) {
11490 break;
11491 }
11492 if self.peek_is_postfix_stmt_modifier_keyword() {
11493 break;
11494 }
11495 if let Token::Ident(ref kw) = self.peek().clone() {
11496 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
11497 self.advance();
11498 self.expect(&Token::FatArrow)?;
11499 let prog = self.parse_assign_expr()?;
11500 return Ok((init, block, merge_expr_list(parts), Some(prog)));
11501 }
11502 }
11503 parts.push(self.parse_assign_expr()?);
11504 }
11505 Ok((init, block, merge_expr_list(parts), None))
11506 }
11507
11508 fn parse_cluster_block_then_list_optional_progress(
11510 &mut self,
11511 ) -> PerlResult<(Expr, Block, Expr, Option<Expr>)> {
11512 let cluster = self.parse_assign_expr()?;
11513 let block = self.parse_block_or_bareword_block()?;
11514 self.eat(&Token::Comma);
11515 let line = self.peek_line();
11516 if let Token::Ident(ref kw) = self.peek().clone() {
11517 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
11518 self.advance();
11519 self.expect(&Token::FatArrow)?;
11520 let prog = self.parse_assign_expr_stop_at_pipe()?;
11521 return Ok((
11522 cluster,
11523 block,
11524 Expr {
11525 kind: ExprKind::List(vec![]),
11526 line,
11527 },
11528 Some(prog),
11529 ));
11530 }
11531 }
11532 let empty_list_ok = matches!(
11533 self.peek(),
11534 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof | Token::PipeForward
11535 ) || (self.in_pipe_rhs() && matches!(self.peek(), Token::Comma));
11536 if empty_list_ok {
11537 return Ok((
11538 cluster,
11539 block,
11540 Expr {
11541 kind: ExprKind::List(vec![]),
11542 line,
11543 },
11544 None,
11545 ));
11546 }
11547 let mut parts = vec![self.parse_assign_expr_stop_at_pipe()?];
11548 loop {
11549 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
11550 break;
11551 }
11552 if matches!(
11553 self.peek(),
11554 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof | Token::PipeForward
11555 ) {
11556 break;
11557 }
11558 if self.peek_is_postfix_stmt_modifier_keyword() {
11559 break;
11560 }
11561 if let Token::Ident(ref kw) = self.peek().clone() {
11562 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
11563 self.advance();
11564 self.expect(&Token::FatArrow)?;
11565 let prog = self.parse_assign_expr_stop_at_pipe()?;
11566 return Ok((cluster, block, merge_expr_list(parts), Some(prog)));
11567 }
11568 }
11569 parts.push(self.parse_assign_expr_stop_at_pipe()?);
11570 }
11571 Ok((cluster, block, merge_expr_list(parts), None))
11572 }
11573
11574 fn parse_block_then_list_optional_progress(
11583 &mut self,
11584 ) -> PerlResult<(Block, Expr, Option<Expr>)> {
11585 let block = self.parse_block_or_bareword_block()?;
11586 self.eat(&Token::Comma);
11587 let line = self.peek_line();
11588 if let Token::Ident(ref kw) = self.peek().clone() {
11589 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
11590 self.advance();
11591 self.expect(&Token::FatArrow)?;
11592 let prog = self.parse_assign_expr_stop_at_pipe()?;
11593 return Ok((
11594 block,
11595 Expr {
11596 kind: ExprKind::List(vec![]),
11597 line,
11598 },
11599 Some(prog),
11600 ));
11601 }
11602 }
11603 let empty_list_ok = matches!(
11609 self.peek(),
11610 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof | Token::PipeForward
11611 ) || (self.in_pipe_rhs() && matches!(self.peek(), Token::Comma));
11612 if empty_list_ok {
11613 return Ok((
11614 block,
11615 Expr {
11616 kind: ExprKind::List(vec![]),
11617 line,
11618 },
11619 None,
11620 ));
11621 }
11622 let mut parts = vec![self.parse_assign_expr_stop_at_pipe()?];
11623 loop {
11624 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
11625 break;
11626 }
11627 if matches!(
11628 self.peek(),
11629 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof | Token::PipeForward
11630 ) {
11631 break;
11632 }
11633 if self.peek_is_postfix_stmt_modifier_keyword() {
11634 break;
11635 }
11636 if let Token::Ident(ref kw) = self.peek().clone() {
11637 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
11638 self.advance();
11639 self.expect(&Token::FatArrow)?;
11640 let prog = self.parse_assign_expr_stop_at_pipe()?;
11641 return Ok((block, merge_expr_list(parts), Some(prog)));
11642 }
11643 }
11644 parts.push(self.parse_assign_expr_stop_at_pipe()?);
11645 }
11646 Ok((block, merge_expr_list(parts), None))
11647 }
11648
11649 fn parse_fan_count_and_block(&mut self, line: usize) -> PerlResult<(Option<Box<Expr>>, Block)> {
11651 if matches!(self.peek(), Token::LBrace) {
11653 let block = self.parse_block()?;
11654 return Ok((None, block));
11655 }
11656 let saved = self.pos;
11657 let first = self.parse_postfix()?;
11659 if matches!(self.peek(), Token::LBrace) {
11660 let block = self.parse_block()?;
11662 Ok((Some(Box::new(first)), block))
11663 } else if matches!(self.peek(), Token::Semicolon | Token::RBrace | Token::Eof)
11664 || (matches!(self.peek(), Token::Comma)
11665 && matches!(self.peek_at(1), Token::Ident(ref kw) if kw == "progress"))
11666 {
11667 let block = self.bareword_to_no_arg_block(first);
11669 Ok((None, block))
11670 } else if matches!(first.kind, ExprKind::Integer(_)) {
11671 self.eat(&Token::Comma);
11673 let body = self.parse_fan_blockless_body(line)?;
11674 Ok((Some(Box::new(first)), body))
11675 } else {
11676 self.pos = saved;
11679 let body = self.parse_fan_blockless_body(line)?;
11680 Ok((None, body))
11681 }
11682 }
11683
11684 fn parse_fan_blockless_body(&mut self, line: usize) -> PerlResult<Block> {
11686 if matches!(self.peek(), Token::LBrace) {
11687 return self.parse_block();
11688 }
11689 if let Token::Ident(ref name) = self.peek().clone() {
11691 if matches!(
11692 self.peek_at(1),
11693 Token::Comma | Token::Semicolon | Token::RBrace | Token::Eof | Token::PipeForward
11694 ) {
11695 let name = name.clone();
11696 self.advance();
11697 let body = Expr {
11698 kind: ExprKind::FuncCall { name, args: vec![] },
11699 line,
11700 };
11701 return Ok(vec![Statement::new(StmtKind::Expression(body), line)]);
11702 }
11703 }
11704 let expr = self.parse_assign_expr_stop_at_pipe()?;
11706 Ok(vec![Statement::new(StmtKind::Expression(expr), line)])
11707 }
11708
11709 fn bareword_to_no_arg_block(&self, expr: Expr) -> Block {
11712 let line = expr.line;
11713 let body = match &expr.kind {
11714 ExprKind::Bareword(name) => Expr {
11715 kind: ExprKind::FuncCall {
11716 name: name.clone(),
11717 args: vec![],
11718 },
11719 line,
11720 },
11721 _ => expr,
11722 };
11723 vec![Statement::new(StmtKind::Expression(body), line)]
11724 }
11725
11726 fn parse_block_or_bareword_block(&mut self) -> PerlResult<Block> {
11735 if matches!(self.peek(), Token::LBrace) {
11736 return self.parse_block();
11737 }
11738 let line = self.peek_line();
11739 if let Token::Ident(ref name) = self.peek().clone() {
11742 if matches!(
11743 self.peek_at(1),
11744 Token::Comma | Token::Semicolon | Token::RBrace | Token::Eof | Token::PipeForward
11745 ) {
11746 let name = name.clone();
11747 self.advance();
11748 let body = Expr {
11749 kind: ExprKind::FuncCall {
11750 name,
11751 args: vec![Expr {
11752 kind: ExprKind::ScalarVar("_".to_string()),
11753 line,
11754 }],
11755 },
11756 line,
11757 };
11758 return Ok(vec![Statement::new(StmtKind::Expression(body), line)]);
11759 }
11760 }
11761 let expr = self.parse_assign_expr_stop_at_pipe()?;
11763 Ok(vec![Statement::new(StmtKind::Expression(expr), line)])
11764 }
11765
11766 fn parse_block_or_bareword_block_no_args(&mut self) -> PerlResult<Block> {
11771 if matches!(self.peek(), Token::LBrace) {
11772 return self.parse_block();
11773 }
11774 let line = self.peek_line();
11775 if let Token::Ident(ref name) = self.peek().clone() {
11776 if matches!(
11777 self.peek_at(1),
11778 Token::Comma
11779 | Token::Semicolon
11780 | Token::RBrace
11781 | Token::Eof
11782 | Token::PipeForward
11783 | Token::Integer(_)
11784 ) {
11785 let name = name.clone();
11786 self.advance();
11787 let body = Expr {
11788 kind: ExprKind::FuncCall { name, args: vec![] },
11789 line,
11790 };
11791 return Ok(vec![Statement::new(StmtKind::Expression(body), line)]);
11792 }
11793 }
11794 let expr = self.parse_postfix()?;
11795 Ok(vec![Statement::new(StmtKind::Expression(expr), line)])
11796 }
11797
11798 fn is_known_bareword(name: &str) -> bool {
11806 Self::is_perl5_core(name) || Self::stryke_extension_name(name).is_some()
11807 }
11808
11809 fn is_try_builtin_name(name: &str) -> bool {
11815 crate::builtins::BUILTIN_ARMS
11816 .iter()
11817 .any(|arm| arm.contains(&name))
11818 }
11819
11820 fn is_perl5_core(name: &str) -> bool {
11825 matches!(
11826 name,
11827 "map" | "grep" | "sort" | "reverse" | "join" | "split"
11829 | "push" | "pop" | "shift" | "unshift" | "splice"
11830 | "pack" | "unpack"
11831 | "keys" | "values" | "each"
11833 | "chomp" | "chop" | "chr" | "ord" | "hex" | "oct"
11835 | "lc" | "uc" | "lcfirst" | "ucfirst"
11836 | "length" | "substr" | "index" | "rindex"
11837 | "sprintf" | "printf" | "print" | "say"
11838 | "pos" | "quotemeta" | "study"
11839 | "abs" | "int" | "sqrt" | "sin" | "cos" | "atan2"
11841 | "exp" | "log" | "rand" | "srand"
11842 | "time" | "localtime" | "gmtime"
11844 | "defined" | "undef" | "ref" | "scalar" | "wantarray"
11846 | "caller" | "delete" | "exists" | "bless" | "prototype"
11847 | "tie" | "untie" | "tied"
11848 | "open" | "close" | "read" | "readline" | "write" | "seek" | "tell"
11850 | "eof" | "binmode" | "getc" | "fileno" | "truncate"
11851 | "format" | "formline" | "select" | "vec"
11852 | "sysopen" | "sysread" | "sysseek" | "syswrite"
11853 | "stat" | "lstat" | "rename" | "unlink" | "utime"
11855 | "mkdir" | "rmdir" | "chdir" | "chmod" | "chown"
11856 | "glob" | "opendir" | "readdir" | "closedir"
11857 | "link" | "readlink" | "symlink"
11858 | "fcntl" | "flock" | "ioctl" | "pipe" | "dbmopen" | "dbmclose"
11860 | "msgctl" | "msgget" | "msgrcv" | "msgsnd"
11862 | "semctl" | "semget" | "semop"
11863 | "shmctl" | "shmget" | "shmread" | "shmwrite"
11864 | "system" | "exec" | "exit" | "die" | "warn" | "dump"
11866 | "fork" | "wait" | "waitpid" | "kill" | "alarm" | "sleep"
11867 | "chroot" | "times" | "umask" | "reset"
11868 | "getpgrp" | "setpgrp" | "getppid"
11869 | "getpriority" | "setpriority"
11870 | "socket" | "socketpair" | "connect" | "listen" | "accept" | "shutdown"
11872 | "send" | "recv" | "bind" | "setsockopt" | "getsockopt"
11873 | "getpeername" | "getsockname"
11874 | "getpwnam" | "getpwuid" | "getpwent" | "setpwent"
11876 | "getgrnam" | "getgrgid" | "getgrent" | "setgrent"
11877 | "getlogin"
11878 | "gethostbyname" | "gethostbyaddr" | "gethostent"
11879 | "getnetbyname" | "getnetent"
11880 | "getprotobyname" | "getprotoent"
11881 | "getservbyname" | "getservent"
11882 | "sethostent" | "setnetent" | "setprotoent" | "setservent"
11883 | "endpwent" | "endgrent"
11884 | "endhostent" | "endnetent" | "endprotoent" | "endservent"
11885 | "return" | "do" | "eval" | "require"
11887 | "my" | "our" | "local" | "use" | "no"
11888 | "sub" | "if" | "unless" | "while" | "until"
11889 | "for" | "foreach" | "last" | "next" | "redo" | "goto"
11890 | "not" | "and" | "or"
11891 | "qw" | "qq" | "q"
11893 | "BEGIN" | "END"
11895 )
11896 }
11897
11898 fn stryke_extension_name(name: &str) -> Option<&str> {
11901 match name {
11902 | "pmap" | "pmap_on" | "pflat_map" | "pflat_map_on" | "pmap_chunked"
11904 | "pgrep" | "pfor" | "psort" | "preduce" | "preduce_init" | "pmap_reduce"
11905 | "pcache" | "pchannel" | "pselect" | "puniq" | "pfirst" | "pany"
11906 | "fan" | "fan_cap" | "par_lines" | "par_walk" | "par_sed"
11907 | "par_find_files" | "par_line_count" | "pwatch" | "par_pipeline_stream"
11908 | "glob_par" | "ppool" | "barrier" | "pipeline" | "cluster"
11909 | "pmaps" | "pflat_maps" | "pgreps"
11910 | "fore" | "e" | "ep" | "flat_map" | "flat_maps" | "maps" | "filter" | "fi" | "find_all" | "reduce" | "fold"
11912 | "inject" | "collect" | "uniq" | "distinct" | "any" | "all" | "none"
11913 | "first" | "detect" | "find" | "compact" | "concat" | "chain" | "reject" | "flatten" | "set"
11914 | "min_by" | "max_by" | "sort_by" | "tally" | "find_index"
11915 | "each_with_index" | "count" | "cnt" |"len" | "group_by" | "chunk_by"
11916 | "zip" | "chunk" | "chunked" | "sliding_window" | "windowed"
11917 | "enumerate" | "with_index" | "shuffle" | "shuffled"| "heap"
11918 | "take_while" | "drop_while" | "skip_while" | "tap" | "peek" | "partition"
11919 | "zip_with" | "count_by" | "skip" | "first_or"
11920 | "input" | "lines" | "words" | "chars" | "digits" | "letters" | "letters_uc" | "letters_lc"
11922 | "punctuation" | "punct"
11923 | "sentences" | "sents"
11924 | "paragraphs" | "paras" | "sections" | "sects"
11925 | "numbers" | "nums" | "graphemes" | "grs" | "columns" | "cols"
11926 | "trim" | "avg" | "stddev"
11927 | "squared" | "sq" | "square" | "cubed" | "cb" | "cube" | "expt" | "pow" | "pw"
11928 | "normalize" | "snake_case" | "camel_case" | "kebab_case"
11929 | "frequencies" | "freq" | "interleave" | "ddump" | "stringify" | "str" | "top"
11930 | "to_json" | "to_csv" | "to_toml" | "to_yaml" | "to_xml"
11931 | "to_html" | "to_markdown" | "to_table" | "xopen"
11932 | "from_json" | "from_csv" | "from_toml" | "from_yaml" | "from_xml"
11933 | "clip" | "clipboard" | "paste" | "pbcopy" | "pbpaste" | "preview"
11934 | "sparkline" | "spark" | "bar_chart" | "bars" | "flame" | "flamechart"
11935 | "histo" | "gauge" | "spinner" | "spinner_start" | "spinner_stop"
11936 | "to_hash" | "to_set"
11937 | "to_file" | "read_lines" | "append_file" | "write_json" | "read_json"
11938 | "tempfile" | "tempdir" | "list_count" | "list_size" | "size"
11939 | "clamp" | "grep_v" | "select_keys" | "pluck" | "glob_match" | "which_all"
11940 | "dedup" | "nth" | "tail" | "take" | "drop" | "tee" | "range"
11941 | "inc" | "dec" | "elapsed"
11942 | "files" | "filesf" | "f" | "fr" | "dirs" | "d" | "dr" | "sym_links"
11944 | "sockets" | "pipes" | "block_devices" | "char_devices" | "exe" | "executables"
11945 | "basename" | "dirname" | "fileparse" | "realpath" | "canonpath"
11946 | "copy" | "move" | "spurt" | "read_bytes" | "which"
11947 | "getcwd" | "touch" | "gethostname" | "uname"
11948 | "csv_read" | "csv_write" | "dataframe" | "sqlite"
11950 | "fetch" | "fetch_json" | "fetch_async" | "fetch_async_json"
11951 | "par_fetch" | "par_csv_read" | "par_pipeline"
11952 | "json_encode" | "json_decode" | "json_jq"
11953 | "http_request" | "serve" | "ssh"
11954 | "html_parse" | "css_select" | "xml_parse" | "xpath"
11955 | "smtp_send"
11956 | "net_interfaces" | "net_ipv4" | "net_ipv6" | "net_mac"
11957 | "net_public_ip" | "net_dns" | "net_reverse_dns"
11958 | "net_ping" | "net_port_open" | "net_ports_scan"
11959 | "net_latency" | "net_download" | "net_headers"
11960 | "net_dns_servers" | "net_gateway" | "net_whois" | "net_hostname"
11961 | "git_log" | "git_status" | "git_diff" | "git_branches"
11963 | "git_tags" | "git_blame" | "git_authors" | "git_files"
11964 | "git_show" | "git_root"
11965 | "audio_convert" | "audio_info" | "id3_read" | "id3_write"
11967 | "to_pdf" | "pdf_text" | "pdf_pages"
11969 | "toml_encode" | "toml_decode"
11971 | "yaml_encode" | "yaml_decode"
11972 | "xml_encode" | "xml_decode"
11973 | "md5" | "sha1" | "sha224" | "sha256" | "sha384" | "sha512"
11975 | "sha3_256" | "s3_256" | "sha3_512" | "s3_512"
11976 | "shake128" | "shake256"
11977 | "hmac_sha256" | "hmac_sha1" | "hmac_sha384" | "hmac_sha512" | "hmac_md5"
11978 | "uuid" | "crc32"
11979 | "blake2b" | "b2b" | "blake2s" | "b2s" | "blake3" | "b3"
11980 | "ripemd160" | "rmd160" | "md4"
11981 | "xxh32" | "xxhash32" | "xxh64" | "xxhash64" | "xxh3" | "xxhash3" | "xxh3_128" | "xxhash3_128"
11982 | "murmur3" | "murmur3_32" | "murmur3_128"
11983 | "siphash" | "siphash_keyed"
11984 | "hkdf_sha256" | "hkdf" | "hkdf_sha512"
11985 | "poly1305" | "poly1305_mac"
11986 | "base32_encode" | "b32e" | "base32_decode" | "b32d"
11987 | "base58_encode" | "b58e" | "base58_decode" | "b58d"
11988 | "totp" | "totp_generate" | "totp_verify" | "hotp" | "hotp_generate"
11989 | "aes_cbc_encrypt" | "aes_cbc_enc" | "aes_cbc_decrypt" | "aes_cbc_dec"
11990 | "blowfish_encrypt" | "bf_enc" | "blowfish_decrypt" | "bf_dec"
11991 | "des3_encrypt" | "3des_enc" | "tdes_enc" | "des3_decrypt" | "3des_dec" | "tdes_dec"
11992 | "twofish_encrypt" | "tf_enc" | "twofish_decrypt" | "tf_dec"
11993 | "camellia_encrypt" | "cam_enc" | "camellia_decrypt" | "cam_dec"
11994 | "cast5_encrypt" | "cast5_enc" | "cast5_decrypt" | "cast5_dec"
11995 | "salsa20" | "salsa20_encrypt" | "salsa20_decrypt"
11996 | "xsalsa20" | "xsalsa20_encrypt" | "xsalsa20_decrypt"
11997 | "secretbox" | "secretbox_seal" | "secretbox_open"
11998 | "nacl_box_keygen" | "box_keygen" | "nacl_box" | "nacl_box_seal" | "box_seal"
11999 | "nacl_box_open" | "box_open"
12000 | "qr_ascii" | "qr" | "qr_png" | "qr_svg"
12001 | "barcode_code128" | "code128" | "barcode_code39" | "code39"
12002 | "barcode_ean13" | "ean13" | "barcode_svg"
12003 | "argon2_hash" | "argon2" | "argon2_verify"
12004 | "bcrypt_hash" | "bcrypt" | "bcrypt_verify"
12005 | "scrypt_hash" | "scrypt" | "scrypt_verify"
12006 | "pbkdf2" | "pbkdf2_derive"
12007 | "random_bytes" | "randbytes" | "random_bytes_hex" | "randhex"
12008 | "aes_encrypt" | "aes_enc" | "aes_decrypt" | "aes_dec"
12009 | "chacha_encrypt" | "chacha_enc" | "chacha_decrypt" | "chacha_dec"
12010 | "rsa_keygen" | "rsa_encrypt" | "rsa_enc" | "rsa_decrypt" | "rsa_dec"
12011 | "rsa_encrypt_pkcs1" | "rsa_decrypt_pkcs1" | "rsa_sign" | "rsa_verify"
12012 | "ecdsa_p256_keygen" | "p256_keygen" | "ecdsa_p256_sign" | "p256_sign"
12013 | "ecdsa_p256_verify" | "p256_verify"
12014 | "ecdsa_p384_keygen" | "p384_keygen" | "ecdsa_p384_sign" | "p384_sign"
12015 | "ecdsa_p384_verify" | "p384_verify"
12016 | "ecdsa_secp256k1_keygen" | "secp256k1_keygen"
12017 | "ecdsa_secp256k1_sign" | "secp256k1_sign"
12018 | "ecdsa_secp256k1_verify" | "secp256k1_verify"
12019 | "ecdh_p256" | "p256_dh" | "ecdh_p384" | "p384_dh"
12020 | "ed25519_keygen" | "ed_keygen" | "ed25519_sign" | "ed_sign"
12021 | "ed25519_verify" | "ed_verify"
12022 | "x25519_keygen" | "x_keygen" | "x25519_dh" | "x_dh"
12023 | "base64_encode" | "base64_decode"
12024 | "hex_encode" | "hex_decode"
12025 | "url_encode" | "url_decode"
12026 | "gzip" | "gunzip" | "gz" | "ugz" | "zstd" | "zstd_decode" | "zst" | "uzst"
12027 | "brotli" | "br" | "brotli_decode" | "ubr"
12028 | "xz" | "lzma" | "xz_decode" | "unxz" | "unlzma"
12029 | "bzip2" | "bz2" | "bzip2_decode" | "bunzip2" | "ubz2"
12030 | "lz4" | "lz4_decode" | "unlz4"
12031 | "snappy" | "snp" | "snappy_decode" | "unsnappy"
12032 | "lzw" | "lzw_decode" | "unlzw"
12033 | "tar_create" | "tar" | "tar_extract" | "untar" | "tar_list"
12034 | "tar_gz_create" | "tgz" | "tar_gz_extract" | "untgz"
12035 | "zip_create" | "zip_archive" | "zip_extract" | "unzip_archive" | "zip_list"
12036 | "erf" | "erfc" | "gamma" | "tgamma" | "lgamma" | "ln_gamma"
12038 | "digamma" | "psi" | "beta_fn" | "lbeta" | "ln_beta"
12039 | "betainc" | "beta_reg" | "gammainc" | "gamma_li"
12040 | "gammaincc" | "gamma_ui" | "gammainc_reg" | "gamma_lr"
12041 | "gammaincc_reg" | "gamma_ur"
12042 | "datetime_utc" | "datetime_now_tz"
12044 | "datetime_format_tz" | "datetime_add_seconds"
12045 | "datetime_from_epoch"
12046 | "datetime_parse_rfc3339" | "datetime_parse_local"
12047 | "datetime_strftime"
12048 | "dateseq" | "dategrep" | "dateround" | "datesort"
12049 | "jwt_encode" | "jwt_decode" | "jwt_decode_unsafe"
12051 | "log_info" | "log_warn" | "log_error"
12053 | "log_debug" | "log_trace" | "log_json" | "log_level"
12054 | "async" | "spawn" | "trace" | "timer" | "bench"
12056 | "eval_timeout" | "retry" | "rate_limit" | "every"
12057 | "gen" | "watch"
12058 | "assert_eq" | "assert_ne" | "assert_ok" | "assert_err"
12060 | "assert_true" | "assert_false"
12061 | "assert_gt" | "assert_lt" | "assert_ge" | "assert_le"
12062 | "assert_match" | "assert_contains" | "assert_near" | "assert_dies"
12063 | "test_run"
12064 | "mounts" | "du" | "du_tree" | "process_list"
12066 | "thread_count" | "pool_info" | "par_bench"
12067 | "slurp" | "cat" | "c" | "capture" | "pager" | "pg" | "less"
12069 | "stdin"
12070 | "__stryke_rust_compile"
12072 | "p" | "rev"
12074 | "even" | "odd" | "zero" | "nonzero"
12076 | "positive" | "pos_n" | "negative" | "neg_n"
12077 | "sign" | "negate" | "double" | "triple" | "half"
12078 | "identity" | "id"
12079 | "round" | "floor" | "ceil" | "ceiling" | "trunc" | "truncn"
12080 | "gcd" | "lcm" | "min2" | "max2"
12081 | "log2" | "log10" | "hypot"
12082 | "rad_to_deg" | "r2d" | "deg_to_rad" | "d2r"
12083 | "pow2" | "abs_diff"
12084 | "factorial" | "fact" | "fibonacci" | "fib"
12085 | "is_prime" | "is_square" | "is_power_of_two" | "is_pow2"
12086 | "cbrt" | "exp2" | "percent" | "pct" | "inverse"
12087 | "median" | "mode_val" | "variance"
12088 | "is_empty" | "is_blank" | "is_numeric"
12090 | "is_upper" | "is_lower" | "is_alpha" | "is_digit" | "is_alnum"
12091 | "is_space" | "is_whitespace"
12092 | "starts_with" | "sw" | "ends_with" | "ew" | "contains"
12093 | "capitalize" | "cap" | "swap_case" | "repeat"
12094 | "title_case" | "title" | "squish"
12095 | "pad_left" | "lpad" | "pad_right" | "rpad" | "center"
12096 | "truncate_at" | "shorten" | "reverse_str" | "rev_str"
12097 | "char_count" | "word_count" | "wc" | "line_count" | "lc_lines"
12098 | "is_array" | "is_arrayref" | "is_hash" | "is_hashref"
12100 | "is_code" | "is_coderef" | "is_ref"
12101 | "is_undef" | "is_defined" | "is_def"
12102 | "is_string" | "is_str" | "is_int" | "is_integer" | "is_float"
12103 | "invert" | "merge_hash"
12105 | "has_key" | "hk" | "has_any_key" | "has_all_keys"
12106 | "both" | "either" | "neither" | "xor_bool" | "bool_to_int" | "b2i"
12108 | "riffle" | "intersperse" | "every_nth"
12110 | "drop_n" | "take_n" | "rotate" | "swap_pairs"
12111 | "to_bin" | "bin_of" | "to_hex" | "hex_of" | "to_oct" | "oct_of"
12113 | "from_bin" | "from_hex" | "from_oct" | "to_base" | "from_base"
12114 | "bits_count" | "popcount" | "leading_zeros" | "lz"
12115 | "trailing_zeros" | "tz" | "bit_length" | "bitlen"
12116 | "bit_and" | "bit_or" | "bit_xor" | "bit_not"
12118 | "shift_left" | "shl" | "shift_right" | "shr"
12119 | "bit_set" | "bit_clear" | "bit_toggle" | "bit_test"
12120 | "c_to_f" | "f_to_c" | "c_to_k" | "k_to_c" | "f_to_k" | "k_to_f"
12122 | "miles_to_km" | "km_to_miles" | "miles_to_m" | "m_to_miles"
12124 | "feet_to_m" | "m_to_feet" | "inches_to_cm" | "cm_to_inches"
12125 | "yards_to_m" | "m_to_yards"
12126 | "kg_to_lbs" | "lbs_to_kg" | "g_to_oz" | "oz_to_g"
12128 | "stone_to_kg" | "kg_to_stone"
12129 | "bytes_to_kb" | "b_to_kb" | "kb_to_bytes" | "kb_to_b"
12131 | "bytes_to_mb" | "mb_to_bytes" | "bytes_to_gb" | "gb_to_bytes"
12132 | "kb_to_mb" | "mb_to_gb"
12133 | "bits_to_bytes" | "bytes_to_bits"
12134 | "seconds_to_minutes" | "s_to_m" | "minutes_to_seconds" | "m_to_s"
12136 | "seconds_to_hours" | "hours_to_seconds"
12137 | "seconds_to_days" | "days_to_seconds"
12138 | "minutes_to_hours" | "hours_to_minutes"
12139 | "hours_to_days" | "days_to_hours"
12140 | "is_leap_year" | "is_leap" | "days_in_month"
12142 | "month_name" | "month_short"
12143 | "weekday_name" | "weekday_short" | "quarter_of"
12144 | "now_ms" | "now_us" | "now_ns"
12146 | "unix_epoch" | "epoch" | "unix_epoch_ms" | "epoch_ms"
12147 | "rgb_to_hex" | "hex_to_rgb"
12149 | "ansi_red" | "ansi_green" | "ansi_yellow" | "ansi_blue"
12150 | "ansi_magenta" | "ansi_cyan" | "ansi_white" | "ansi_black"
12151 | "ansi_bold" | "ansi_dim" | "ansi_underline" | "ansi_reverse"
12152 | "strip_ansi"
12153 | "red" | "green" | "yellow" | "blue" | "magenta" | "purple" | "cyan"
12154 | "white" | "black" | "bold" | "dim" | "italic" | "underline"
12155 | "strikethrough" | "ansi_off" | "off" | "gray" | "grey"
12156 | "bright_red" | "bright_green" | "bright_yellow" | "bright_blue"
12157 | "bright_magenta" | "bright_cyan" | "bright_white"
12158 | "bg_red" | "bg_green" | "bg_yellow" | "bg_blue"
12159 | "bg_magenta" | "bg_cyan" | "bg_white" | "bg_black"
12160 | "red_bold" | "bold_red" | "green_bold" | "bold_green"
12161 | "yellow_bold" | "bold_yellow" | "blue_bold" | "bold_blue"
12162 | "magenta_bold" | "bold_magenta" | "cyan_bold" | "bold_cyan"
12163 | "white_bold" | "bold_white"
12164 | "blink" | "rapid_blink" | "hidden" | "overline"
12165 | "bg_bright_red" | "bg_bright_green" | "bg_bright_yellow" | "bg_bright_blue"
12166 | "bg_bright_magenta" | "bg_bright_cyan" | "bg_bright_white"
12167 | "rgb" | "bg_rgb" | "color256" | "c256" | "bg_color256" | "bg_c256"
12168 | "ipv4_to_int" | "int_to_ipv4"
12170 | "is_valid_ipv4" | "is_valid_ipv6" | "is_valid_email" | "is_valid_url"
12171 | "path_ext" | "path_stem" | "path_parent" | "path_join" | "path_split"
12173 | "strip_prefix" | "strip_suffix" | "ensure_prefix" | "ensure_suffix"
12174 | "const_fn" | "always_true" | "always_false"
12176 | "flip_args" | "first_arg" | "second_arg" | "last_arg"
12177 | "count_eq" | "count_ne" | "all_eq"
12179 | "all_distinct" | "all_unique" | "has_duplicates"
12180 | "sum_of" | "product_of" | "max_of" | "min_of" | "range_of"
12181 | "quote" | "single_quote" | "unquote"
12183 | "extract_between" | "ellipsis"
12184 | "coin_flip" | "dice_roll"
12186 | "random_int" | "random_float" | "random_bool"
12187 | "random_choice" | "random_between"
12188 | "random_string" | "random_alpha" | "random_digit"
12189 | "os_name" | "os_arch" | "num_cpus"
12191 | "pid" | "ppid" | "uid" | "gid"
12192 | "username" | "home_dir" | "temp_dir"
12193 | "mem_total" | "mem_free" | "mem_used"
12194 | "swap_total" | "swap_free" | "swap_used"
12195 | "disk_total" | "disk_free" | "disk_avail" | "disk_used"
12196 | "load_avg" | "sys_uptime" | "page_size"
12197 | "os_version" | "os_family" | "endianness" | "pointer_width"
12198 | "proc_mem" | "rss"
12199 | "transpose" | "unzip"
12201 | "run_length_encode" | "rle" | "run_length_decode" | "rld"
12202 | "sliding_pairs" | "consecutive_eq" | "flatten_deep"
12203 | "tan" | "asin" | "acos" | "atan"
12205 | "sinh" | "cosh" | "tanh" | "asinh" | "acosh" | "atanh"
12206 | "sqr" | "cube_fn"
12207 | "mod_op" | "ceil_div" | "floor_div"
12208 | "is_finite" | "is_infinite" | "is_inf" | "is_nan"
12209 | "degrees" | "radians"
12210 | "min_abs" | "max_abs"
12211 | "saturate" | "sat01" | "wrap_around"
12212 | "rot13" | "rot47" | "caesar_shift" | "reverse_words"
12214 | "count_vowels" | "count_consonants" | "is_vowel" | "is_consonant"
12215 | "first_word" | "last_word"
12216 | "left_str" | "head_str" | "right_str" | "tail_str" | "mid_str"
12217 | "lowercase" | "uppercase"
12218 | "pascal_case" | "pc_case"
12219 | "constant_case" | "upper_snake" | "dot_case" | "path_case"
12220 | "is_palindrome" | "hamming_distance"
12221 | "longest_common_prefix" | "lcp"
12222 | "ascii_ord" | "ascii_chr" | "count_char" | "indexes_of"
12223 | "replace_first" | "replace_all_str"
12224 | "contains_any" | "contains_all"
12225 | "starts_with_any" | "ends_with_any"
12226 | "is_pair" | "is_triple"
12228 | "is_sorted" | "is_asc" | "is_sorted_desc" | "is_desc"
12229 | "is_empty_arr" | "is_empty_hash"
12230 | "is_subset" | "is_superset" | "is_permutation"
12231 | "first_eq" | "last_eq"
12233 | "index_of" | "last_index_of" | "positions_of"
12234 | "batch" | "binary_search" | "bsearch" | "linear_search" | "lsearch"
12235 | "distinct_count" | "longest" | "shortest"
12236 | "array_union" | "list_union"
12237 | "array_intersection" | "list_intersection"
12238 | "array_difference" | "list_difference"
12239 | "symmetric_diff" | "group_of_n" | "chunk_n"
12240 | "repeat_list" | "cycle_n" | "random_sample" | "sample_n"
12241 | "pick_keys" | "pick" | "omit_keys" | "omit"
12243 | "map_keys_fn" | "map_values_fn"
12244 | "hash_size" | "hash_from_pairs" | "pairs_from_hash"
12245 | "hash_eq" | "keys_sorted" | "values_sorted" | "remove_keys"
12246 | "today" | "yesterday" | "tomorrow" | "is_weekend" | "is_weekday"
12248 | "json_pretty" | "json_minify" | "escape_json" | "json_escape"
12250 | "cmd_exists" | "env_get" | "env_has" | "env_keys"
12252 | "argc" | "script_name"
12253 | "has_stdin_tty" | "has_stdout_tty" | "has_stderr_tty"
12254 | "uuid_v4" | "nanoid" | "short_id" | "is_uuid" | "token"
12256 | "email_domain" | "email_local"
12258 | "url_host" | "url_path" | "url_query" | "url_scheme"
12259 | "file_size" | "fsize" | "file_mtime" | "mtime"
12261 | "file_atime" | "atime" | "file_ctime" | "ctime"
12262 | "is_symlink" | "is_readable" | "is_writable" | "is_executable"
12263 | "path_is_abs" | "path_is_rel"
12264 | "min_max" | "percentile" | "harmonic_mean" | "geometric_mean" | "zscore"
12266 | "sorted" | "sorted_desc" | "sorted_nums" | "sorted_by_length"
12267 | "reverse_list" | "list_reverse"
12268 | "without" | "without_nth" | "take_last" | "drop_last"
12269 | "pairwise" | "zipmap"
12270 | "format_bytes" | "human_bytes"
12271 | "format_duration" | "human_duration"
12272 | "format_number" | "group_number"
12273 | "format_percent" | "pad_number"
12274 | "spaceship" | "cmp_num" | "cmp_str"
12275 | "compare_versions" | "version_cmp"
12276 | "hash_insert" | "hash_update" | "hash_delete"
12277 | "matches_regex" | "re_match"
12278 | "count_regex_matches" | "regex_extract"
12279 | "regex_split_str" | "regex_replace_str"
12280 | "shuffle_chars" | "random_char" | "nth_word"
12281 | "head_lines" | "tail_lines" | "count_substring"
12282 | "is_valid_hex" | "hex_upper" | "hex_lower"
12283 | "ms_to_s" | "s_to_ms" | "ms_to_ns" | "ns_to_ms"
12284 | "us_to_ns" | "ns_to_us"
12285 | "liters_to_gallons" | "gallons_to_liters"
12286 | "liters_to_ml" | "ml_to_liters"
12287 | "cups_to_ml" | "ml_to_cups"
12288 | "newtons_to_lbf" | "lbf_to_newtons"
12289 | "joules_to_cal" | "cal_to_joules"
12290 | "watts_to_hp" | "hp_to_watts"
12291 | "pascals_to_psi" | "psi_to_pascals"
12292 | "bar_to_pascals" | "pascals_to_bar"
12293 | "match"
12295 | "fst" | "rest" | "rst" | "second" | "snd"
12297 | "last_clj" | "lastc" | "butlast" | "bl"
12298 | "ffirst" | "ffs" | "fnext" | "fne" | "nfirst" | "nfs" | "nnext" | "nne"
12299 | "cons" | "conj"
12300 | "peek_clj" | "pkc" | "pop_clj" | "popc"
12301 | "some" | "not_any" | "not_every"
12302 | "comp" | "compose" | "partial" | "constantly" | "complement" | "compl"
12303 | "fnil" | "juxt"
12304 | "memoize" | "memo" | "curry" | "once"
12305 | "deep_clone" | "dclone" | "deep_merge" | "dmerge" | "deep_equal" | "deq"
12306 | "iterate" | "iter" | "repeatedly" | "rptd" | "cycle" | "cyc"
12307 | "mapcat" | "mcat" | "keep" | "kp" | "remove_clj" | "remc"
12308 | "reductions" | "rdcs"
12309 | "partition_by" | "pby" | "partition_all" | "pall"
12310 | "split_at" | "spat" | "split_with" | "spw"
12311 | "assoc" | "dissoc" | "get_in" | "gin" | "assoc_in" | "ain" | "update_in" | "uin"
12312 | "into" | "empty_clj" | "empc" | "seq" | "vec_clj" | "vecc"
12313 | "apply" | "appl"
12314 | "divmod" | "dm" | "accumulate" | "accum" | "starmap" | "smap"
12316 | "zip_longest" | "zipl" | "combinations" | "comb" | "permutations" | "perm"
12317 | "cartesian_product" | "cprod" | "compress" | "cmpr" | "filterfalse" | "falf"
12318 | "islice" | "isl" | "chain_from" | "chfr" | "pairwise_iter" | "pwi"
12319 | "tee_iter" | "teei" | "groupby_iter" | "gbi"
12320 | "each_slice" | "eslice" | "each_cons" | "econs"
12321 | "one" | "none_match" | "nonem"
12322 | "find_index_fn" | "fidx" | "rindex_fn" | "ridx"
12323 | "minmax" | "mmx" | "minmax_by" | "mmxb"
12324 | "dig" | "values_at" | "vat" | "fetch_val" | "fv" | "slice_arr" | "sla"
12325 | "transform_keys" | "tkeys" | "transform_values" | "tvals"
12326 | "sum_by" | "sumb" | "uniq_by" | "uqb"
12327 | "flat_map_fn" | "fmf" | "then_fn" | "thfn" | "times_fn" | "timf"
12328 | "step" | "upto" | "downto"
12329 | "find_last" | "fndl" | "find_last_index" | "fndli"
12331 | "at_index" | "ati" | "replace_at" | "repa"
12332 | "to_sorted" | "tsrt" | "to_reversed" | "trev" | "to_spliced" | "tspl"
12333 | "flat_depth" | "fltd" | "fill_arr" | "filla" | "includes_val" | "incv"
12334 | "object_keys" | "okeys" | "object_values" | "ovals"
12335 | "object_entries" | "oents" | "object_from_entries" | "ofents"
12336 | "span_fn" | "spanf" | "break_fn" | "brkf" | "group_runs" | "gruns"
12338 | "nub" | "sort_on" | "srton"
12339 | "intersperse_val" | "isp" | "intercalate" | "ical"
12340 | "replicate_val" | "repv" | "elem_of" | "elof" | "not_elem" | "ntelm"
12341 | "lookup_assoc" | "lkpa" | "scanl" | "scanr" | "unfoldr" | "unfr"
12342 | "find_map" | "fndm" | "filter_map" | "fltm" | "fold_right" | "fldr"
12344 | "partition_either" | "peith" | "try_fold" | "tfld"
12345 | "map_while" | "mapw" | "inspect" | "insp"
12346 | "tally_by" | "talb" | "sole" | "chunk_while" | "chkw" | "count_while" | "cntw"
12348 | "insert_at" | "insa" | "delete_at" | "dela" | "update_at" | "upda"
12350 | "split_on" | "spon" | "words_from" | "wfrm" | "unwords" | "unwds"
12351 | "lines_from" | "lfrm" | "unlines" | "unlns"
12352 | "window_n" | "winn" | "adjacent_pairs" | "adjp"
12353 | "zip_all" | "zall" | "unzip_pairs" | "uzp"
12354 | "interpose" | "ipos" | "partition_n" | "partn"
12355 | "map_indexed" | "mapi" | "reduce_indexed" | "redi" | "filter_indexed" | "flti"
12356 | "group_by_fn" | "gbf" | "index_by" | "idxb" | "associate" | "assoc_fn"
12357 | "combinations_rep" | "combrep" | "inits" | "tails" | "subsequences" | "subseqs"
12359 | "nub_by" | "nubb" | "slice_when" | "slcw" | "slice_before" | "slcb" | "slice_after" | "slca"
12360 | "each_with_object" | "ewo" | "reduce_right" | "redr"
12361 | "is_sorted_by" | "issrtb" | "intersperse_with" | "ispw"
12362 | "running_reduce" | "runred" | "windowed_circular" | "wincirc"
12363 | "distinct_by" | "distb" | "average" | "mean" | "copy_within" | "cpyw"
12364 | "and_list" | "andl" | "or_list" | "orl" | "concat_map" | "cmap"
12365 | "elem_index" | "elidx" | "elem_indices" | "elidxs" | "find_indices" | "fndidxs"
12366 | "delete_first" | "delfst" | "delete_by" | "delby" | "insert_sorted" | "inssrt"
12367 | "union_list" | "unionl" | "intersect_list" | "intl"
12368 | "maximum_by" | "maxby" | "minimum_by" | "minby" | "batched" | "btch"
12369 | "match_all" | "mall" | "capture_groups" | "capg" | "is_match" | "ism"
12371 | "split_regex" | "splre" | "replace_regex" | "replre"
12372 | "is_ascii" | "isasc" | "to_ascii" | "toasc"
12373 | "char_at" | "chat" | "code_point_at" | "cpat" | "from_code_point" | "fcp"
12374 | "normalize_spaces" | "nrmsp" | "remove_whitespace" | "rmws"
12375 | "pluralize" | "plur" | "ordinalize" | "ordn"
12376 | "parse_int" | "pint" | "parse_float" | "pflt" | "parse_bool" | "pbool"
12377 | "levenshtein" | "lev" | "soundex" | "sdx" | "similarity" | "sim"
12378 | "common_prefix" | "cpfx" | "common_suffix" | "csfx"
12379 | "wrap_text" | "wrpt" | "dedent" | "ddt" | "indent" | "idt"
12380 | "lerp" | "inv_lerp" | "ilerp" | "smoothstep" | "smst" | "remap"
12382 | "dot_product" | "dotp" | "cross_product" | "crossp"
12383 | "matrix_mul" | "matmul" | "mm"
12384 | "magnitude" | "mag" | "normalize_vec" | "nrmv"
12385 | "distance" | "dist" | "manhattan_distance" | "mdist"
12386 | "covariance" | "cov" | "correlation" | "corr"
12387 | "iqr" | "quantile" | "qntl" | "clamp_int" | "clpi"
12388 | "in_range" | "inrng" | "wrap_range" | "wrprng"
12389 | "sum_squares" | "sumsq" | "rms" | "cumsum" | "csum" | "cumprod" | "cprod_acc" | "diff"
12390 | "add_days" | "addd" | "add_hours" | "addh" | "add_minutes" | "addm"
12392 | "diff_days" | "diffd" | "diff_hours" | "diffh"
12393 | "start_of_day" | "sod" | "end_of_day" | "eod"
12394 | "start_of_hour" | "soh" | "start_of_minute" | "som"
12395 | "urle" | "urld"
12397 | "html_encode" | "htmle" | "html_decode" | "htmld"
12398 | "adler32" | "adl32" | "fnv1a" | "djb2"
12399 | "is_credit_card" | "iscc" | "is_isbn10" | "isbn10" | "is_isbn13" | "isbn13"
12401 | "is_iban" | "isiban" | "is_hex_str" | "ishex" | "is_binary_str" | "isbin"
12402 | "is_octal_str" | "isoct" | "is_json" | "isjson" | "is_base64" | "isb64"
12403 | "is_semver" | "issv" | "is_slug" | "isslug" | "slugify" | "slug"
12404 | "mode_stat" | "mstat" | "sampn" | "weighted_sample" | "wsamp"
12406 | "shuffle_arr" | "shuf" | "argmax" | "amax" | "argmin" | "amin"
12407 | "argsort" | "asrt" | "rank" | "rnk" | "dense_rank" | "drnk"
12408 | "partition_point" | "ppt" | "lower_bound" | "lbound"
12409 | "upper_bound" | "ubound" | "equal_range" | "eqrng"
12410 | "matrix_add" | "madd" | "matrix_sub" | "msub" | "matrix_mult" | "mmult"
12412 | "matrix_scalar" | "mscal" | "matrix_identity" | "mident"
12413 | "matrix_zeros" | "mzeros" | "matrix_ones" | "mones"
12414 | "matrix_diag" | "mdiag" | "matrix_trace" | "mtrace"
12415 | "matrix_row" | "mrow" | "matrix_col" | "mcol"
12416 | "matrix_shape" | "mshape" | "matrix_det" | "mdet"
12417 | "matrix_scale" | "mat_scale" | "diagonal" | "diag"
12418 | "topological_sort" | "toposort" | "bfs_traverse" | "bfs"
12420 | "dfs_traverse" | "dfs" | "shortest_path_bfs" | "spbfs"
12421 | "connected_components_graph" | "ccgraph"
12422 | "has_cycle_graph" | "hascyc" | "is_bipartite_graph" | "isbip"
12423 | "is_ipv4_addr" | "isip4" | "is_ipv6_addr" | "isip6"
12425 | "is_mac_addr" | "ismac" | "is_port_num" | "isport"
12426 | "is_hostname_valid" | "ishost"
12427 | "is_iso_date" | "isisodt" | "is_iso_time" | "isisotm"
12428 | "is_iso_datetime" | "isisodtm"
12429 | "is_phone_num" | "isphone" | "is_us_zip" | "iszip"
12430 | "word_wrap_text" | "wwrap" | "center_text" | "ctxt"
12432 | "ljust_text" | "ljt" | "rjust_text" | "rjt" | "zfill_num" | "zfill"
12433 | "remove_all_str" | "rmall" | "replace_n_times" | "repln"
12434 | "find_all_indices" | "fndalli"
12435 | "text_between" | "txbtwn" | "text_before" | "txbef" | "text_after" | "txaft"
12436 | "text_before_last" | "txbefl" | "text_after_last" | "txaftl"
12437 | "is_even_num" | "iseven" | "is_odd_num" | "isodd"
12439 | "is_positive_num" | "ispos" | "is_negative_num" | "isneg"
12440 | "is_zero_num" | "iszero" | "is_whole_num" | "iswhole"
12441 | "log_with_base" | "logb" | "nth_root_of" | "nroot"
12442 | "frac_part" | "fracp" | "reciprocal_of" | "recip"
12443 | "copy_sign" | "cpsgn" | "fused_mul_add" | "fmadd"
12444 | "floor_mod" | "fmod" | "floor_div_op" | "fdivop"
12445 | "signum_of" | "sgnum" | "midpoint_of" | "midpt"
12446 | "longest_run" | "lrun" | "longest_increasing" | "linc"
12448 | "longest_decreasing" | "ldec" | "max_sum_subarray" | "maxsub"
12449 | "majority_element" | "majority" | "kth_largest" | "kthl"
12450 | "kth_smallest" | "kths" | "count_inversions" | "cinv"
12451 | "is_monotonic" | "ismono" | "equilibrium_index" | "eqidx"
12452 | "jaccard_index" | "jaccard" | "dice_coefficient" | "dicecoef"
12454 | "overlap_coefficient" | "overlapcoef"
12455 | "power_set" | "powerset" | "cartesian_power" | "cartpow"
12456 | "is_isogram" | "isiso" | "is_heterogram" | "ishet"
12458 | "hamdist" | "jaro_similarity" | "jarosim"
12459 | "longest_common_substring" | "lcsub"
12460 | "longest_common_subsequence" | "lcseq"
12461 | "count_words" | "wcount" | "count_lines" | "lcount"
12462 | "count_chars" | "ccount" | "count_bytes" | "bcount"
12463 | "binomial" | "binom" | "catalan" | "catn" | "pascal_row" | "pascrow"
12465 | "is_coprime" | "iscopr" | "euler_totient" | "etot"
12466 | "mobius" | "mob" | "is_squarefree" | "issqfr"
12467 | "digital_root" | "digroot" | "is_narcissistic" | "isnarc"
12468 | "is_harshad" | "isharsh" | "is_kaprekar" | "iskap"
12469 | "day_of_year" | "doy" | "week_of_year" | "woy"
12471 | "days_in_month_fn" | "daysinmo" | "is_valid_date" | "isvdate"
12472 | "age_in_years" | "ageyrs"
12473 | "when_true" | "when_false" | "if_else" | "clamp_fn"
12476 | "attempt" | "try_fn" | "safe_div" | "safe_mod" | "safe_sqrt" | "safe_log"
12477 | "juxt2" | "juxt3" | "tap_val" | "debug_val" | "converge"
12478 | "iterate_n" | "unfold" | "arity_of" | "is_callable"
12479 | "coalesce" | "default_to" | "fallback"
12480 | "apply_list" | "zip_apply" | "scan"
12481 | "keep_if" | "reject_if" | "group_consecutive"
12482 | "after_n" | "before_n" | "clamp_list" | "normalize_list" | "softmax"
12483
12484 | "matrix_multiply" | "mat_mul"
12488 | "identity_matrix" | "eye" | "zeros_matrix" | "zeros" | "ones_matrix" | "ones"
12489
12490
12491
12492 | "vec_normalize" | "unit_vec" | "vec_add" | "vec_sub" | "vec_scale"
12493 | "linspace" | "arange"
12494 | "re_test" | "re_find_all" | "re_groups" | "re_escape"
12496 | "re_split_limit" | "glob_to_regex" | "is_regex_valid"
12497 | "cwd" | "pwd_str" | "cpu_count" | "is_root" | "uptime_secs"
12499 | "env_pairs" | "env_set" | "env_remove" | "hostname_str" | "is_tty" | "signal_name"
12500 | "stack_new" | "queue_new" | "lru_new"
12502 | "counter" | "counter_most_common" | "defaultdict" | "ordered_set"
12503 | "bitset_new" | "bitset_set" | "bitset_test" | "bitset_clear"
12504 | "abs_ceil" | "abs_each" | "abs_floor" | "ceil_each" | "dec_each"
12506 | "double_each" | "floor_each" | "half_each" | "inc_each" | "length_each"
12507 | "negate_each" | "not_each" | "offset_each" | "reverse_each" | "round_each"
12508 | "scale_each" | "sqrt_each" | "square_each" | "to_float_each" | "to_int_each"
12509 | "trim_each" | "type_each" | "upcase_each" | "downcase_each" | "bool_each"
12510 | "avogadro" | "boltzmann" | "golden_ratio" | "gravity" | "ln10" | "ln2"
12512 | "planck" | "speed_of_light" | "sqrt2"
12513 | "bmi_calc" | "compound_interest" | "dew_point" | "discount_amount"
12515 | "force_mass_acc" | "freq_wavelength" | "future_value" | "haversine"
12516 | "heat_index" | "kinetic_energy" | "margin_price" | "markup_price"
12517 | "mortgage_payment" | "ohms_law_i" | "ohms_law_r" | "ohms_law_v"
12518 | "potential_energy" | "present_value" | "simple_interest" | "speed_distance_time"
12519 | "tax_amount" | "tip_amount" | "wavelength_freq" | "wind_chill"
12520 | "angle_between_deg" | "approx_eq" | "chebyshev_distance" | "copysign"
12522 | "cosine_similarity" | "cube_root" | "entropy" | "float_bits" | "fma"
12523 | "int_bits" | "jaccard_similarity" | "log_base" | "mae" | "mse" | "nth_root"
12524 | "r_squared" | "reciprocal" | "relu" | "rmse" | "rotate_point" | "round_to"
12525 | "sigmoid" | "signum" | "square_root"
12526 | "cubes_seq" | "fibonacci_seq" | "powers_of_seq" | "primes_seq"
12528 | "squares_seq" | "triangular_seq"
12529 | "alternate_case" | "angle_bracket" | "bracket" | "byte_length"
12531 | "bytes_to_hex_str" | "camel_words" | "char_length" | "chars_to_string"
12532 | "chomp_str" | "chop_str" | "filter_chars" | "from_csv_line" | "hex_to_bytes"
12533 | "insert_str" | "intersperse_char" | "ljust" | "map_chars" | "mirror_string"
12534 | "normalize_whitespace" | "only_alnum" | "only_alpha" | "only_ascii"
12535 | "only_digits" | "parenthesize" | "remove_str" | "repeat_string" | "rjust"
12536 | "sentence_case" | "string_count" | "string_sort" | "string_to_chars"
12537 | "string_unique_chars" | "substring" | "to_csv_line" | "trim_left" | "trim_right"
12538 | "xor_strings"
12539 | "adjacent_difference" | "append_elem" | "consecutive_pairs" | "contains_elem"
12541 | "count_elem" | "drop_every" | "duplicate_count" | "elem_at" | "find_first"
12542 | "first_elem" | "flatten_once" | "fold_left" | "from_digits" | "from_pairs"
12543 | "group_by_size" | "hash_filter_keys" | "hash_from_list" | "hash_map_values"
12544 | "hash_merge_deep" | "hash_to_list" | "hash_zip" | "head_n" | "histogram_bins"
12545 | "index_of_elem" | "init_list" | "interleave_lists" | "last_elem" | "least_common"
12546 | "list_compact" | "list_eq" | "list_flatten_deep" | "max_list" | "mean_list"
12547 | "min_list" | "mode_list" | "most_common" | "partition_two" | "prefix_sums"
12548 | "prepend" | "product_list" | "remove_at" | "remove_elem" | "remove_first_elem"
12549 | "repeat_elem" | "running_max" | "running_min" | "sample_one" | "scan_left"
12550 | "second_elem" | "span" | "suffix_sums" | "sum_list" | "tail_n" | "take_every"
12551 | "third_elem" | "to_array" | "to_pairs" | "trimmed_mean" | "unique_count_of"
12552 | "wrap_index" | "digits_of"
12553 | "all_match" | "any_match" | "is_between" | "is_blank_or_nil" | "is_divisible_by"
12555 | "is_email" | "is_even" | "is_falsy" | "is_fibonacci" | "is_hex_color"
12556 | "is_in_range" | "is_ipv4" | "is_multiple_of" | "is_negative" | "is_nil"
12557 | "is_nonzero" | "is_odd" | "is_perfect_square" | "is_positive" | "is_power_of"
12558 | "is_prefix" | "is_present" | "is_strictly_decreasing" | "is_strictly_increasing"
12559 | "is_suffix" | "is_triangular" | "is_truthy" | "is_url" | "is_whole" | "is_zero"
12560 | "count_digits" | "count_letters" | "count_lower" | "count_match"
12562 | "count_punctuation" | "count_spaces" | "count_upper" | "defined_count"
12563 | "empty_count" | "falsy_count" | "nonempty_count" | "numeric_count"
12564 | "truthy_count" | "undef_count"
12565 | "assert_type" | "between" | "clamp_each" | "die_if" | "die_unless"
12567 | "join_colons" | "join_commas" | "join_dashes" | "join_dots" | "join_lines"
12568 | "join_pipes" | "join_slashes" | "join_spaces" | "join_tabs" | "measure"
12569 | "max_float" | "min_float" | "noop_val" | "nop" | "pass" | "pred" | "succ"
12570 | "tap_debug" | "to_bool" | "to_float" | "to_int" | "to_string" | "void"
12571 | "range_exclusive" | "range_inclusive"
12572 | "aliquot_sum" | "autocorrelation" | "bell_number" | "cagr" | "coeff_of_variation"
12574 | "collatz_length" | "collatz_sequence" | "convolution" | "cross_entropy"
12575 | "depreciation_double" | "depreciation_linear" | "discount" | "divisors"
12576 | "epsilon" | "euclidean_distance" | "euler_number" | "exponential_moving_average"
12577 | "f64_max" | "f64_min" | "fft_magnitude" | "goldbach" | "i64_max" | "i64_min"
12578 | "kurtosis" | "linear_regression" | "look_and_say" | "lucas" | "luhn_check"
12579 | "mean_absolute_error" | "mean_squared_error" | "median_absolute_deviation"
12580 | "minkowski_distance" | "moving_average" | "multinomial" | "neg_inf" | "npv"
12581 | "num_divisors" | "partition_number" | "pascals_triangle" | "skewness"
12582 | "standard_error" | "subfactorial" | "sum_divisors" | "totient_sum"
12583 | "tribonacci" | "weighted_mean" | "winsorize"
12584 | "chi_square_stat" | "describe" | "five_number_summary"
12586 | "gini" | "gini_coefficient" | "lorenz_curve" | "outliers_iqr"
12587 | "percentile_rank" | "quartiles" | "sample_stddev" | "sample_variance"
12588 | "spearman_correlation" | "t_test_one_sample" | "t_test_two_sample"
12589 | "z_score" | "z_scores"
12590 | "abundant_numbers" | "deficient_numbers" | "is_abundant" | "is_deficient"
12592 | "is_pentagonal" | "is_perfect" | "is_smith" | "next_prime" | "nth_prime"
12593 | "pentagonal_number" | "perfect_numbers" | "prev_prime" | "prime_factors"
12594 | "prime_pi" | "primes_up_to" | "triangular_number" | "twin_primes"
12595 | "area_circle" | "area_ellipse" | "area_rectangle" | "area_trapezoid" | "area_triangle"
12597 | "bearing" | "circumference" | "cone_volume" | "cylinder_volume" | "heron_area"
12598 | "midpoint" | "perimeter_rectangle" | "perimeter_triangle" | "point_distance"
12599 | "polygon_area" | "slope" | "sphere_surface" | "sphere_volume" | "triangle_hypotenuse"
12600 | "angle_between" | "arc_length" | "bounding_box" | "centroid"
12602 | "circle_from_three_points" | "convex_hull" | "ellipse_perimeter"
12603 | "frustum_volume" | "haversine_distance" | "line_intersection"
12604 | "point_in_polygon" | "polygon_perimeter" | "pyramid_volume"
12605 | "reflect_point" | "scale_point" | "sector_area"
12606 | "torus_surface" | "torus_volume" | "translate_point"
12607 | "vector_angle" | "vector_cross" | "vector_dot" | "vector_magnitude" | "vector_normalize"
12608 | "avogadro_number" | "boltzmann_constant" | "electron_mass" | "elementary_charge"
12610 | "gravitational_constant" | "phi" | "pi" | "planck_constant" | "proton_mass"
12611 | "sol" | "tau"
12612 | "bac_estimate" | "bmi" | "break_even" | "margin" | "markup" | "roi" | "tax" | "tip"
12614 | "amortization_schedule" | "black_scholes_call" | "black_scholes_put"
12616 | "bond_price" | "bond_yield" | "capm" | "continuous_compound"
12617 | "discounted_payback" | "duration" | "irr"
12618 | "max_drawdown" | "modified_duration" | "nper" | "num_periods" | "payback_period"
12619 | "pmt" | "pv" | "rule_of_72" | "sharpe_ratio" | "sortino_ratio"
12620 | "wacc" | "xirr"
12621 | "acronym" | "atbash" | "bigrams" | "camel_to_snake" | "char_frequencies"
12623 | "chunk_string" | "collapse_whitespace" | "dedent_text" | "indent_text"
12624 | "initials" | "leetspeak" | "mask_string" | "ngrams" | "pig_latin"
12625 | "remove_consonants" | "remove_vowels" | "reverse_each_word" | "snake_to_camel"
12626 | "sort_words" | "string_distance" | "string_multiply" | "strip_html"
12627 | "trigrams" | "unique_words" | "word_frequencies" | "zalgo"
12628 | "braille_encode" | "double_metaphone" | "metaphone" | "morse_decode"
12630 | "morse_encode" | "nato_phonetic" | "phonetic_digit" | "subscript" | "superscript"
12631 | "to_emoji_num"
12632 | "int_to_roman" | "roman_add" | "roman_numeral_list" | "roman_to_int"
12634 | "base_convert" | "binary_to_gray" | "gray_code_sequence" | "gray_to_binary"
12636 | "ansi_256" | "ansi_truecolor" | "color_blend" | "color_complement"
12638 | "color_darken" | "color_distance" | "color_grayscale" | "color_invert"
12639 | "color_lighten" | "hsl_to_rgb" | "hsv_to_rgb" | "random_color"
12640 | "rgb_to_hsl" | "rgb_to_hsv"
12641 | "matrix_flatten" | "matrix_from_rows" | "matrix_hadamard" | "matrix_inverse"
12643 | "matrix_map" | "matrix_max" | "matrix_min" | "matrix_power" | "matrix_sum"
12644 | "matrix_transpose"
12645 | "binary_insert" | "bucket" | "clamp_array" | "group_consecutive_by"
12647 | "histogram" | "merge_sorted" | "next_permutation" | "normalize_array"
12648 | "normalize_range" | "peak_detect" | "range_compress" | "range_expand"
12649 | "reservoir_sample" | "run_length_decode_str" | "run_length_encode_str"
12650 | "zero_crossings"
12651 | "apply_window" | "bandpass_filter" | "cross_correlation" | "dft"
12653 | "downsample" | "energy" | "envelope" | "highpass_filter" | "idft"
12654 | "lowpass_filter" | "median_filter" | "normalize_signal" | "phase_spectrum"
12655 | "power_spectrum" | "resample" | "spectral_centroid" | "spectrogram" | "upsample"
12656 | "window_blackman" | "window_hamming" | "window_hann" | "window_kaiser"
12657 | "is_anagram" | "is_balanced_parens" | "is_control" | "is_numeric_string"
12659 | "is_pangram" | "is_printable" | "is_valid_cidr" | "is_valid_cron"
12660 | "is_valid_hex_color" | "is_valid_latitude" | "is_valid_longitude" | "is_valid_mime"
12661 | "eval_rpn" | "fizzbuzz" | "game_of_life_step" | "mandelbrot_char"
12663 | "sierpinski" | "tower_of_hanoi" | "truth_table"
12664 | "byte_size" | "degrees_to_compass" | "to_string_val" | "type_of"
12666 | "quadratic_roots" | "quadratic_discriminant" | "arithmetic_series"
12668 | "geometric_series" | "stirling_approx"
12669 | "double_factorial" | "rising_factorial" | "falling_factorial"
12670 | "gamma_approx" | "erf_approx" | "normal_pdf" | "normal_cdf"
12671 | "poisson_pmf" | "exponential_pdf" | "inverse_lerp"
12672 | "map_range"
12673 | "momentum" | "impulse" | "work" | "power_phys" | "torque" | "angular_velocity"
12675 | "centripetal_force" | "escape_velocity" | "orbital_velocity" | "orbital_period"
12676 | "gravitational_force" | "coulomb_force" | "electric_field" | "capacitance"
12677 | "capacitor_energy" | "inductor_energy" | "resonant_frequency"
12678 | "rc_time_constant" | "rl_time_constant" | "impedance_rlc"
12679 | "relativistic_mass" | "lorentz_factor" | "time_dilation" | "length_contraction"
12680 | "relativistic_energy" | "rest_energy" | "de_broglie_wavelength"
12681 | "photon_energy" | "photon_energy_wavelength" | "schwarzschild_radius"
12682 | "stefan_boltzmann" | "wien_displacement" | "ideal_gas_pressure" | "ideal_gas_volume"
12683 | "projectile_range" | "projectile_max_height" | "projectile_time"
12684 | "spring_force" | "spring_energy" | "pendulum_period" | "doppler_frequency"
12685 | "decibel_ratio" | "snells_law" | "brewster_angle" | "critical_angle"
12686 | "lens_power" | "thin_lens" | "magnification_lens"
12687 | "euler_mascheroni" | "apery_constant" | "feigenbaum_delta" | "feigenbaum_alpha"
12689 | "catalan_constant" | "khinchin_constant" | "glaisher_constant"
12690 | "plastic_number" | "silver_ratio" | "supergolden_ratio"
12691 | "vacuum_permittivity" | "vacuum_permeability" | "coulomb_constant"
12693 | "fine_structure_constant" | "rydberg_constant" | "bohr_radius"
12694 | "bohr_magneton" | "nuclear_magneton" | "stefan_boltzmann_constant"
12695 | "wien_constant" | "gas_constant" | "faraday_constant" | "neutron_mass"
12696 | "atomic_mass_unit" | "earth_mass" | "earth_radius" | "sun_mass" | "sun_radius"
12697 | "astronomical_unit" | "light_year" | "parsec" | "hubble_constant"
12698 | "planck_length" | "planck_time" | "planck_mass" | "planck_temperature"
12699 | "matrix_solve" | "msolve" | "solve"
12701 | "matrix_lu" | "mlu" | "matrix_qr" | "mqr"
12702 | "matrix_eigenvalues" | "meig" | "eigenvalues" | "eig"
12703 | "matrix_norm" | "mnorm" | "matrix_cond" | "mcond" | "cond"
12704 | "matrix_pinv" | "mpinv" | "pinv"
12705 | "matrix_cholesky" | "mchol" | "cholesky"
12706 | "matrix_det_general" | "mdetg" | "det"
12707 | "welch_ttest" | "welcht" | "paired_ttest" | "pairedt"
12709 | "cohen_d" | "cohend" | "anova_oneway" | "anova" | "anova1"
12710 | "spearman_corr" | "rho" | "kendall_tau" | "kendall" | "ktau"
12711 | "confidence_interval" | "ci"
12712 | "beta_pdf" | "betapdf" | "gamma_pdf" | "gammapdf"
12714 | "chi2_pdf" | "chi2pdf" | "chi_squared_pdf"
12715 | "t_pdf" | "tpdf" | "student_pdf"
12716 | "f_pdf" | "fpdf" | "fisher_pdf"
12717 | "lognormal_pdf" | "lnormpdf" | "weibull_pdf" | "weibpdf"
12718 | "cauchy_pdf" | "cauchypdf" | "laplace_pdf" | "laplacepdf"
12719 | "pareto_pdf" | "paretopdf"
12720 | "lagrange_interp" | "lagrange" | "linterp"
12722 | "cubic_spline" | "cspline" | "spline"
12723 | "poly_eval" | "polyval" | "polynomial_fit" | "polyfit"
12724 | "trapz" | "trapezoid" | "simpson" | "simps"
12726 | "numerical_diff" | "numdiff" | "diff_array"
12727 | "cumtrapz" | "cumulative_trapz"
12728 | "bisection" | "bisect" | "newton_method" | "newton" | "newton_raphson"
12730 | "golden_section" | "golden" | "gss"
12731 | "rk4" | "runge_kutta" | "rk4_ode" | "euler_ode" | "euler_method"
12733 | "dijkstra" | "shortest_path" | "bellman_ford" | "bellmanford"
12735 | "floyd_warshall" | "floydwarshall" | "apsp"
12736 | "prim_mst" | "mst" | "prim"
12737 | "cot" | "sec" | "csc" | "acot" | "asec" | "acsc" | "sinc" | "versin" | "versine"
12739 | "leaky_relu" | "lrelu" | "elu" | "selu" | "gelu"
12741 | "silu" | "swish" | "mish" | "softplus"
12742 | "hard_sigmoid" | "hardsigmoid" | "hard_swish" | "hardswish"
12743 | "bessel_j0" | "j0" | "bessel_j1" | "j1"
12745 | "lambert_w" | "lambertw" | "productlog"
12746 | "mod_exp" | "modexp" | "powmod"
12748 | "mod_inv" | "modinv" | "chinese_remainder" | "crt"
12749 | "miller_rabin" | "millerrabin" | "is_probable_prime"
12750 | "derangements" | "stirling2" | "stirling_second"
12752 | "bernoulli_number" | "bernoulli" | "harmonic_number" | "harmonic"
12753 | "drag_force" | "fdrag" | "ideal_gas" | "pv_nrt"
12755 | "bs_delta" | "bsdelta" | "option_delta"
12757 | "bs_gamma" | "bsgamma" | "option_gamma"
12758 | "bs_vega" | "bsvega" | "option_vega"
12759 | "bs_theta" | "bstheta" | "option_theta"
12760 | "bs_rho" | "bsrho" | "option_rho"
12761 | "bond_duration" | "mac_duration"
12762 | "dct" | "idct" | "goertzel" | "chirp" | "chirp_signal"
12764 | "base85_encode" | "b85e" | "ascii85_encode" | "a85e"
12766 | "base85_decode" | "b85d" | "ascii85_decode" | "a85d"
12767 | "pnorm" | "qnorm" | "pbinom" | "dbinom" | "ppois"
12769 | "punif" | "pexp" | "pweibull" | "plnorm" | "pcauchy"
12770 | "rbind" | "cbind"
12772 | "row_sums" | "rowSums" | "col_sums" | "colSums"
12773 | "row_means" | "rowMeans" | "col_means" | "colMeans"
12774 | "outer_product" | "outer" | "crossprod" | "tcrossprod"
12775 | "nrow" | "ncol" | "prop_table" | "proptable"
12776 | "cummax" | "cummin" | "scale_vec" | "scale"
12778 | "which_fn" | "tabulate"
12779 | "duplicated" | "duped" | "rev_vec"
12780 | "seq_fn" | "rep_fn" | "rep"
12781 | "cut_bins" | "cut" | "find_interval" | "findInterval"
12782 | "ecdf_fn" | "ecdf" | "density_est" | "density"
12783 | "embed_ts" | "embed"
12784 | "shapiro_test" | "shapiro" | "ks_test" | "ks"
12786 | "wilcox_test" | "wilcox" | "mann_whitney"
12787 | "prop_test" | "proptest" | "binom_test" | "binomtest"
12788 | "sapply" | "tapply" | "do_call" | "docall"
12790 | "kmeans" | "prcomp" | "pca"
12792 | "rnorm" | "runif" | "rexp" | "rbinom" | "rpois" | "rgeom"
12794 | "rgamma" | "rbeta" | "rchisq" | "rt" | "rf"
12795 | "rweibull" | "rlnorm" | "rcauchy"
12796 | "qunif" | "qexp" | "qweibull" | "qlnorm" | "qcauchy"
12798 | "pgamma" | "pbeta" | "pchisq" | "pt_cdf" | "pt" | "pf_cdf" | "pf"
12800 | "dgeom" | "dunif" | "dnbinom" | "dhyper"
12802 | "lowess" | "loess" | "approx_fn" | "approx"
12804 | "lm_fit" | "lm"
12806 | "qgamma" | "qbeta" | "qchisq" | "qt_fn" | "qt" | "qf_fn" | "qf"
12808 | "qbinom" | "qpois"
12809 | "acf_fn" | "acf" | "pacf_fn" | "pacf"
12811 | "diff_lag" | "diff_ts" | "ts_filter" | "filter_ts"
12812 | "predict_lm" | "predict" | "confint_lm" | "confint"
12814 | "cor_matrix" | "cor_mat" | "cov_matrix" | "cov_mat"
12816 | "mahalanobis" | "mahal" | "dist_matrix" | "dist_mat"
12817 | "hclust" | "cutree" | "weighted_var" | "wvar" | "cov2cor"
12818 | "scatter_svg" | "scatter_plot" | "line_svg" | "line_plot"
12820 | "plot_svg" | "hist_svg" | "histogram_svg"
12821 | "boxplot_svg" | "box_plot" | "bar_svg" | "barchart_svg"
12822 | "pie_svg" | "pie_chart" | "heatmap_svg" | "heatmap"
12823 | "donut_svg" | "donut" | "area_svg" | "area_chart"
12824 | "hbar_svg" | "hbar" | "radar_svg" | "radar" | "spider"
12825 | "candlestick_svg" | "candlestick" | "ohlc"
12826 | "violin_svg" | "violin" | "cor_heatmap" | "cor_matrix_svg"
12827 | "stacked_bar_svg" | "stacked_bar"
12828 | "wordcloud_svg" | "wordcloud" | "wcloud"
12829 | "treemap_svg" | "treemap"
12830 | "pvw"
12831 | "cyber_city" | "cyber_grid" | "cyber_rain" | "matrix_rain"
12833 | "cyber_glitch" | "glitch_text" | "cyber_banner" | "neon_banner"
12834 | "cyber_circuit" | "cyber_skull" | "cyber_eye"
12835 => Some(name),
12836 _ => None,
12837 }
12838 }
12839
12840 fn is_reserved_hash_name(name: &str) -> bool {
12843 matches!(
12844 name,
12845 "b" | "pc"
12846 | "e"
12847 | "a"
12848 | "d"
12849 | "c"
12850 | "p"
12851 | "all"
12852 | "stryke::builtins"
12853 | "stryke::perl_compats"
12854 | "stryke::extensions"
12855 | "stryke::aliases"
12856 | "stryke::descriptions"
12857 | "stryke::categories"
12858 | "stryke::primaries"
12859 | "stryke::all"
12860 )
12861 }
12862
12863 fn check_udf_shadows_builtin(&self, name: &str, line: usize) -> PerlResult<()> {
12866 if Self::is_known_bareword(name) || Self::is_try_builtin_name(name) {
12867 return Err(self.syntax_err(
12868 format!(
12869"`{name}` is a stryke builtin and cannot be redefined (this is not Perl 5; use `fn` not `sub`, or pass --compat)"
12870 ),
12871 line,
12872 ));
12873 }
12874 Ok(())
12875 }
12876
12877 fn check_hash_shadows_reserved(&self, name: &str, line: usize) -> PerlResult<()> {
12880 if Self::is_reserved_hash_name(name) {
12881 return Err(self.syntax_err(
12882 format!(
12883"`%{name}` is a stryke reserved hash and cannot be redefined (this is not Perl 5; pass --compat for Perl 5 mode)"
12884 ),
12885 line,
12886 ));
12887 }
12888 Ok(())
12889 }
12890
12891 fn validate_hash_assignment(&self, value: &Expr, line: usize) -> PerlResult<()> {
12894 match &value.kind {
12895 ExprKind::Integer(_) | ExprKind::Float(_) => {
12896 return Err(self.syntax_err(
12897 "cannot assign scalar to hash — use %h = (key => value) or %h = %{$hashref}",
12898 line,
12899 ));
12900 }
12901 ExprKind::String(_) | ExprKind::InterpolatedString(_) | ExprKind::Bareword(_) => {
12902 return Err(self.syntax_err(
12903 "cannot assign string to hash — use %h = (key => value) or %h = %{$hashref}",
12904 line,
12905 ));
12906 }
12907 ExprKind::ArrayRef(_) => {
12908 return Err(self.syntax_err(
12909 "cannot assign arrayref to hash — use %h = @{$arrayref} for even-length list",
12910 line,
12911 ));
12912 }
12913 ExprKind::ScalarRef(inner) => {
12914 if matches!(inner.kind, ExprKind::ArrayVar(_)) {
12915 return Err(self.syntax_err(
12916 "cannot assign \\@array to hash — use %h = @array for even-length list",
12917 line,
12918 ));
12919 }
12920 if matches!(inner.kind, ExprKind::HashVar(_)) {
12921 return Err(self.syntax_err(
12922 "cannot assign \\%hash to hash — use %h = %other directly",
12923 line,
12924 ));
12925 }
12926 }
12927 ExprKind::HashRef(_) => {
12928 return Err(self.syntax_err(
12929 "cannot assign hashref to hash — use %h = %{$hashref} to dereference",
12930 line,
12931 ));
12932 }
12933 ExprKind::CodeRef { .. } => {
12934 return Err(self.syntax_err("cannot assign coderef to hash", line));
12935 }
12936 ExprKind::Undef => {
12937 return Err(
12938 self.syntax_err("cannot assign undef to hash — use %h = () to empty", line)
12939 );
12940 }
12941 ExprKind::List(items)
12942 if items.len() % 2 != 0
12943 && !items.iter().any(|e| {
12944 matches!(
12945 e.kind,
12946 ExprKind::ArrayVar(_)
12947 | ExprKind::HashVar(_)
12948 | ExprKind::FuncCall { .. }
12949 | ExprKind::Deref { .. }
12950 | ExprKind::ScalarVar(_)
12951 )
12952 }) =>
12953 {
12954 return Err(self.syntax_err(
12955 format!(
12956 "odd-length list ({} elements) in hash assignment — missing value for last key",
12957 items.len()
12958 ),
12959 line,
12960 ));
12961 }
12962 _ => {}
12963 }
12964 Ok(())
12965 }
12966
12967 fn validate_array_assignment(&self, value: &Expr, line: usize) -> PerlResult<()> {
12972 if let ExprKind::Undef = &value.kind {
12973 return Err(
12974 self.syntax_err("cannot assign undef to array — use @a = () to empty", line)
12975 );
12976 }
12977 Ok(())
12978 }
12979
12980 fn validate_scalar_assignment(&self, value: &Expr, line: usize) -> PerlResult<()> {
12983 if let ExprKind::List(items) = &value.kind {
12984 if items.len() > 1 {
12985 return Err(self.syntax_err(
12986 format!(
12987 "cannot assign {}-element list to scalar — Perl 5 silently takes last element; use ($x) = (list) or $x = $list[-1]",
12988 items.len()
12989 ),
12990 line,
12991 ));
12992 }
12993 }
12994 Ok(())
12995 }
12996
12997 fn validate_assignment(&self, target: &Expr, value: &Expr, line: usize) -> PerlResult<()> {
12999 if crate::compat_mode() {
13000 return Ok(());
13001 }
13002 match &target.kind {
13003 ExprKind::HashVar(_) => self.validate_hash_assignment(value, line),
13004 ExprKind::ArrayVar(_) => self.validate_array_assignment(value, line),
13005 ExprKind::ScalarVar(_) => self.validate_scalar_assignment(value, line),
13006 _ => Ok(()),
13007 }
13008 }
13009
13010 fn parse_block_or_bareword_cmp_block(&mut self) -> PerlResult<Block> {
13014 if matches!(self.peek(), Token::LBrace) {
13015 return self.parse_block();
13016 }
13017 let line = self.peek_line();
13018 if let Token::Ident(ref name) = self.peek().clone() {
13020 if matches!(
13021 self.peek_at(1),
13022 Token::Comma | Token::Semicolon | Token::RBrace | Token::Eof | Token::PipeForward
13023 ) {
13024 let name = name.clone();
13025 self.advance();
13026 let body = Expr {
13027 kind: ExprKind::FuncCall {
13028 name,
13029 args: vec![
13030 Expr {
13031 kind: ExprKind::ScalarVar("a".to_string()),
13032 line,
13033 },
13034 Expr {
13035 kind: ExprKind::ScalarVar("b".to_string()),
13036 line,
13037 },
13038 ],
13039 },
13040 line,
13041 };
13042 return Ok(vec![Statement::new(StmtKind::Expression(body), line)]);
13043 }
13044 }
13045 let expr = self.parse_assign_expr_stop_at_pipe()?;
13047 Ok(vec![Statement::new(StmtKind::Expression(expr), line)])
13048 }
13049
13050 fn parse_fan_optional_progress(
13052 &mut self,
13053 which: &'static str,
13054 ) -> PerlResult<Option<Box<Expr>>> {
13055 let line = self.peek_line();
13056 if self.eat(&Token::Comma) {
13057 match self.peek() {
13058 Token::Ident(ref kw)
13059 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) =>
13060 {
13061 self.advance();
13062 self.expect(&Token::FatArrow)?;
13063 return Ok(Some(Box::new(self.parse_assign_expr()?)));
13064 }
13065 _ => {
13066 return Err(self.syntax_err(
13067 format!("{which}: expected `progress => EXPR` after comma"),
13068 line,
13069 ));
13070 }
13071 }
13072 }
13073 if let Token::Ident(ref kw) = self.peek().clone() {
13074 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
13075 self.advance();
13076 self.expect(&Token::FatArrow)?;
13077 return Ok(Some(Box::new(self.parse_assign_expr()?)));
13078 }
13079 }
13080 Ok(None)
13081 }
13082
13083 fn parse_assign_expr_list_optional_progress(&mut self) -> PerlResult<(Expr, Option<Expr>)> {
13090 if self.in_pipe_rhs()
13096 && matches!(
13097 self.peek(),
13098 Token::Semicolon
13099 | Token::RBrace
13100 | Token::RParen
13101 | Token::Eof
13102 | Token::PipeForward
13103 | Token::Comma
13104 )
13105 {
13106 return Ok((self.pipe_placeholder_list(self.peek_line()), None));
13107 }
13108 let mut parts = vec![self.parse_assign_expr_stop_at_pipe()?];
13109 loop {
13110 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
13111 break;
13112 }
13113 if matches!(
13114 self.peek(),
13115 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof | Token::PipeForward
13116 ) {
13117 break;
13118 }
13119 if self.peek_is_postfix_stmt_modifier_keyword() {
13120 break;
13121 }
13122 if let Token::Ident(ref kw) = self.peek().clone() {
13123 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
13124 self.advance();
13125 self.expect(&Token::FatArrow)?;
13126 let prog = self.parse_assign_expr_stop_at_pipe()?;
13127 return Ok((merge_expr_list(parts), Some(prog)));
13128 }
13129 }
13130 parts.push(self.parse_assign_expr_stop_at_pipe()?);
13131 }
13132 Ok((merge_expr_list(parts), None))
13133 }
13134
13135 fn parse_one_arg(&mut self) -> PerlResult<Expr> {
13136 if matches!(self.peek(), Token::LParen) {
13137 self.advance();
13138 let expr = self.parse_expression()?;
13139 self.expect(&Token::RParen)?;
13140 Ok(expr)
13141 } else {
13142 self.parse_assign_expr_stop_at_pipe()
13143 }
13144 }
13145
13146 fn parse_one_arg_or_default(&mut self) -> PerlResult<Expr> {
13147 if matches!(
13154 self.peek(),
13155 Token::Semicolon
13157 | Token::RBrace
13158 | Token::RParen
13159 | Token::RBracket
13160 | Token::Eof
13161 | Token::Comma
13162 | Token::FatArrow
13163 | Token::PipeForward
13164 | Token::Question
13166 | Token::Colon
13167 | Token::NumEq | Token::NumNe | Token::NumLt | Token::NumGt
13169 | Token::NumLe | Token::NumGe | Token::Spaceship
13170 | Token::StrEq | Token::StrNe | Token::StrLt | Token::StrGt
13171 | Token::StrLe | Token::StrGe | Token::StrCmp
13172 | Token::LogAnd | Token::LogOr | Token::LogNot
13174 | Token::LogAndWord | Token::LogOrWord | Token::LogNotWord
13175 | Token::DefinedOr
13176 | Token::Range | Token::RangeExclusive
13178 | Token::Assign | Token::PlusAssign | Token::MinusAssign
13180 | Token::MulAssign | Token::DivAssign | Token::ModAssign
13181 | Token::PowAssign | Token::DotAssign | Token::AndAssign
13182 | Token::OrAssign | Token::XorAssign | Token::DefinedOrAssign
13183 | Token::ShiftLeftAssign | Token::ShiftRightAssign
13184 | Token::BitAndAssign | Token::BitOrAssign
13185 ) {
13186 return Ok(Expr {
13187 kind: ExprKind::ScalarVar("_".into()),
13188 line: self.peek_line(),
13189 });
13190 }
13191 if matches!(self.peek(), Token::LParen) && matches!(self.peek_at(1), Token::RParen) {
13195 let line = self.peek_line();
13196 self.advance(); self.advance(); return Ok(Expr {
13199 kind: ExprKind::ScalarVar("_".into()),
13200 line,
13201 });
13202 }
13203 self.parse_one_arg()
13204 }
13205
13206 fn parse_one_arg_or_argv(&mut self) -> PerlResult<Expr> {
13208 let line = self.prev_line(); if matches!(self.peek(), Token::LParen) {
13210 self.advance();
13211 if matches!(self.peek(), Token::RParen) {
13212 self.advance();
13213 return Ok(Expr {
13214 kind: ExprKind::ArrayVar("_".into()),
13215 line: self.peek_line(),
13216 });
13217 }
13218 let expr = self.parse_expression()?;
13219 self.expect(&Token::RParen)?;
13220 return Ok(expr);
13221 }
13222 if matches!(
13224 self.peek(),
13225 Token::Semicolon
13226 | Token::RBrace
13227 | Token::RParen
13228 | Token::Eof
13229 | Token::Comma
13230 | Token::PipeForward
13231 ) || self.peek_line() > line
13232 {
13233 Ok(Expr {
13234 kind: ExprKind::ArrayVar("_".into()),
13235 line,
13236 })
13237 } else {
13238 self.parse_assign_expr()
13239 }
13240 }
13241
13242 fn parse_builtin_args(&mut self) -> PerlResult<Vec<Expr>> {
13243 if matches!(self.peek(), Token::LParen) {
13244 self.advance();
13245 let args = self.parse_arg_list()?;
13246 self.expect(&Token::RParen)?;
13247 Ok(args)
13248 } else if self.suppress_parenless_call > 0 && matches!(self.peek(), Token::Ident(_)) {
13249 Ok(vec![])
13252 } else {
13253 self.parse_list_until_terminator()
13254 }
13255 }
13256
13257 #[inline]
13261 fn fat_arrow_autoquote(&self, name: &str, line: usize) -> Option<Expr> {
13262 if matches!(self.peek(), Token::FatArrow) {
13263 Some(Expr {
13264 kind: ExprKind::String(name.to_string()),
13265 line,
13266 })
13267 } else {
13268 None
13269 }
13270 }
13271
13272 fn parse_hash_subscript_key(&mut self) -> PerlResult<Expr> {
13277 let line = self.peek_line();
13278 if let Token::Ident(ref k) = self.peek().clone() {
13279 if matches!(self.peek_at(1), Token::RBrace) {
13280 let s = k.clone();
13281 self.advance();
13282 return Ok(Expr {
13283 kind: ExprKind::String(s),
13284 line,
13285 });
13286 }
13287 }
13288 self.parse_expression()
13289 }
13290
13291 #[inline]
13293 fn peek_is_glob_par_progress_kw(&self) -> bool {
13294 matches!(self.peek(), Token::Ident(ref kw) if kw == "progress")
13295 && matches!(self.peek_at(1), Token::FatArrow)
13296 }
13297
13298 fn parse_pattern_list_until_rparen_or_progress(&mut self) -> PerlResult<Vec<Expr>> {
13300 let mut args = Vec::new();
13301 loop {
13302 if matches!(self.peek(), Token::RParen | Token::Eof) {
13303 break;
13304 }
13305 if self.peek_is_glob_par_progress_kw() {
13306 break;
13307 }
13308 args.push(self.parse_assign_expr()?);
13309 match self.peek() {
13310 Token::RParen => break,
13311 Token::Comma => {
13312 self.advance();
13313 if matches!(self.peek(), Token::RParen) {
13314 break;
13315 }
13316 if self.peek_is_glob_par_progress_kw() {
13317 break;
13318 }
13319 }
13320 _ => {
13321 return Err(self.syntax_err(
13322 "expected `,`, `)`, or `progress =>` after argument in `glob_par` / `par_sed`",
13323 self.peek_line(),
13324 ));
13325 }
13326 }
13327 }
13328 Ok(args)
13329 }
13330
13331 fn parse_pattern_list_glob_par_bare(&mut self) -> PerlResult<Vec<Expr>> {
13333 let mut args = Vec::new();
13334 loop {
13335 if matches!(
13336 self.peek(),
13337 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof
13338 ) {
13339 break;
13340 }
13341 if self.peek_is_postfix_stmt_modifier_keyword() {
13342 break;
13343 }
13344 if self.peek_is_glob_par_progress_kw() {
13345 break;
13346 }
13347 args.push(self.parse_assign_expr()?);
13348 if !self.eat(&Token::Comma) {
13349 break;
13350 }
13351 if self.peek_is_glob_par_progress_kw() {
13352 break;
13353 }
13354 }
13355 Ok(args)
13356 }
13357
13358 fn parse_glob_par_or_par_sed_args(&mut self) -> PerlResult<(Vec<Expr>, Option<Box<Expr>>)> {
13360 if matches!(self.peek(), Token::LParen) {
13361 self.advance();
13362 let args = self.parse_pattern_list_until_rparen_or_progress()?;
13363 let progress = if self.peek_is_glob_par_progress_kw() {
13364 self.advance();
13365 self.expect(&Token::FatArrow)?;
13366 Some(Box::new(self.parse_assign_expr()?))
13367 } else {
13368 None
13369 };
13370 self.expect(&Token::RParen)?;
13371 Ok((args, progress))
13372 } else {
13373 let args = self.parse_pattern_list_glob_par_bare()?;
13374 let progress = if self.peek_is_glob_par_progress_kw() {
13376 self.advance();
13377 self.expect(&Token::FatArrow)?;
13378 Some(Box::new(self.parse_assign_expr()?))
13379 } else {
13380 None
13381 };
13382 Ok((args, progress))
13383 }
13384 }
13385
13386 pub(crate) fn parse_arg_list(&mut self) -> PerlResult<Vec<Expr>> {
13387 let mut args = Vec::new();
13388 let saved_no_pf = self.no_pipe_forward_depth;
13392 self.no_pipe_forward_depth = 0;
13393 while !matches!(
13394 self.peek(),
13395 Token::RParen | Token::RBracket | Token::RBrace | Token::Eof
13396 ) {
13397 let arg = match self.parse_assign_expr() {
13398 Ok(e) => e,
13399 Err(err) => {
13400 self.no_pipe_forward_depth = saved_no_pf;
13401 return Err(err);
13402 }
13403 };
13404 args.push(arg);
13405 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
13406 break;
13407 }
13408 }
13409 self.no_pipe_forward_depth = saved_no_pf;
13410 Ok(args)
13411 }
13412
13413 fn parse_method_arg_list_no_paren(&mut self) -> PerlResult<Vec<Expr>> {
13417 let mut args = Vec::new();
13418 let call_line = self.prev_line();
13419 loop {
13420 if args.is_empty() && matches!(self.peek(), Token::LBrace) {
13423 break;
13424 }
13425 if matches!(
13426 self.peek(),
13427 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof | Token::PipeForward
13428 ) {
13429 break;
13430 }
13431 if let Token::Ident(ref kw) = self.peek().clone() {
13432 if matches!(
13433 kw.as_str(),
13434 "if" | "unless" | "while" | "until" | "for" | "foreach"
13435 ) {
13436 break;
13437 }
13438 }
13439 if args.is_empty()
13442 && (self.peek_method_arg_infix_terminator() || matches!(self.peek(), Token::Comma))
13443 {
13444 break;
13445 }
13446 if args.is_empty() && self.peek_line() > call_line {
13449 break;
13450 }
13451 args.push(self.parse_assign_expr()?);
13452 if !self.eat(&Token::Comma) {
13453 break;
13454 }
13455 }
13456 Ok(args)
13457 }
13458
13459 fn peek_method_arg_infix_terminator(&self) -> bool {
13462 matches!(
13463 self.peek(),
13464 Token::Plus
13465 | Token::Minus
13466 | Token::Star
13467 | Token::Slash
13468 | Token::Percent
13469 | Token::Power
13470 | Token::Dot
13471 | Token::X
13472 | Token::NumEq
13473 | Token::NumNe
13474 | Token::NumLt
13475 | Token::NumGt
13476 | Token::NumLe
13477 | Token::NumGe
13478 | Token::Spaceship
13479 | Token::StrEq
13480 | Token::StrNe
13481 | Token::StrLt
13482 | Token::StrGt
13483 | Token::StrLe
13484 | Token::StrGe
13485 | Token::StrCmp
13486 | Token::LogAnd
13487 | Token::LogOr
13488 | Token::LogAndWord
13489 | Token::LogOrWord
13490 | Token::DefinedOr
13491 | Token::BitAnd
13492 | Token::BitOr
13493 | Token::BitXor
13494 | Token::ShiftLeft
13495 | Token::ShiftRight
13496 | Token::Range
13497 | Token::RangeExclusive
13498 | Token::BindMatch
13499 | Token::BindNotMatch
13500 | Token::Arrow
13501 | Token::Question
13503 | Token::Colon
13504 | Token::Assign
13506 | Token::PlusAssign
13507 | Token::MinusAssign
13508 | Token::MulAssign
13509 | Token::DivAssign
13510 | Token::ModAssign
13511 | Token::PowAssign
13512 | Token::DotAssign
13513 | Token::AndAssign
13514 | Token::OrAssign
13515 | Token::XorAssign
13516 | Token::DefinedOrAssign
13517 | Token::ShiftLeftAssign
13518 | Token::ShiftRightAssign
13519 | Token::BitAndAssign
13520 | Token::BitOrAssign
13521 )
13522 }
13523
13524 fn parse_list_until_terminator(&mut self) -> PerlResult<Vec<Expr>> {
13525 let mut args = Vec::new();
13526 let call_line = self.prev_line();
13531 loop {
13532 if matches!(
13533 self.peek(),
13534 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof | Token::PipeForward
13535 ) {
13536 break;
13537 }
13538 if let Token::Ident(ref kw) = self.peek().clone() {
13540 if matches!(
13541 kw.as_str(),
13542 "if" | "unless" | "while" | "until" | "for" | "foreach"
13543 ) {
13544 break;
13545 }
13546 }
13547 if args.is_empty() && self.peek_line() > call_line {
13554 break;
13555 }
13556 args.push(self.parse_assign_expr_stop_at_pipe()?);
13559 if !self.eat(&Token::Comma) {
13560 break;
13561 }
13562 }
13563 Ok(args)
13564 }
13565
13566 fn try_parse_hash_ref(&mut self) -> PerlResult<Vec<(Expr, Expr)>> {
13567 let mut pairs = Vec::new();
13568 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
13569 let line = self.peek_line();
13572 let key = if let Token::Ident(ref name) = self.peek().clone() {
13573 if matches!(self.peek_at(1), Token::FatArrow) {
13574 self.advance();
13575 Expr {
13576 kind: ExprKind::String(name.clone()),
13577 line,
13578 }
13579 } else {
13580 self.parse_assign_expr()?
13581 }
13582 } else {
13583 self.parse_assign_expr()?
13584 };
13585 if matches!(self.peek(), Token::RBrace | Token::Comma)
13589 && matches!(
13590 key.kind,
13591 ExprKind::HashVar(_)
13592 | ExprKind::Deref {
13593 kind: Sigil::Hash,
13594 ..
13595 }
13596 )
13597 {
13598 let sentinel_key = Expr {
13602 kind: ExprKind::String("__HASH_SPREAD__".into()),
13603 line,
13604 };
13605 pairs.push((sentinel_key, key));
13606 self.eat(&Token::Comma);
13607 continue;
13608 }
13609 if self.eat(&Token::FatArrow) || self.eat(&Token::Comma) {
13611 let val = self.parse_assign_expr()?;
13612 pairs.push((key, val));
13613 self.eat(&Token::Comma);
13614 } else {
13615 return Err(self.syntax_err("Expected => or , in hash ref", key.line));
13616 }
13617 }
13618 self.expect(&Token::RBrace)?;
13619 Ok(pairs)
13620 }
13621
13622 fn parse_hashref_pairs_until(&mut self, term: &Token) -> PerlResult<Vec<(Expr, Expr)>> {
13627 let mut pairs = Vec::new();
13628 while !matches!(&self.peek(), t if std::mem::discriminant(*t) == std::mem::discriminant(term))
13629 && !matches!(self.peek(), Token::Eof)
13630 {
13631 let line = self.peek_line();
13632 let key = if let Token::Ident(ref name) = self.peek().clone() {
13633 if matches!(self.peek_at(1), Token::FatArrow) {
13634 self.advance();
13635 Expr {
13636 kind: ExprKind::String(name.clone()),
13637 line,
13638 }
13639 } else {
13640 self.parse_assign_expr()?
13641 }
13642 } else {
13643 self.parse_assign_expr()?
13644 };
13645 if self.eat(&Token::FatArrow) || self.eat(&Token::Comma) {
13646 let val = self.parse_assign_expr()?;
13647 pairs.push((key, val));
13648 self.eat(&Token::Comma);
13649 } else {
13650 return Err(self.syntax_err("Expected => or , in hash ref", key.line));
13651 }
13652 }
13653 Ok(pairs)
13654 }
13655
13656 fn interp_chain_subscripts(
13662 &self,
13663 chars: &[char],
13664 i: &mut usize,
13665 mut base: Expr,
13666 line: usize,
13667 ) -> Expr {
13668 loop {
13669 let (after, requires_subscript) =
13671 if *i + 1 < chars.len() && chars[*i] == '-' && chars[*i + 1] == '>' {
13672 (*i + 2, true)
13673 } else {
13674 (*i, false)
13675 };
13676 if after >= chars.len() {
13677 break;
13678 }
13679 match chars[after] {
13680 '[' => {
13681 *i = after + 1;
13682 let mut idx_str = String::new();
13683 while *i < chars.len() && chars[*i] != ']' {
13684 idx_str.push(chars[*i]);
13685 *i += 1;
13686 }
13687 if *i < chars.len() {
13688 *i += 1;
13689 }
13690 let idx_expr = if let Some(rest) = idx_str.strip_prefix('$') {
13691 Expr {
13692 kind: ExprKind::ScalarVar(rest.to_string()),
13693 line,
13694 }
13695 } else if let Ok(n) = idx_str.parse::<i64>() {
13696 Expr {
13697 kind: ExprKind::Integer(n),
13698 line,
13699 }
13700 } else {
13701 Expr {
13702 kind: ExprKind::String(idx_str),
13703 line,
13704 }
13705 };
13706 base = Expr {
13707 kind: ExprKind::ArrowDeref {
13708 expr: Box::new(base),
13709 index: Box::new(idx_expr),
13710 kind: DerefKind::Array,
13711 },
13712 line,
13713 };
13714 }
13715 '{' => {
13716 *i = after + 1;
13717 let mut key = String::new();
13718 let mut depth = 1usize;
13719 while *i < chars.len() && depth > 0 {
13720 if chars[*i] == '{' {
13721 depth += 1;
13722 } else if chars[*i] == '}' {
13723 depth -= 1;
13724 if depth == 0 {
13725 break;
13726 }
13727 }
13728 key.push(chars[*i]);
13729 *i += 1;
13730 }
13731 if *i < chars.len() {
13732 *i += 1;
13733 }
13734 let key_expr = if let Some(rest) = key.strip_prefix('$') {
13735 Expr {
13736 kind: ExprKind::ScalarVar(rest.to_string()),
13737 line,
13738 }
13739 } else {
13740 Expr {
13741 kind: ExprKind::String(key),
13742 line,
13743 }
13744 };
13745 base = Expr {
13746 kind: ExprKind::ArrowDeref {
13747 expr: Box::new(base),
13748 index: Box::new(key_expr),
13749 kind: DerefKind::Hash,
13750 },
13751 line,
13752 };
13753 }
13754 _ => {
13755 if requires_subscript {
13756 }
13758 break;
13759 }
13760 }
13761 }
13762 base
13763 }
13764
13765 fn parse_interpolated_string(&self, s: &str, line: usize) -> PerlResult<Expr> {
13766 let mut parts = Vec::new();
13768 let mut literal = String::new();
13769 let chars: Vec<char> = s.chars().collect();
13770 let mut i = 0;
13771
13772 'istr: while i < chars.len() {
13773 if chars[i] == LITERAL_DOLLAR_IN_DQUOTE {
13774 literal.push('$');
13775 i += 1;
13776 continue;
13777 }
13778 if chars[i] == '\\' && i + 1 < chars.len() && chars[i + 1] == '$' {
13780 literal.push('\\');
13781 i += 1;
13782 }
13784 if chars[i] == '$' && i + 1 < chars.len() {
13785 if !literal.is_empty() {
13786 parts.push(StringPart::Literal(std::mem::take(&mut literal)));
13787 }
13788 i += 1; while i < chars.len() && chars[i].is_whitespace() {
13791 i += 1;
13792 }
13793 if i >= chars.len() {
13794 return Err(self.syntax_err("Final $ should be \\$ or $name", line));
13795 }
13796 if chars[i] == '#' {
13798 i += 1;
13799 let mut sname = String::from("#");
13800 while i < chars.len()
13801 && (chars[i].is_alphanumeric() || chars[i] == '_' || chars[i] == ':')
13802 {
13803 sname.push(chars[i]);
13804 i += 1;
13805 }
13806 while i + 1 < chars.len() && chars[i] == ':' && chars[i + 1] == ':' {
13807 sname.push_str("::");
13808 i += 2;
13809 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
13810 sname.push(chars[i]);
13811 i += 1;
13812 }
13813 }
13814 parts.push(StringPart::ScalarVar(sname));
13815 continue;
13816 }
13817 if chars[i] == '$' {
13821 let next_c = chars.get(i + 1).copied();
13822 let is_pid = match next_c {
13823 None => true,
13824 Some(c)
13825 if !c.is_ascii_digit() && !matches!(c, 'A'..='Z' | 'a'..='z' | '_') =>
13826 {
13827 true
13828 }
13829 _ => false,
13830 };
13831 if is_pid {
13832 parts.push(StringPart::ScalarVar("$$".to_string()));
13833 i += 1; continue;
13835 }
13836 i += 1; }
13838 if chars[i] == '{' {
13839 i += 1;
13845 let mut inner = String::new();
13846 let mut depth = 1usize;
13847 while i < chars.len() && depth > 0 {
13848 match chars[i] {
13849 '{' => depth += 1,
13850 '}' => {
13851 depth -= 1;
13852 if depth == 0 {
13853 break;
13854 }
13855 }
13856 _ => {}
13857 }
13858 inner.push(chars[i]);
13859 i += 1;
13860 }
13861 if i < chars.len() {
13862 i += 1; }
13864
13865 let trimmed = inner.trim();
13869 let is_expr = trimmed.starts_with('$')
13870 || trimmed.starts_with('\\')
13871 || trimmed.starts_with('@') || trimmed.starts_with('%') || trimmed.contains(['(', '+', '-', '*', '/', '.', '?', '&', '|']);
13874 let mut base: Expr = if is_expr {
13875 match parse_expression_from_str(trimmed, "<interp>") {
13879 Ok(e) => Expr {
13880 kind: ExprKind::Deref {
13881 expr: Box::new(e),
13882 kind: Sigil::Scalar,
13883 },
13884 line,
13885 },
13886 Err(_) => Expr {
13887 kind: ExprKind::ScalarVar(inner.clone()),
13888 line,
13889 },
13890 }
13891 } else {
13892 Expr {
13894 kind: ExprKind::ScalarVar(inner),
13895 line,
13896 }
13897 };
13898
13899 base = self.interp_chain_subscripts(&chars, &mut i, base, line);
13903 parts.push(StringPart::Expr(base));
13904 } else if chars[i] == '^' {
13905 let mut name = String::from("^");
13907 i += 1;
13908 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
13909 name.push(chars[i]);
13910 i += 1;
13911 }
13912 if i < chars.len() && chars[i] == '{' {
13913 i += 1; let mut key = String::new();
13915 let mut depth = 1;
13916 while i < chars.len() && depth > 0 {
13917 if chars[i] == '{' {
13918 depth += 1;
13919 } else if chars[i] == '}' {
13920 depth -= 1;
13921 if depth == 0 {
13922 break;
13923 }
13924 }
13925 key.push(chars[i]);
13926 i += 1;
13927 }
13928 if i < chars.len() {
13929 i += 1;
13930 }
13931 let key_expr = if let Some(rest) = key.strip_prefix('$') {
13932 Expr {
13933 kind: ExprKind::ScalarVar(rest.to_string()),
13934 line,
13935 }
13936 } else {
13937 Expr {
13938 kind: ExprKind::String(key),
13939 line,
13940 }
13941 };
13942 parts.push(StringPart::Expr(Expr {
13943 kind: ExprKind::HashElement {
13944 hash: name,
13945 key: Box::new(key_expr),
13946 },
13947 line,
13948 }));
13949 } else if i < chars.len() && chars[i] == '[' {
13950 i += 1;
13951 let mut idx_str = String::new();
13952 while i < chars.len() && chars[i] != ']' {
13953 idx_str.push(chars[i]);
13954 i += 1;
13955 }
13956 if i < chars.len() {
13957 i += 1;
13958 }
13959 let idx_expr = if let Some(rest) = idx_str.strip_prefix('$') {
13960 Expr {
13961 kind: ExprKind::ScalarVar(rest.to_string()),
13962 line,
13963 }
13964 } else if let Ok(n) = idx_str.parse::<i64>() {
13965 Expr {
13966 kind: ExprKind::Integer(n),
13967 line,
13968 }
13969 } else {
13970 Expr {
13971 kind: ExprKind::String(idx_str),
13972 line,
13973 }
13974 };
13975 parts.push(StringPart::Expr(Expr {
13976 kind: ExprKind::ArrayElement {
13977 array: name,
13978 index: Box::new(idx_expr),
13979 },
13980 line,
13981 }));
13982 } else {
13983 parts.push(StringPart::ScalarVar(name));
13984 }
13985 } else if chars[i].is_alphabetic() || chars[i] == '_' {
13986 let mut name = String::new();
13987 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
13988 name.push(chars[i]);
13989 i += 1;
13990 }
13991 if name == "_" {
13993 while i < chars.len() && chars[i] == '<' {
13994 name.push('<');
13995 i += 1;
13996 }
13997 }
13998 let mut base = if i < chars.len() && chars[i] == '{' {
14003 i += 1; let mut key = String::new();
14006 let mut depth = 1;
14007 while i < chars.len() && depth > 0 {
14008 if chars[i] == '{' {
14009 depth += 1;
14010 } else if chars[i] == '}' {
14011 depth -= 1;
14012 if depth == 0 {
14013 break;
14014 }
14015 }
14016 key.push(chars[i]);
14017 i += 1;
14018 }
14019 if i < chars.len() {
14020 i += 1;
14021 } let key_expr = if let Some(rest) = key.strip_prefix('$') {
14023 Expr {
14024 kind: ExprKind::ScalarVar(rest.to_string()),
14025 line,
14026 }
14027 } else {
14028 Expr {
14029 kind: ExprKind::String(key),
14030 line,
14031 }
14032 };
14033 Expr {
14034 kind: ExprKind::HashElement {
14035 hash: name,
14036 key: Box::new(key_expr),
14037 },
14038 line,
14039 }
14040 } else if i < chars.len() && chars[i] == '[' {
14041 i += 1;
14043 let mut idx_str = String::new();
14044 while i < chars.len() && chars[i] != ']' {
14045 idx_str.push(chars[i]);
14046 i += 1;
14047 }
14048 if i < chars.len() {
14049 i += 1;
14050 }
14051 let idx_expr = if let Some(rest) = idx_str.strip_prefix('$') {
14052 Expr {
14053 kind: ExprKind::ScalarVar(rest.to_string()),
14054 line,
14055 }
14056 } else if let Ok(n) = idx_str.parse::<i64>() {
14057 Expr {
14058 kind: ExprKind::Integer(n),
14059 line,
14060 }
14061 } else {
14062 Expr {
14063 kind: ExprKind::String(idx_str),
14064 line,
14065 }
14066 };
14067 Expr {
14068 kind: ExprKind::ArrayElement {
14069 array: name,
14070 index: Box::new(idx_expr),
14071 },
14072 line,
14073 }
14074 } else {
14075 Expr {
14077 kind: ExprKind::ScalarVar(name),
14078 line,
14079 }
14080 };
14081
14082 base = self.interp_chain_subscripts(&chars, &mut i, base, line);
14086 parts.push(StringPart::Expr(base));
14087 } else if chars[i].is_ascii_digit() {
14088 if chars[i] == '0' {
14090 i += 1;
14091 if i < chars.len() && chars[i].is_ascii_digit() {
14092 return Err(self.syntax_err(
14093 "Numeric variables with more than one digit may not start with '0'",
14094 line,
14095 ));
14096 }
14097 parts.push(StringPart::ScalarVar("0".into()));
14098 } else {
14099 let start = i;
14100 while i < chars.len() && chars[i].is_ascii_digit() {
14101 i += 1;
14102 }
14103 parts.push(StringPart::ScalarVar(chars[start..i].iter().collect()));
14104 }
14105 } else {
14106 let c = chars[i];
14107 let probe = c.to_string();
14108 if Interpreter::is_special_scalar_name_for_get(&probe)
14109 || matches!(c, '\'' | '`')
14110 {
14111 i += 1;
14112 if i < chars.len() && chars[i] == '{' {
14114 i += 1; let mut key = String::new();
14116 let mut depth = 1;
14117 while i < chars.len() && depth > 0 {
14118 if chars[i] == '{' {
14119 depth += 1;
14120 } else if chars[i] == '}' {
14121 depth -= 1;
14122 if depth == 0 {
14123 break;
14124 }
14125 }
14126 key.push(chars[i]);
14127 i += 1;
14128 }
14129 if i < chars.len() {
14130 i += 1;
14131 } let key_expr = if let Some(rest) = key.strip_prefix('$') {
14133 Expr {
14134 kind: ExprKind::ScalarVar(rest.to_string()),
14135 line,
14136 }
14137 } else {
14138 Expr {
14139 kind: ExprKind::String(key),
14140 line,
14141 }
14142 };
14143 let mut base = Expr {
14144 kind: ExprKind::HashElement {
14145 hash: probe,
14146 key: Box::new(key_expr),
14147 },
14148 line,
14149 };
14150 base = self.interp_chain_subscripts(&chars, &mut i, base, line);
14151 parts.push(StringPart::Expr(base));
14152 } else {
14153 let mut base = Expr {
14155 kind: ExprKind::ScalarVar(probe),
14156 line,
14157 };
14158 base = self.interp_chain_subscripts(&chars, &mut i, base, line);
14159 if matches!(base.kind, ExprKind::ScalarVar(_)) {
14160 if let ExprKind::ScalarVar(name) = base.kind {
14162 parts.push(StringPart::ScalarVar(name));
14163 }
14164 } else {
14165 parts.push(StringPart::Expr(base));
14166 }
14167 }
14168 } else {
14169 literal.push('$');
14170 literal.push(c);
14171 i += 1;
14172 }
14173 }
14174 } else if chars[i] == '@' && i + 1 < chars.len() {
14175 let next = chars[i + 1];
14176 if next == '$' {
14178 if !literal.is_empty() {
14179 parts.push(StringPart::Literal(std::mem::take(&mut literal)));
14180 }
14181 i += 1; debug_assert_eq!(chars[i], '$');
14183 i += 1; while i < chars.len() && chars[i].is_whitespace() {
14185 i += 1;
14186 }
14187 if i >= chars.len() {
14188 return Err(self.syntax_err(
14189 "Expected variable or block after `@$` in double-quoted string",
14190 line,
14191 ));
14192 }
14193 let inner_expr = if chars[i] == '{' {
14194 i += 1;
14195 let start = i;
14196 let mut depth = 1usize;
14197 while i < chars.len() && depth > 0 {
14198 match chars[i] {
14199 '{' => depth += 1,
14200 '}' => {
14201 depth -= 1;
14202 if depth == 0 {
14203 break;
14204 }
14205 }
14206 _ => {}
14207 }
14208 i += 1;
14209 }
14210 if depth != 0 {
14211 return Err(self.syntax_err(
14212 "Unterminated `${ ... }` after `@` in double-quoted string",
14213 line,
14214 ));
14215 }
14216 let inner: String = chars[start..i].iter().collect();
14217 i += 1; parse_expression_from_str(inner.trim(), "-e")?
14219 } else {
14220 let mut name = String::new();
14221 if chars[i] == '^' {
14222 name.push('^');
14223 i += 1;
14224 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_')
14225 {
14226 name.push(chars[i]);
14227 i += 1;
14228 }
14229 } else {
14230 while i < chars.len()
14231 && (chars[i].is_alphanumeric()
14232 || chars[i] == '_'
14233 || chars[i] == ':')
14234 {
14235 name.push(chars[i]);
14236 i += 1;
14237 }
14238 while i + 1 < chars.len() && chars[i] == ':' && chars[i + 1] == ':' {
14239 name.push_str("::");
14240 i += 2;
14241 while i < chars.len()
14242 && (chars[i].is_alphanumeric() || chars[i] == '_')
14243 {
14244 name.push(chars[i]);
14245 i += 1;
14246 }
14247 }
14248 }
14249 if name.is_empty() {
14250 return Err(self.syntax_err(
14251 "Expected identifier after `@$` in double-quoted string",
14252 line,
14253 ));
14254 }
14255 Expr {
14256 kind: ExprKind::ScalarVar(name),
14257 line,
14258 }
14259 };
14260 parts.push(StringPart::Expr(Expr {
14261 kind: ExprKind::Deref {
14262 expr: Box::new(inner_expr),
14263 kind: Sigil::Array,
14264 },
14265 line,
14266 }));
14267 continue 'istr;
14268 }
14269 if next == '{' {
14270 if !literal.is_empty() {
14271 parts.push(StringPart::Literal(std::mem::take(&mut literal)));
14272 }
14273 i += 2; let start = i;
14275 let mut depth = 1usize;
14276 while i < chars.len() && depth > 0 {
14277 match chars[i] {
14278 '{' => depth += 1,
14279 '}' => {
14280 depth -= 1;
14281 if depth == 0 {
14282 break;
14283 }
14284 }
14285 _ => {}
14286 }
14287 i += 1;
14288 }
14289 if depth != 0 {
14290 return Err(
14291 self.syntax_err("Unterminated @{ ... } in double-quoted string", line)
14292 );
14293 }
14294 let inner: String = chars[start..i].iter().collect();
14295 i += 1; let inner_expr = parse_expression_from_str(inner.trim(), "-e")?;
14297 parts.push(StringPart::Expr(Expr {
14298 kind: ExprKind::Deref {
14299 expr: Box::new(inner_expr),
14300 kind: Sigil::Array,
14301 },
14302 line,
14303 }));
14304 continue 'istr;
14305 }
14306 if !(next.is_alphabetic() || next == '_' || next == '+' || next == '-') {
14307 literal.push(chars[i]);
14308 i += 1;
14309 } else {
14310 if !literal.is_empty() {
14311 parts.push(StringPart::Literal(std::mem::take(&mut literal)));
14312 }
14313 i += 1;
14314 let mut name = String::new();
14315 if i < chars.len() && (chars[i] == '+' || chars[i] == '-') {
14316 name.push(chars[i]);
14317 i += 1;
14318 } else {
14319 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
14320 name.push(chars[i]);
14321 i += 1;
14322 }
14323 while i + 1 < chars.len() && chars[i] == ':' && chars[i + 1] == ':' {
14324 name.push_str("::");
14325 i += 2;
14326 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_')
14327 {
14328 name.push(chars[i]);
14329 i += 1;
14330 }
14331 }
14332 }
14333 if i < chars.len() && chars[i] == '[' {
14334 i += 1;
14335 let start_inner = i;
14336 let mut depth = 1usize;
14337 while i < chars.len() && depth > 0 {
14338 match chars[i] {
14339 '[' => depth += 1,
14340 ']' => depth -= 1,
14341 _ => {}
14342 }
14343 if depth == 0 {
14344 let inner: String = chars[start_inner..i].iter().collect();
14345 i += 1; let indices = parse_slice_indices_from_str(inner.trim(), "-e")?;
14347 parts.push(StringPart::Expr(Expr {
14348 kind: ExprKind::ArraySlice {
14349 array: name.clone(),
14350 indices,
14351 },
14352 line,
14353 }));
14354 continue 'istr;
14355 }
14356 i += 1;
14357 }
14358 return Err(self.syntax_err(
14359 "Unterminated [ in array slice inside quoted string",
14360 line,
14361 ));
14362 }
14363 parts.push(StringPart::ArrayVar(name));
14364 }
14365 } else if chars[i] == '#'
14366 && i + 1 < chars.len()
14367 && chars[i + 1] == '{'
14368 && !crate::compat_mode()
14369 {
14370 if !literal.is_empty() {
14372 parts.push(StringPart::Literal(std::mem::take(&mut literal)));
14373 }
14374 i += 2; let mut inner = String::new();
14376 let mut depth = 1usize;
14377 while i < chars.len() && depth > 0 {
14378 match chars[i] {
14379 '{' => depth += 1,
14380 '}' => {
14381 depth -= 1;
14382 if depth == 0 {
14383 break;
14384 }
14385 }
14386 _ => {}
14387 }
14388 inner.push(chars[i]);
14389 i += 1;
14390 }
14391 if i < chars.len() {
14392 i += 1; }
14394 let expr = parse_block_from_str(inner.trim(), "-e", line)?;
14395 parts.push(StringPart::Expr(expr));
14396 } else {
14397 literal.push(chars[i]);
14398 i += 1;
14399 }
14400 }
14401 if !literal.is_empty() {
14402 parts.push(StringPart::Literal(literal));
14403 }
14404
14405 if parts.len() == 1 {
14406 if let StringPart::Literal(s) = &parts[0] {
14407 return Ok(Expr {
14408 kind: ExprKind::String(s.clone()),
14409 line,
14410 });
14411 }
14412 }
14413 if parts.is_empty() {
14414 return Ok(Expr {
14415 kind: ExprKind::String(String::new()),
14416 line,
14417 });
14418 }
14419
14420 Ok(Expr {
14421 kind: ExprKind::InterpolatedString(parts),
14422 line,
14423 })
14424 }
14425
14426 fn expr_to_overload_key(&self, e: &Expr) -> PerlResult<String> {
14427 match &e.kind {
14428 ExprKind::String(s) => Ok(s.clone()),
14429 _ => Err(self.syntax_err(
14430 "overload key must be a string literal (e.g. '\"\"' or '+')",
14431 e.line,
14432 )),
14433 }
14434 }
14435
14436 fn expr_to_overload_sub(&self, e: &Expr) -> PerlResult<String> {
14437 match &e.kind {
14438 ExprKind::String(s) => Ok(s.clone()),
14439 ExprKind::Integer(n) => Ok(n.to_string()),
14440 ExprKind::SubroutineRef(s) | ExprKind::SubroutineCodeRef(s) => Ok(s.clone()),
14441 _ => Err(self.syntax_err(
14442 "overload handler must be a string literal, number (e.g. fallback => 1), or \\&subname (method in current package)",
14443 e.line,
14444 )),
14445 }
14446 }
14447}
14448
14449fn merge_expr_list(parts: Vec<Expr>) -> Expr {
14450 if parts.len() == 1 {
14451 parts.into_iter().next().unwrap()
14452 } else {
14453 let line = parts.first().map(|e| e.line).unwrap_or(0);
14454 Expr {
14455 kind: ExprKind::List(parts),
14456 line,
14457 }
14458 }
14459}
14460
14461pub fn parse_expression_from_str(s: &str, file: &str) -> PerlResult<Expr> {
14463 let mut lexer = Lexer::new_with_file(s, file);
14464 let tokens = lexer.tokenize()?;
14465 let mut parser = Parser::new_with_file(tokens, file);
14466 let e = parser.parse_expression()?;
14467 if !parser.at_eof() {
14468 return Err(parser.syntax_err(
14469 "Extra tokens in embedded string expression",
14470 parser.peek_line(),
14471 ));
14472 }
14473 Ok(e)
14474}
14475
14476pub fn parse_block_from_str(s: &str, file: &str, line: usize) -> PerlResult<Expr> {
14478 let mut lexer = Lexer::new_with_file(s, file);
14479 let tokens = lexer.tokenize()?;
14480 let mut parser = Parser::new_with_file(tokens, file);
14481 let stmts = parser.parse_statements()?;
14482 let inner_line = stmts.first().map(|st| st.line).unwrap_or(line);
14483 let inner = Expr {
14484 kind: ExprKind::CodeRef {
14485 params: vec![],
14486 body: stmts,
14487 },
14488 line: inner_line,
14489 };
14490 Ok(Expr {
14491 kind: ExprKind::Do(Box::new(inner)),
14492 line,
14493 })
14494}
14495
14496pub fn parse_slice_indices_from_str(s: &str, file: &str) -> PerlResult<Vec<Expr>> {
14499 let mut lexer = Lexer::new_with_file(s, file);
14500 let tokens = lexer.tokenize()?;
14501 let mut parser = Parser::new_with_file(tokens, file);
14502 parser.parse_arg_list()
14503}
14504
14505pub fn parse_format_value_line(line: &str) -> PerlResult<Vec<Expr>> {
14506 let trimmed = line.trim();
14507 if trimmed.is_empty() {
14508 return Ok(vec![]);
14509 }
14510 let mut lexer = Lexer::new(trimmed);
14511 let tokens = lexer.tokenize()?;
14512 let mut parser = Parser::new(tokens);
14513 let mut exprs = Vec::new();
14514 loop {
14515 if parser.at_eof() {
14516 break;
14517 }
14518 exprs.push(parser.parse_assign_expr()?);
14520 if parser.eat(&Token::Comma) {
14521 continue;
14522 }
14523 if !parser.at_eof() {
14524 return Err(parser.syntax_err("Extra tokens in format value line", parser.peek_line()));
14525 }
14526 break;
14527 }
14528 Ok(exprs)
14529}
14530
14531#[cfg(test)]
14532mod tests {
14533 use super::*;
14534
14535 fn parse_ok(code: &str) -> Program {
14536 let mut lexer = Lexer::new(code);
14537 let tokens = lexer.tokenize().expect("tokenize");
14538 let mut parser = Parser::new(tokens);
14539 parser.parse_program().expect("parse")
14540 }
14541
14542 fn parse_err(code: &str) -> String {
14543 let mut lexer = Lexer::new(code);
14544 let tokens = match lexer.tokenize() {
14545 Ok(t) => t,
14546 Err(e) => return e.message,
14547 };
14548 let mut parser = Parser::new(tokens);
14549 parser.parse_program().unwrap_err().message
14550 }
14551
14552 #[test]
14553 fn parse_empty_program() {
14554 let p = parse_ok("");
14555 assert!(p.statements.is_empty());
14556 }
14557
14558 #[test]
14559 fn parse_semicolons_only() {
14560 let p = parse_ok(";;");
14561 assert!(p.statements.len() <= 3);
14562 }
14563
14564 #[test]
14565 fn parse_simple_scalar_assignment() {
14566 let p = parse_ok("$x = 1");
14567 assert_eq!(p.statements.len(), 1);
14568 }
14569
14570 #[test]
14571 fn parse_simple_array_assignment() {
14572 let p = parse_ok("@arr = (1, 2, 3)");
14573 assert_eq!(p.statements.len(), 1);
14574 }
14575
14576 #[test]
14577 fn parse_simple_hash_assignment() {
14578 let p = parse_ok("%h = (a => 1, b => 2)");
14579 assert_eq!(p.statements.len(), 1);
14580 }
14581
14582 #[test]
14583 fn parse_subroutine_decl() {
14584 let p = parse_ok("fn foo { 1 }");
14585 assert_eq!(p.statements.len(), 1);
14586 match &p.statements[0].kind {
14587 StmtKind::SubDecl { name, .. } => assert_eq!(name, "foo"),
14588 _ => panic!("expected SubDecl"),
14589 }
14590 }
14591
14592 #[test]
14593 fn parse_subroutine_with_prototype() {
14594 let p = parse_ok("fn foo ($$) { 1 }");
14595 assert_eq!(p.statements.len(), 1);
14596 match &p.statements[0].kind {
14597 StmtKind::SubDecl { prototype, .. } => {
14598 assert!(prototype.is_some());
14599 }
14600 _ => panic!("expected SubDecl"),
14601 }
14602 }
14603
14604 #[test]
14605 fn parse_anonymous_fn() {
14606 let p = parse_ok("my $f = fn { 1 }");
14607 assert_eq!(p.statements.len(), 1);
14608 }
14609
14610 #[test]
14611 fn parse_if_statement() {
14612 let p = parse_ok("if (1) { 2 }");
14613 assert_eq!(p.statements.len(), 1);
14614 matches!(&p.statements[0].kind, StmtKind::If { .. });
14615 }
14616
14617 #[test]
14618 fn parse_if_elsif_else() {
14619 let p = parse_ok("if (0) { 1 } elsif (1) { 2 } else { 3 }");
14620 assert_eq!(p.statements.len(), 1);
14621 }
14622
14623 #[test]
14624 fn parse_unless_statement() {
14625 let p = parse_ok("unless (0) { 1 }");
14626 assert_eq!(p.statements.len(), 1);
14627 }
14628
14629 #[test]
14630 fn parse_while_loop() {
14631 let p = parse_ok("while ($x) { $x-- }");
14632 assert_eq!(p.statements.len(), 1);
14633 }
14634
14635 #[test]
14636 fn parse_until_loop() {
14637 let p = parse_ok("until ($x) { $x++ }");
14638 assert_eq!(p.statements.len(), 1);
14639 }
14640
14641 #[test]
14642 fn parse_for_c_style() {
14643 let p = parse_ok("for (my $i=0; $i<10; $i++) { 1 }");
14644 assert_eq!(p.statements.len(), 1);
14645 }
14646
14647 #[test]
14648 fn parse_foreach_loop() {
14649 let p = parse_ok("foreach my $x (@arr) { 1 }");
14650 assert_eq!(p.statements.len(), 1);
14651 }
14652
14653 #[test]
14654 fn parse_loop_with_label() {
14655 let p = parse_ok("OUTER: for my $i (1..10) { last OUTER }");
14656 assert_eq!(p.statements.len(), 1);
14657 assert_eq!(p.statements[0].label.as_deref(), Some("OUTER"));
14658 }
14659
14660 #[test]
14661 fn parse_begin_block() {
14662 let p = parse_ok("BEGIN { 1 }");
14663 assert_eq!(p.statements.len(), 1);
14664 matches!(&p.statements[0].kind, StmtKind::Begin(_));
14665 }
14666
14667 #[test]
14668 fn parse_end_block() {
14669 let p = parse_ok("END { 1 }");
14670 assert_eq!(p.statements.len(), 1);
14671 matches!(&p.statements[0].kind, StmtKind::End(_));
14672 }
14673
14674 #[test]
14675 fn parse_package_statement() {
14676 let p = parse_ok("package Foo::Bar");
14677 assert_eq!(p.statements.len(), 1);
14678 match &p.statements[0].kind {
14679 StmtKind::Package { name } => assert_eq!(name, "Foo::Bar"),
14680 _ => panic!("expected Package"),
14681 }
14682 }
14683
14684 #[test]
14685 fn parse_use_statement() {
14686 let p = parse_ok("use strict");
14687 assert_eq!(p.statements.len(), 1);
14688 }
14689
14690 #[test]
14691 fn parse_no_statement() {
14692 let p = parse_ok("no warnings");
14693 assert_eq!(p.statements.len(), 1);
14694 }
14695
14696 #[test]
14697 fn parse_require_bareword() {
14698 let p = parse_ok("require Foo::Bar");
14699 assert_eq!(p.statements.len(), 1);
14700 }
14701
14702 #[test]
14703 fn parse_require_string() {
14704 let p = parse_ok(r#"require "foo.pl""#);
14705 assert_eq!(p.statements.len(), 1);
14706 }
14707
14708 #[test]
14709 fn parse_eval_block() {
14710 let p = parse_ok("eval { 1 }");
14711 assert_eq!(p.statements.len(), 1);
14712 }
14713
14714 #[test]
14715 fn parse_eval_string() {
14716 let p = parse_ok(r#"eval "1 + 2""#);
14717 assert_eq!(p.statements.len(), 1);
14718 }
14719
14720 #[test]
14721 fn parse_qw_word_list() {
14722 let p = parse_ok("my @a = qw(foo bar baz)");
14723 assert_eq!(p.statements.len(), 1);
14724 }
14725
14726 #[test]
14727 fn parse_q_string() {
14728 let p = parse_ok("my $s = q{hello}");
14729 assert_eq!(p.statements.len(), 1);
14730 }
14731
14732 #[test]
14733 fn parse_qq_string() {
14734 let p = parse_ok(r#"my $s = qq(hello $x)"#);
14735 assert_eq!(p.statements.len(), 1);
14736 }
14737
14738 #[test]
14739 fn parse_regex_match() {
14740 let p = parse_ok(r#"$x =~ /foo/"#);
14741 assert_eq!(p.statements.len(), 1);
14742 }
14743
14744 #[test]
14745 fn parse_regex_substitution() {
14746 let p = parse_ok(r#"$x =~ s/foo/bar/g"#);
14747 assert_eq!(p.statements.len(), 1);
14748 }
14749
14750 #[test]
14751 fn parse_transliterate() {
14752 let p = parse_ok(r#"$x =~ tr/a-z/A-Z/"#);
14753 assert_eq!(p.statements.len(), 1);
14754 }
14755
14756 #[test]
14757 fn parse_ternary_operator() {
14758 let p = parse_ok("my $x = $a ? 1 : 2");
14759 assert_eq!(p.statements.len(), 1);
14760 }
14761
14762 #[test]
14763 fn parse_arrow_method_call() {
14764 let p = parse_ok("$obj->method()");
14765 assert_eq!(p.statements.len(), 1);
14766 }
14767
14768 #[test]
14769 fn parse_arrow_deref_hash() {
14770 let p = parse_ok("$r->{key}");
14771 assert_eq!(p.statements.len(), 1);
14772 }
14773
14774 #[test]
14775 fn parse_arrow_deref_array() {
14776 let p = parse_ok("$r->[0]");
14777 assert_eq!(p.statements.len(), 1);
14778 }
14779
14780 #[test]
14781 fn parse_chained_arrow_deref() {
14782 let p = parse_ok("$r->{a}[0]{b}");
14783 assert_eq!(p.statements.len(), 1);
14784 }
14785
14786 #[test]
14787 fn parse_my_multiple_vars() {
14788 let p = parse_ok("my ($a, $b, $c) = (1, 2, 3)");
14789 assert_eq!(p.statements.len(), 1);
14790 }
14791
14792 #[test]
14793 fn parse_our_scalar() {
14794 let p = parse_ok("our $VERSION = '1.0'");
14795 assert_eq!(p.statements.len(), 1);
14796 }
14797
14798 #[test]
14799 fn parse_local_scalar() {
14800 let p = parse_ok("local $/ = undef");
14801 assert_eq!(p.statements.len(), 1);
14802 }
14803
14804 #[test]
14805 fn parse_state_variable() {
14806 let p = parse_ok("fn my_counter { state $n = 0; $n++ }");
14807 assert_eq!(p.statements.len(), 1);
14808 }
14809
14810 #[test]
14811 fn parse_postfix_if() {
14812 let p = parse_ok("print 1 if $x");
14813 assert_eq!(p.statements.len(), 1);
14814 }
14815
14816 #[test]
14817 fn parse_postfix_unless() {
14818 let p = parse_ok("die 'error' unless $ok");
14819 assert_eq!(p.statements.len(), 1);
14820 }
14821
14822 #[test]
14823 fn parse_postfix_while() {
14824 let p = parse_ok("$x++ while $x < 10");
14825 assert_eq!(p.statements.len(), 1);
14826 }
14827
14828 #[test]
14829 fn parse_postfix_for() {
14830 let p = parse_ok("print for @arr");
14831 assert_eq!(p.statements.len(), 1);
14832 }
14833
14834 #[test]
14835 fn parse_last_next_redo() {
14836 let p = parse_ok("for (@a) { next if $_ < 0; last if $_ > 10 }");
14837 assert_eq!(p.statements.len(), 1);
14838 }
14839
14840 #[test]
14841 fn parse_return_statement() {
14842 let p = parse_ok("fn foo { return 42 }");
14843 assert_eq!(p.statements.len(), 1);
14844 }
14845
14846 #[test]
14847 fn parse_wantarray() {
14848 let p = parse_ok("fn foo { wantarray ? @a : $a }");
14849 assert_eq!(p.statements.len(), 1);
14850 }
14851
14852 #[test]
14853 fn parse_caller_builtin() {
14854 let p = parse_ok("my @c = caller");
14855 assert_eq!(p.statements.len(), 1);
14856 }
14857
14858 #[test]
14859 fn parse_ref_to_array() {
14860 let p = parse_ok("my $r = \\@arr");
14861 assert_eq!(p.statements.len(), 1);
14862 }
14863
14864 #[test]
14865 fn parse_ref_to_hash() {
14866 let p = parse_ok("my $r = \\%hash");
14867 assert_eq!(p.statements.len(), 1);
14868 }
14869
14870 #[test]
14871 fn parse_ref_to_scalar() {
14872 let p = parse_ok("my $r = \\$x");
14873 assert_eq!(p.statements.len(), 1);
14874 }
14875
14876 #[test]
14877 fn parse_deref_scalar() {
14878 let p = parse_ok("my $v = $$r");
14879 assert_eq!(p.statements.len(), 1);
14880 }
14881
14882 #[test]
14883 fn parse_deref_array() {
14884 let p = parse_ok("my @a = @$r");
14885 assert_eq!(p.statements.len(), 1);
14886 }
14887
14888 #[test]
14889 fn parse_deref_hash() {
14890 let p = parse_ok("my %h = %$r");
14891 assert_eq!(p.statements.len(), 1);
14892 }
14893
14894 #[test]
14895 fn parse_blessed_ref() {
14896 let p = parse_ok("bless $r, 'Foo'");
14897 assert_eq!(p.statements.len(), 1);
14898 }
14899
14900 #[test]
14901 fn parse_heredoc_basic() {
14902 let p = parse_ok("my $s = <<END;\nfoo\nEND");
14903 assert_eq!(p.statements.len(), 1);
14904 }
14905
14906 #[test]
14907 fn parse_heredoc_quoted() {
14908 let p = parse_ok("my $s = <<'END';\nfoo\nEND");
14909 assert_eq!(p.statements.len(), 1);
14910 }
14911
14912 #[test]
14913 fn parse_do_block() {
14914 let p = parse_ok("my $x = do { 1 + 2 }");
14915 assert_eq!(p.statements.len(), 1);
14916 }
14917
14918 #[test]
14919 fn parse_do_file() {
14920 let p = parse_ok(r#"do "foo.pl""#);
14921 assert_eq!(p.statements.len(), 1);
14922 }
14923
14924 #[test]
14925 fn parse_map_expression() {
14926 let p = parse_ok("my @b = map { $_ * 2 } @a");
14927 assert_eq!(p.statements.len(), 1);
14928 }
14929
14930 #[test]
14931 fn parse_grep_expression() {
14932 let p = parse_ok("my @b = grep { $_ > 0 } @a");
14933 assert_eq!(p.statements.len(), 1);
14934 }
14935
14936 #[test]
14937 fn parse_sort_expression() {
14938 let p = parse_ok("my @b = sort { $a <=> $b } @a");
14939 assert_eq!(p.statements.len(), 1);
14940 }
14941
14942 #[test]
14943 fn parse_pipe_forward() {
14944 let p = parse_ok("@a |> map { $_ * 2 }");
14945 assert_eq!(p.statements.len(), 1);
14946 }
14947
14948 #[test]
14949 fn parse_expression_from_str_simple() {
14950 let e = parse_expression_from_str("$x + 1", "-e").unwrap();
14951 assert!(matches!(e.kind, ExprKind::BinOp { .. }));
14952 }
14953
14954 #[test]
14955 fn parse_expression_from_str_extra_tokens_error() {
14956 let err = parse_expression_from_str("$x; $y", "-e").unwrap_err();
14957 assert!(err.message.contains("Extra tokens"));
14958 }
14959
14960 #[test]
14961 fn parse_slice_indices_from_str_basic() {
14962 let indices = parse_slice_indices_from_str("0, 1, 2", "-e").unwrap();
14963 assert_eq!(indices.len(), 3);
14964 }
14965
14966 #[test]
14967 fn parse_format_value_line_empty() {
14968 let exprs = parse_format_value_line("").unwrap();
14969 assert!(exprs.is_empty());
14970 }
14971
14972 #[test]
14973 fn parse_format_value_line_single() {
14974 let exprs = parse_format_value_line("$x").unwrap();
14975 assert_eq!(exprs.len(), 1);
14976 }
14977
14978 #[test]
14979 fn parse_format_value_line_multiple() {
14980 let exprs = parse_format_value_line("$a, $b, $c").unwrap();
14981 assert_eq!(exprs.len(), 3);
14982 }
14983
14984 #[test]
14985 fn parse_unclosed_brace_error() {
14986 let err = parse_err("fn foo {");
14987 assert!(!err.is_empty());
14988 }
14989
14990 #[test]
14991 fn parse_unclosed_paren_error() {
14992 let err = parse_err("print (1, 2");
14993 assert!(!err.is_empty());
14994 }
14995
14996 #[test]
14997 fn parse_invalid_statement_error() {
14998 let err = parse_err("???");
14999 assert!(!err.is_empty());
15000 }
15001
15002 #[test]
15003 fn merge_expr_list_single() {
15004 let e = Expr {
15005 kind: ExprKind::Integer(1),
15006 line: 1,
15007 };
15008 let merged = merge_expr_list(vec![e.clone()]);
15009 matches!(merged.kind, ExprKind::Integer(1));
15010 }
15011
15012 #[test]
15013 fn merge_expr_list_multiple() {
15014 let e1 = Expr {
15015 kind: ExprKind::Integer(1),
15016 line: 1,
15017 };
15018 let e2 = Expr {
15019 kind: ExprKind::Integer(2),
15020 line: 1,
15021 };
15022 let merged = merge_expr_list(vec![e1, e2]);
15023 matches!(merged.kind, ExprKind::List(_));
15024 }
15025}