1use crate::ast::*;
2use crate::error::{ErrorKind, PerlError, PerlResult};
3use crate::vm_helper::VMHelper;
4use crate::lexer::{Lexer, LITERAL_AT_IN_DQUOTE, 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 suppress_tilde_range: u32,
132 thread_last_mode: bool,
135 pub parsing_module: bool,
138 list_construct_close_pos: Option<usize>,
146 pending_synthetic_subs: Vec<Statement>,
151 next_overload_anon_id: u32,
153}
154
155impl Parser {
156 pub fn new(tokens: Vec<(Token, usize)>) -> Self {
157 Self::new_with_file(tokens, "-e")
158 }
159
160 pub fn new_with_file(tokens: Vec<(Token, usize)>, file: impl Into<String>) -> Self {
161 Self {
162 tokens,
163 pos: 0,
164 next_rate_limit_slot: 0,
165 suppress_indirect_paren_call: 0,
166 pipe_rhs_depth: 0,
167 no_pipe_forward_depth: 0,
168 suppress_scalar_hash_brace: 0,
169 next_desugar_tmp: 0,
170 error_file: file.into(),
171 declared_subs: std::collections::HashSet::new(),
172 suppress_parenless_call: 0,
173 suppress_slash_as_div: 0,
174 suppress_m_regex: 0,
175 suppress_colon_range: 0,
176 suppress_tilde_range: 0,
177 thread_last_mode: false,
178 pending_synthetic_subs: Vec::new(),
179 next_overload_anon_id: 0,
180 parsing_module: false,
181 list_construct_close_pos: None,
182 }
183 }
184
185 fn alloc_desugar_tmp(&mut self) -> u32 {
186 let n = self.next_desugar_tmp;
187 self.next_desugar_tmp = self.next_desugar_tmp.saturating_add(1);
188 n
189 }
190
191 #[inline]
195 fn in_pipe_rhs(&self) -> bool {
196 self.pipe_rhs_depth > 0
197 }
198
199 fn pipe_supplies_slurped_list_operand(&self) -> bool {
202 self.in_pipe_rhs()
203 && (matches!(
204 self.peek(),
205 Token::Semicolon
206 | Token::RBrace
207 | Token::RParen
208 | Token::Eof
209 | Token::Comma
210 | Token::PipeForward
211 ) || self.peek_line() > self.prev_line())
212 }
213
214 #[inline]
219 fn pipe_placeholder_list(&self, line: usize) -> Expr {
220 Expr {
221 kind: ExprKind::List(vec![]),
222 line,
223 }
224 }
225
226 fn is_block_then_list_pipe_builtin(name: &str) -> bool {
231 matches!(
232 name,
233 "pfirst"
234 | "pany"
235 | "any"
236 | "all"
237 | "none"
238 | "first"
239 | "take_while"
240 | "drop_while"
241 | "skip_while"
242 | "reject"
243 | "tap"
244 | "peek"
245 | "group_by"
246 | "chunk_by"
247 | "partition"
248 | "min_by"
249 | "max_by"
250 | "zip_with"
251 | "count_by"
252 )
253 }
254
255 fn lift_bareword_to_topic_call(expr: Expr) -> Expr {
266 let line = expr.line;
267 let topic = || Expr {
268 kind: ExprKind::ScalarVar("_".into()),
269 line,
270 };
271 match expr.kind {
272 ExprKind::Bareword(ref name) => Expr {
273 kind: ExprKind::FuncCall {
274 name: name.clone(),
275 args: vec![topic()],
276 },
277 line,
278 },
279 ExprKind::Unlink(ref args) if args.is_empty() => Expr {
281 kind: ExprKind::Unlink(vec![topic()]),
282 line,
283 },
284 ExprKind::Chmod(ref args) if args.is_empty() => Expr {
285 kind: ExprKind::Chmod(vec![topic()]),
286 line,
287 },
288 ExprKind::Stat(_) => expr,
290 ExprKind::Lstat(_) => expr,
291 ExprKind::Readlink(_) => expr,
292 ExprKind::Rev(ref inner) => {
294 if matches!(inner.kind, ExprKind::List(ref v) if v.is_empty()) {
295 Expr {
296 kind: ExprKind::Rev(Box::new(topic())),
297 line,
298 }
299 } else {
300 expr
301 }
302 }
303 _ => expr,
304 }
305 }
306
307 fn parse_assign_expr_stop_at_pipe(&mut self) -> PerlResult<Expr> {
315 self.no_pipe_forward_depth = self.no_pipe_forward_depth.saturating_add(1);
316 let r = self.parse_assign_expr();
317 self.no_pipe_forward_depth = self.no_pipe_forward_depth.saturating_sub(1);
318 r
319 }
320
321 fn syntax_err(&self, message: impl Into<String>, line: usize) -> PerlError {
322 PerlError::new(ErrorKind::Syntax, message, line, self.error_file.clone())
323 }
324
325 fn alloc_rate_limit_slot(&mut self) -> u32 {
326 let s = self.next_rate_limit_slot;
327 self.next_rate_limit_slot = self.next_rate_limit_slot.saturating_add(1);
328 s
329 }
330
331 fn peek(&self) -> &Token {
332 self.tokens
333 .get(self.pos)
334 .map(|(t, _)| t)
335 .unwrap_or(&Token::Eof)
336 }
337
338 fn peek_line(&self) -> usize {
339 self.tokens.get(self.pos).map(|(_, l)| *l).unwrap_or(0)
340 }
341
342 fn peek_at(&self, offset: usize) -> &Token {
343 self.tokens
344 .get(self.pos + offset)
345 .map(|(t, _)| t)
346 .unwrap_or(&Token::Eof)
347 }
348
349 fn advance(&mut self) -> (Token, usize) {
350 let tok = self
351 .tokens
352 .get(self.pos)
353 .cloned()
354 .unwrap_or((Token::Eof, 0));
355 self.pos += 1;
356 tok
357 }
358
359 fn prev_line(&self) -> usize {
361 if self.pos > 0 {
362 self.tokens.get(self.pos - 1).map(|(_, l)| *l).unwrap_or(0)
363 } else {
364 0
365 }
366 }
367
368 fn looks_like_hashref(&self) -> bool {
377 debug_assert!(matches!(self.peek(), Token::LBrace));
378 let tok1 = self.peek_at(1);
379 let tok2 = self.peek_at(2);
380 match tok1 {
381 Token::RBrace => true,
382 Token::Ident(_)
383 | Token::SingleString(_)
384 | Token::DoubleString(_)
385 | Token::ScalarVar(_)
386 | Token::Integer(_) => matches!(tok2, Token::FatArrow),
387 Token::HashVar(_) => matches!(tok2, Token::RBrace | Token::Comma),
388 _ => false,
389 }
390 }
391
392 fn expect(&mut self, expected: &Token) -> PerlResult<usize> {
393 let (tok, line) = self.advance();
394 if std::mem::discriminant(&tok) == std::mem::discriminant(expected) {
395 Ok(line)
396 } else {
397 Err(self.syntax_err(format!("Expected {:?}, got {:?}", expected, tok), line))
398 }
399 }
400
401 fn eat(&mut self, expected: &Token) -> bool {
402 if std::mem::discriminant(self.peek()) == std::mem::discriminant(expected) {
403 self.advance();
404 true
405 } else {
406 false
407 }
408 }
409
410 fn at_eof(&self) -> bool {
411 matches!(self.peek(), Token::Eof)
412 }
413
414 fn filetest_allows_implicit_topic(tok: &Token) -> bool {
416 matches!(
417 tok,
418 Token::RParen
419 | Token::Semicolon
420 | Token::Comma
421 | Token::RBrace
422 | Token::Eof
423 | Token::LogAnd
424 | Token::LogOr
425 | Token::LogAndWord
426 | Token::LogOrWord
427 | Token::PipeForward
428 )
429 }
430
431 fn next_is_new_stmt_keyword(&self, stmt_line: usize) -> bool {
435 if crate::compat_mode() {
437 return false;
438 }
439 if self.peek_line() == stmt_line {
440 return false;
441 }
442 matches!(
443 self.peek(),
444 Token::Ident(ref kw) if matches!(kw.as_str(),
445 "use" | "no" | "my" | "our" | "local" | "sub" | "struct" | "enum"
446 | "if" | "unless" | "while" | "until" | "for" | "foreach"
447 | "return" | "last" | "next" | "redo" | "package" | "require"
448 | "BEGIN" | "END" | "UNITCHECK" | "frozen" | "const" | "typed"
449 )
450 )
451 }
452
453 fn next_is_new_statement_start(&self, stmt_line: usize) -> bool {
457 if crate::compat_mode() {
458 return false;
459 }
460 if self.peek_line() == stmt_line {
461 return false;
462 }
463 matches!(
464 self.peek(),
465 Token::ScalarVar(_)
466 | Token::DerefScalarVar(_)
467 | Token::ArrayVar(_)
468 | Token::HashVar(_)
469 | Token::LBrace
470 ) || self.next_is_new_stmt_keyword(stmt_line)
471 }
472
473 pub fn parse_program(&mut self) -> PerlResult<Program> {
476 let mut statements = self.parse_statements()?;
477 if !self.pending_synthetic_subs.is_empty() {
481 let synthetics = std::mem::take(&mut self.pending_synthetic_subs);
482 let mut combined = Vec::with_capacity(synthetics.len() + statements.len());
483 combined.extend(synthetics);
484 combined.extend(statements.drain(..));
485 statements = combined;
486 }
487 Ok(Program { statements })
488 }
489
490 pub fn parse_statements(&mut self) -> PerlResult<Vec<Statement>> {
492 let mut statements = Vec::new();
493 while !self.at_eof() {
494 if matches!(self.peek(), Token::Semicolon) {
495 let line = self.peek_line();
496 self.advance();
497 statements.push(Statement {
498 label: None,
499 kind: StmtKind::Empty,
500 line,
501 });
502 continue;
503 }
504 statements.push(self.parse_statement()?);
505 }
506 Ok(statements)
507 }
508
509 fn parse_statement(&mut self) -> PerlResult<Statement> {
512 let line = self.peek_line();
513
514 let label = match self.peek().clone() {
517 Token::Ident(_) => {
518 if matches!(self.peek_at(1), Token::Colon)
519 && !matches!(self.peek_at(2), Token::Colon)
520 {
521 let (tok, _) = self.advance();
522 let l = match tok {
523 Token::Ident(l) => l,
524 _ => unreachable!(),
525 };
526 self.advance(); Some(l)
528 } else {
529 None
530 }
531 }
532 _ => None,
533 };
534
535 let mut stmt = match self.peek().clone() {
536 Token::FormatDecl { .. } => {
537 let tok_line = self.peek_line();
538 let (tok, _) = self.advance();
539 match tok {
540 Token::FormatDecl { name, lines } => Statement {
541 label: label.clone(),
542 kind: StmtKind::FormatDecl { name, lines },
543 line: tok_line,
544 },
545 _ => unreachable!(),
546 }
547 }
548 Token::Ident(ref kw) => match kw.as_str() {
549 "if" => self.parse_if()?,
550 "unless" => self.parse_unless()?,
551 "while" => {
552 let mut s = self.parse_while()?;
553 if let StmtKind::While {
554 label: ref mut lbl, ..
555 } = s.kind
556 {
557 *lbl = label.clone();
558 }
559 s
560 }
561 "until" => {
562 let mut s = self.parse_until()?;
563 if let StmtKind::Until {
564 label: ref mut lbl, ..
565 } = s.kind
566 {
567 *lbl = label.clone();
568 }
569 s
570 }
571 "for" => {
572 let mut s = self.parse_for_or_foreach()?;
573 match s.kind {
574 StmtKind::For {
575 label: ref mut lbl, ..
576 }
577 | StmtKind::Foreach {
578 label: ref mut lbl, ..
579 } => *lbl = label.clone(),
580 _ => {}
581 }
582 s
583 }
584 "foreach" => {
585 let mut s = self.parse_foreach()?;
586 if let StmtKind::Foreach {
587 label: ref mut lbl, ..
588 } = s.kind
589 {
590 *lbl = label.clone();
591 }
592 s
593 }
594 "sub" => {
595 if crate::no_interop_mode() {
596 return Err(self.syntax_err(
597 "stryke uses `fn` instead of `sub` (--no-interop is active)",
598 self.peek_line(),
599 ));
600 }
601 self.parse_sub_decl(true)?
602 }
603 "fn" => self.parse_sub_decl(false)?,
604 "struct" => {
605 if crate::compat_mode() {
606 return Err(self.syntax_err(
607 "`struct` is a stryke extension (disabled by --compat)",
608 self.peek_line(),
609 ));
610 }
611 self.parse_struct_decl()?
612 }
613 "enum" => {
614 if crate::compat_mode() {
615 return Err(self.syntax_err(
616 "`enum` is a stryke extension (disabled by --compat)",
617 self.peek_line(),
618 ));
619 }
620 self.parse_enum_decl()?
621 }
622 "class" => {
623 if crate::compat_mode() {
624 return Err(self.syntax_err(
626 "Perl 5.38 `class` syntax not yet implemented in --compat mode",
627 self.peek_line(),
628 ));
629 }
630 self.parse_class_decl(false, false)?
631 }
632 "abstract" => {
633 self.advance(); if !matches!(self.peek(), Token::Ident(ref s) if s == "class") {
635 return Err(self.syntax_err(
636 "`abstract` must be followed by `class`",
637 self.peek_line(),
638 ));
639 }
640 self.parse_class_decl(true, false)?
641 }
642 "final" => {
643 self.advance(); if !matches!(self.peek(), Token::Ident(ref s) if s == "class") {
645 return Err(self
646 .syntax_err("`final` must be followed by `class`", self.peek_line()));
647 }
648 self.parse_class_decl(false, true)?
649 }
650 "trait" => {
651 if crate::compat_mode() {
652 return Err(self.syntax_err(
653 "`trait` is a stryke extension (disabled by --compat)",
654 self.peek_line(),
655 ));
656 }
657 self.parse_trait_decl()?
658 }
659 "my" => self.parse_my_our_local("my", false)?,
660 "state" => self.parse_my_our_local("state", false)?,
661 "mysync" => {
662 if crate::compat_mode() {
663 return Err(self.syntax_err(
664 "`mysync` is a stryke extension (disabled by --compat)",
665 self.peek_line(),
666 ));
667 }
668 self.parse_my_our_local("mysync", false)?
669 }
670 "frozen" | "const" => {
671 let leading = kw.as_str().to_string();
672 if crate::compat_mode() {
673 return Err(self.syntax_err(
674 format!("`{leading}` is a stryke extension (disabled by --compat)"),
675 self.peek_line(),
676 ));
677 }
678 self.advance(); if let Token::Ident(ref kw) = self.peek().clone() {
684 if kw == "my" {
685 let mut stmt = self.parse_my_our_local("my", false)?;
686 if let StmtKind::My(ref mut decls) = stmt.kind {
687 for decl in decls.iter_mut() {
688 decl.frozen = true;
689 }
690 }
691 stmt
692 } else {
693 return Err(self.syntax_err(
694 format!("Expected 'my' after '{leading}'"),
695 self.peek_line(),
696 ));
697 }
698 } else {
699 return Err(self.syntax_err(
700 format!("Expected 'my' after '{leading}'"),
701 self.peek_line(),
702 ));
703 }
704 }
705 "typed" => {
706 if crate::compat_mode() {
707 return Err(self.syntax_err(
708 "`typed` is a stryke extension (disabled by --compat)",
709 self.peek_line(),
710 ));
711 }
712 self.advance();
713 if let Token::Ident(ref kw) = self.peek().clone() {
714 if kw == "my" {
715 self.parse_my_our_local("my", true)?
716 } else {
717 return Err(
718 self.syntax_err("Expected 'my' after 'typed'", self.peek_line())
719 );
720 }
721 } else {
722 return Err(
723 self.syntax_err("Expected 'my' after 'typed'", self.peek_line())
724 );
725 }
726 }
727 "our" => self.parse_my_our_local("our", false)?,
728 "local" => self.parse_my_our_local("local", false)?,
729 "package" => self.parse_package()?,
730 "use" => self.parse_use()?,
731 "no" => self.parse_no()?,
732 "return" => self.parse_return()?,
733 "last" => {
734 self.advance();
735 let lbl = if let Token::Ident(ref s) = self.peek() {
736 if s.chars().all(|c| c.is_uppercase() || c == '_') {
737 let (Token::Ident(l), _) = self.advance() else {
738 unreachable!()
739 };
740 Some(l)
741 } else {
742 None
743 }
744 } else {
745 None
746 };
747 let stmt = Statement {
748 label: None,
749 kind: StmtKind::Last(lbl.or(label.clone())),
750 line,
751 };
752 self.parse_stmt_postfix_modifier(stmt)?
753 }
754 "next" => {
755 self.advance();
756 let lbl = if let Token::Ident(ref s) = self.peek() {
757 if s.chars().all(|c| c.is_uppercase() || c == '_') {
758 let (Token::Ident(l), _) = self.advance() else {
759 unreachable!()
760 };
761 Some(l)
762 } else {
763 None
764 }
765 } else {
766 None
767 };
768 let stmt = Statement {
769 label: None,
770 kind: StmtKind::Next(lbl.or(label.clone())),
771 line,
772 };
773 self.parse_stmt_postfix_modifier(stmt)?
774 }
775 "redo" => {
776 self.advance();
777 self.eat(&Token::Semicolon);
778 Statement {
779 label: None,
780 kind: StmtKind::Redo(label.clone()),
781 line,
782 }
783 }
784 "BEGIN" => {
785 self.advance();
786 let block = self.parse_block()?;
787 Statement {
788 label: None,
789 kind: StmtKind::Begin(block),
790 line,
791 }
792 }
793 "END" => {
794 self.advance();
795 let block = self.parse_block()?;
796 Statement {
797 label: None,
798 kind: StmtKind::End(block),
799 line,
800 }
801 }
802 "UNITCHECK" => {
803 self.advance();
804 let block = self.parse_block()?;
805 Statement {
806 label: None,
807 kind: StmtKind::UnitCheck(block),
808 line,
809 }
810 }
811 "CHECK" => {
812 self.advance();
813 let block = self.parse_block()?;
814 Statement {
815 label: None,
816 kind: StmtKind::Check(block),
817 line,
818 }
819 }
820 "INIT" => {
821 self.advance();
822 let block = self.parse_block()?;
823 Statement {
824 label: None,
825 kind: StmtKind::Init(block),
826 line,
827 }
828 }
829 "goto" => {
830 self.advance();
831 let target = self.parse_expression()?;
832 let stmt = Statement {
833 label: None,
834 kind: StmtKind::Goto {
835 target: Box::new(target),
836 },
837 line,
838 };
839 self.parse_stmt_postfix_modifier(stmt)?
841 }
842 "continue" => {
843 self.advance();
844 let block = self.parse_block()?;
845 Statement {
846 label: None,
847 kind: StmtKind::Continue(block),
848 line,
849 }
850 }
851 "before"
852 if matches!(
853 self.peek_at(1),
854 Token::SingleString(_) | Token::DoubleString(_)
855 ) =>
856 {
857 self.parse_advice_decl(crate::ast::AdviceKind::Before)?
858 }
859 "after"
860 if matches!(
861 self.peek_at(1),
862 Token::SingleString(_) | Token::DoubleString(_)
863 ) =>
864 {
865 self.parse_advice_decl(crate::ast::AdviceKind::After)?
866 }
867 "around"
868 if matches!(
869 self.peek_at(1),
870 Token::SingleString(_) | Token::DoubleString(_)
871 ) =>
872 {
873 self.parse_advice_decl(crate::ast::AdviceKind::Around)?
874 }
875 "try" => self.parse_try_catch()?,
876 "defer" => self.parse_defer_stmt()?,
877 "tie" => self.parse_tie_stmt()?,
878 "given" => self.parse_given()?,
879 "when" => self.parse_when_stmt()?,
880 "default" => self.parse_default_stmt()?,
881 "eval_timeout" => self.parse_eval_timeout()?,
882 "do" => {
883 if matches!(self.peek_at(1), Token::LBrace) {
884 self.advance();
885 let body = self.parse_block()?;
886 if let Token::Ident(ref w) = self.peek().clone() {
887 if w == "while" {
888 self.advance();
889 self.expect(&Token::LParen)?;
890 let mut condition = self.parse_expression()?;
891 Self::mark_match_scalar_g_for_boolean_condition(&mut condition);
892 self.expect(&Token::RParen)?;
893 self.eat(&Token::Semicolon);
894 Statement {
895 label: label.clone(),
896 kind: StmtKind::DoWhile { body, condition },
897 line,
898 }
899 } else {
900 let inner_line = body.first().map(|s| s.line).unwrap_or(line);
901 let inner = Expr {
902 kind: ExprKind::CodeRef {
903 params: vec![],
904 body,
905 },
906 line: inner_line,
907 };
908 let expr = Expr {
909 kind: ExprKind::Do(Box::new(inner)),
910 line,
911 };
912 let stmt = Statement {
913 label: label.clone(),
914 kind: StmtKind::Expression(expr),
915 line,
916 };
917 self.parse_stmt_postfix_modifier(stmt)?
919 }
920 } else {
921 let inner_line = body.first().map(|s| s.line).unwrap_or(line);
922 let inner = Expr {
923 kind: ExprKind::CodeRef {
924 params: vec![],
925 body,
926 },
927 line: inner_line,
928 };
929 let expr = Expr {
930 kind: ExprKind::Do(Box::new(inner)),
931 line,
932 };
933 let stmt = Statement {
934 label: label.clone(),
935 kind: StmtKind::Expression(expr),
936 line,
937 };
938 self.parse_stmt_postfix_modifier(stmt)?
939 }
940 } else {
941 if let Some(expr) = self.try_parse_bareword_stmt_call() {
942 let stmt = self.maybe_postfix_modifier(expr)?;
943 self.parse_stmt_postfix_modifier(stmt)?
944 } else {
945 let expr = self.parse_expression()?;
946 let stmt = self.maybe_postfix_modifier(expr)?;
947 self.parse_stmt_postfix_modifier(stmt)?
948 }
949 }
950 }
951 _ => {
952 if let Some(expr) = self.try_parse_bareword_stmt_call() {
954 let stmt = self.maybe_postfix_modifier(expr)?;
955 self.parse_stmt_postfix_modifier(stmt)?
956 } else {
957 let expr = self.parse_expression()?;
958 let stmt = self.maybe_postfix_modifier(expr)?;
959 self.parse_stmt_postfix_modifier(stmt)?
960 }
961 }
962 },
963 Token::LBrace => {
964 if self.looks_like_hashref() {
967 let expr = self.parse_expression()?;
968 let stmt = self.maybe_postfix_modifier(expr)?;
969 self.parse_stmt_postfix_modifier(stmt)?
970 } else {
971 let block = self.parse_block()?;
972 let stmt = Statement {
973 label: None,
974 kind: StmtKind::Block(block),
975 line,
976 };
977 self.parse_stmt_postfix_modifier(stmt)?
979 }
980 }
981 _ => {
982 let expr = self.parse_expression()?;
983 let stmt = self.maybe_postfix_modifier(expr)?;
984 self.parse_stmt_postfix_modifier(stmt)?
985 }
986 };
987
988 stmt.label = label;
989 Ok(stmt)
990 }
991
992 fn parse_stmt_postfix_modifier(&mut self, stmt: Statement) -> PerlResult<Statement> {
994 let line = stmt.line;
995 if self.peek_line() > self.prev_line() {
1000 self.eat(&Token::Semicolon);
1001 return Ok(stmt);
1002 }
1003 if let Token::Ident(ref kw) = self.peek().clone() {
1004 match kw.as_str() {
1005 "if" => {
1006 self.advance();
1007 let mut cond = self.parse_expression()?;
1008 Self::mark_match_scalar_g_for_boolean_condition(&mut cond);
1009 self.eat(&Token::Semicolon);
1010 return Ok(Statement {
1011 label: None,
1012 kind: StmtKind::If {
1013 condition: cond,
1014 body: vec![stmt],
1015 elsifs: vec![],
1016 else_block: None,
1017 },
1018 line,
1019 });
1020 }
1021 "unless" => {
1022 self.advance();
1023 let mut cond = self.parse_expression()?;
1024 Self::mark_match_scalar_g_for_boolean_condition(&mut cond);
1025 self.eat(&Token::Semicolon);
1026 return Ok(Statement {
1027 label: None,
1028 kind: StmtKind::Unless {
1029 condition: cond,
1030 body: vec![stmt],
1031 else_block: None,
1032 },
1033 line,
1034 });
1035 }
1036 "while" | "until" | "for" | "foreach" => {
1037 if let Some(expr) = Self::stmt_into_postfix_body_expr(stmt) {
1040 let out = self.maybe_postfix_modifier(expr)?;
1041 self.eat(&Token::Semicolon);
1042 return Ok(out);
1043 }
1044 return Err(self.syntax_err(
1045 format!("postfix `{}` is not supported on this statement form", kw),
1046 self.peek_line(),
1047 ));
1048 }
1049 "pmap" | "pflat_map" | "pgrep" | "pfor" | "preduce" | "pcache" => {
1051 let line = stmt.line;
1052 let block = self.stmt_into_parallel_block(stmt)?;
1053 let which = kw.as_str();
1054 self.advance();
1055 self.eat(&Token::Comma);
1056 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
1057 self.eat(&Token::Semicolon);
1058 let list = Box::new(list);
1059 let progress = progress.map(Box::new);
1060 let kind = match which {
1061 "pmap" => ExprKind::PMapExpr {
1062 block,
1063 list,
1064 progress,
1065 flat_outputs: false,
1066 on_cluster: None,
1067 stream: false,
1068 },
1069 "pflat_map" => ExprKind::PMapExpr {
1070 block,
1071 list,
1072 progress,
1073 flat_outputs: true,
1074 on_cluster: None,
1075 stream: false,
1076 },
1077 "pgrep" => ExprKind::PGrepExpr {
1078 block,
1079 list,
1080 progress,
1081 stream: false,
1082 },
1083 "pfor" => ExprKind::PForExpr {
1084 block,
1085 list,
1086 progress,
1087 },
1088 "preduce" => ExprKind::PReduceExpr {
1089 block,
1090 list,
1091 progress,
1092 },
1093 "pcache" => ExprKind::PcacheExpr {
1094 block,
1095 list,
1096 progress,
1097 },
1098 _ => unreachable!(),
1099 };
1100 return Ok(Statement {
1101 label: None,
1102 kind: StmtKind::Expression(Expr { kind, line }),
1103 line,
1104 });
1105 }
1106 _ => {}
1107 }
1108 }
1109 self.eat(&Token::Semicolon);
1110 Ok(stmt)
1111 }
1112
1113 fn stmt_into_parallel_block(&self, stmt: Statement) -> PerlResult<Block> {
1116 let line = stmt.line;
1117 match stmt.kind {
1118 StmtKind::Block(block) => Ok(block),
1119 StmtKind::Expression(expr) => {
1120 if let ExprKind::Do(ref inner) = expr.kind {
1121 if let ExprKind::CodeRef { ref body, .. } = inner.kind {
1122 return Ok(body.clone());
1123 }
1124 }
1125 Ok(vec![Statement {
1126 label: None,
1127 kind: StmtKind::Expression(expr),
1128 line,
1129 }])
1130 }
1131 _ => Err(self.syntax_err(
1132 "postfix parallel op expects `do { }`, a bare `{ }` block, or an expression statement",
1133 line,
1134 )),
1135 }
1136 }
1137
1138 fn stmt_into_postfix_body_expr(stmt: Statement) -> Option<Expr> {
1141 match stmt.kind {
1142 StmtKind::Expression(expr) => Some(expr),
1143 StmtKind::Block(block) => {
1144 let line = stmt.line;
1145 let inner = Expr {
1146 kind: ExprKind::CodeRef {
1147 params: vec![],
1148 body: block,
1149 },
1150 line,
1151 };
1152 Some(Expr {
1153 kind: ExprKind::Do(Box::new(inner)),
1154 line,
1155 })
1156 }
1157 _ => None,
1158 }
1159 }
1160
1161 fn peek_is_postfix_stmt_modifier_keyword(&self) -> bool {
1164 matches!(
1165 self.peek(),
1166 Token::Ident(ref kw)
1167 if matches!(
1168 kw.as_str(),
1169 "if" | "unless" | "while" | "until" | "for" | "foreach"
1170 )
1171 )
1172 }
1173
1174 fn peek_is_named_unary_terminator(&self) -> bool {
1181 matches!(
1182 self.peek(),
1183 Token::Semicolon
1184 | Token::RBrace
1185 | Token::RParen
1186 | Token::RBracket
1187 | Token::Eof
1188 | Token::Comma
1189 | Token::FatArrow
1190 | Token::PipeForward
1191 | Token::Question
1192 | Token::Colon
1193 | Token::NumEq
1194 | Token::NumNe
1195 | Token::NumLt
1196 | Token::NumGt
1197 | Token::NumLe
1198 | Token::NumGe
1199 | Token::Spaceship
1200 | Token::StrEq
1201 | Token::StrNe
1202 | Token::StrLt
1203 | Token::StrGt
1204 | Token::StrLe
1205 | Token::StrGe
1206 | Token::StrCmp
1207 | Token::LogAnd
1208 | Token::LogOr
1209 | Token::LogAndWord
1210 | Token::LogOrWord
1211 | Token::DefinedOr
1212 | Token::Range
1213 | Token::RangeExclusive
1214 | Token::Assign
1215 | Token::PlusAssign
1216 | Token::MinusAssign
1217 | Token::MulAssign
1218 | Token::DivAssign
1219 | Token::ModAssign
1220 | Token::PowAssign
1221 | Token::DotAssign
1222 | Token::AndAssign
1223 | Token::OrAssign
1224 | Token::XorAssign
1225 | Token::DefinedOrAssign
1226 | Token::ShiftLeftAssign
1227 | Token::ShiftRightAssign
1228 | Token::BitAndAssign
1229 | Token::BitOrAssign
1230 )
1231 }
1232
1233 fn maybe_postfix_modifier(&mut self, expr: Expr) -> PerlResult<Statement> {
1234 let line = expr.line;
1235 if self.peek_line() > self.prev_line() {
1237 return Ok(Statement {
1238 label: None,
1239 kind: StmtKind::Expression(expr),
1240 line,
1241 });
1242 }
1243 match self.peek() {
1244 Token::Ident(ref kw) => match kw.as_str() {
1245 "if" => {
1246 self.advance();
1247 let cond = self.parse_expression()?;
1248 Ok(Statement {
1249 label: None,
1250 kind: StmtKind::Expression(Expr {
1251 kind: ExprKind::PostfixIf {
1252 expr: Box::new(expr),
1253 condition: Box::new(cond),
1254 },
1255 line,
1256 }),
1257 line,
1258 })
1259 }
1260 "unless" => {
1261 self.advance();
1262 let cond = self.parse_expression()?;
1263 Ok(Statement {
1264 label: None,
1265 kind: StmtKind::Expression(Expr {
1266 kind: ExprKind::PostfixUnless {
1267 expr: Box::new(expr),
1268 condition: Box::new(cond),
1269 },
1270 line,
1271 }),
1272 line,
1273 })
1274 }
1275 "while" => {
1276 self.advance();
1277 let cond = self.parse_expression()?;
1278 Ok(Statement {
1279 label: None,
1280 kind: StmtKind::Expression(Expr {
1281 kind: ExprKind::PostfixWhile {
1282 expr: Box::new(expr),
1283 condition: Box::new(cond),
1284 },
1285 line,
1286 }),
1287 line,
1288 })
1289 }
1290 "until" => {
1291 self.advance();
1292 let cond = self.parse_expression()?;
1293 Ok(Statement {
1294 label: None,
1295 kind: StmtKind::Expression(Expr {
1296 kind: ExprKind::PostfixUntil {
1297 expr: Box::new(expr),
1298 condition: Box::new(cond),
1299 },
1300 line,
1301 }),
1302 line,
1303 })
1304 }
1305 "for" | "foreach" => {
1306 self.advance();
1307 let list = self.parse_expression()?;
1308 Ok(Statement {
1309 label: None,
1310 kind: StmtKind::Expression(Expr {
1311 kind: ExprKind::PostfixForeach {
1312 expr: Box::new(expr),
1313 list: Box::new(list),
1314 },
1315 line,
1316 }),
1317 line,
1318 })
1319 }
1320 _ => Ok(Statement {
1321 label: None,
1322 kind: StmtKind::Expression(expr),
1323 line,
1324 }),
1325 },
1326 _ => Ok(Statement {
1327 label: None,
1328 kind: StmtKind::Expression(expr),
1329 line,
1330 }),
1331 }
1332 }
1333
1334 fn try_parse_bareword_stmt_call(&mut self) -> Option<Expr> {
1336 let saved = self.pos;
1337 let line = self.peek_line();
1338 let mut name = match self.peek() {
1339 Token::Ident(n) => n.clone(),
1340 _ => return None,
1341 };
1342 if name.starts_with('\x00') || !Self::bareword_stmt_may_be_sub(&name) {
1344 return None;
1345 }
1346 self.advance();
1347 while self.eat(&Token::PackageSep) {
1348 match self.advance() {
1349 (Token::Ident(part), _) => {
1350 name = format!("{}::{}", name, part);
1351 }
1352 _ => {
1353 self.pos = saved;
1354 return None;
1355 }
1356 }
1357 }
1358 match self.peek() {
1359 Token::Semicolon | Token::RBrace => Some(Expr {
1360 kind: ExprKind::FuncCall { name, args: vec![] },
1361 line,
1362 }),
1363 _ => {
1364 self.pos = saved;
1365 None
1366 }
1367 }
1368 }
1369
1370 pub(crate) fn operator_keyword_to_ident_str(tok: &Token) -> Option<&'static str> {
1374 Some(match tok {
1375 Token::StrEq => "eq",
1376 Token::StrNe => "ne",
1377 Token::StrLt => "lt",
1378 Token::StrGt => "gt",
1379 Token::StrLe => "le",
1380 Token::StrGe => "ge",
1381 Token::StrCmp => "cmp",
1382 Token::LogAndWord => "and",
1383 Token::LogOrWord => "or",
1384 Token::LogNotWord => "not",
1385 Token::X => "x",
1386 _ => return None,
1387 })
1388 }
1389
1390 pub(crate) fn is_underscore_topic_slot(name: &str) -> bool {
1394 if name == "_" {
1395 return true;
1396 }
1397 if !name.starts_with('_') || name.len() < 2 {
1398 return false;
1399 }
1400 let bytes = name.as_bytes();
1401 let mut i = 1;
1402 while i < bytes.len() && bytes[i].is_ascii_digit() {
1404 i += 1;
1405 }
1406 let chevrons_start = i;
1408 while i < bytes.len() && bytes[i] == b'<' {
1409 i += 1;
1410 }
1411 i == bytes.len() && (i > 1 || chevrons_start > 1)
1413 }
1414
1415 fn bareword_stmt_may_be_sub(name: &str) -> bool {
1417 if Self::is_underscore_topic_slot(name) {
1422 return false;
1423 }
1424 !matches!(
1425 name,
1426 "__FILE__"
1427 | "__LINE__"
1428 | "abs"
1429 | "async"
1430 | "spawn"
1431 | "atan2"
1432 | "await"
1433 | "barrier"
1434 | "bless"
1435 | "caller"
1436 | "capture"
1437 | "cat"
1438 | "chdir"
1439 | "chmod"
1440 | "chomp"
1441 | "chop"
1442 | "chr"
1443 | "chown"
1444 | "closedir"
1445 | "close"
1446 | "collect"
1447 | "cos"
1448 | "crypt"
1449 | "defined"
1450 | "dec"
1451 | "delete"
1452 | "die"
1453 | "deque"
1454 | "do"
1455 | "each"
1456 | "eof"
1457 | "fore"
1458 | "eval"
1459 | "exec"
1460 | "exists"
1461 | "exit"
1462 | "exp"
1463 | "fan"
1464 | "fan_cap"
1465 | "fc"
1466 | "fetch_url"
1467 | "d"
1468 | "dirs"
1469 | "dr"
1470 | "f"
1471 | "fi"
1472 | "files"
1473 | "filesf"
1474 | "filter"
1475 | "fr"
1476 | "getcwd"
1477 | "glob_par"
1478 | "par_sed"
1479 | "glob"
1480 | "grep"
1481 | "greps"
1482 | "heap"
1483 | "hex"
1484 | "inc"
1485 | "index"
1486 | "int"
1487 | "join"
1488 | "keys"
1489 | "lcfirst"
1490 | "lc"
1491 | "length"
1492 | "link"
1493 | "log"
1494 | "lstat"
1495 | "map"
1496 | "flat_map"
1497 | "maps"
1498 | "flat_maps"
1499 | "flatten"
1500 | "frequencies"
1501 | "freq"
1502 | "interleave"
1503 | "ddump"
1504 | "stringify"
1505 | "str"
1506 | "s"
1507 | "input"
1508 | "lines"
1509 | "words"
1510 | "chars"
1511 | "digits"
1512 | "letters"
1513 | "letters_uc"
1514 | "letters_lc"
1515 | "punctuation"
1516 | "sentences"
1517 | "paragraphs"
1518 | "sections"
1519 | "numbers"
1520 | "graphemes"
1521 | "columns"
1522 | "trim"
1523 | "avg"
1524 | "top"
1525 | "pager"
1526 | "pg"
1527 | "less"
1528 | "count_by"
1529 | "to_file"
1530 | "to_json"
1531 | "to_csv"
1532 | "grep_v"
1533 | "select_keys"
1534 | "pluck"
1535 | "clamp"
1536 | "normalize"
1537 | "stddev"
1538 | "squared"
1539 | "square"
1540 | "cubed"
1541 | "cube"
1542 | "expt"
1543 | "pow"
1544 | "pw"
1545 | "snake_case"
1546 | "camel_case"
1547 | "kebab_case"
1548 | "to_toml"
1549 | "to_yaml"
1550 | "to_xml"
1551 | "to_html"
1552 | "to_markdown"
1553 | "xopen"
1554 | "clip"
1555 | "paste"
1556 | "to_table"
1557 | "sparkline"
1558 | "bar_chart"
1559 | "flame"
1560 | "set"
1561 | "list_count"
1562 | "list_size"
1563 | "count"
1564 | "size"
1565 | "cnt"
1566 | "len"
1567 | "all"
1568 | "any"
1569 | "none"
1570 | "take_while"
1571 | "drop_while"
1572 | "skip_while"
1573 | "skip"
1574 | "first_or"
1575 | "tap"
1576 | "peek"
1577 | "partition"
1578 | "min_by"
1579 | "max_by"
1580 | "zip_with"
1581 | "group_by"
1582 | "chunk_by"
1583 | "with_index"
1584 | "puniq"
1585 | "pfirst"
1586 | "pany"
1587 | "uniq"
1588 | "distinct"
1589 | "shuffle"
1590 | "shuffled"
1591 | "chunked"
1592 | "windowed"
1593 | "match"
1594 | "mkdir"
1595 | "every"
1596 | "gen"
1597 | "oct"
1598 | "open"
1599 | "p"
1600 | "opendir"
1601 | "ord"
1602 | "par_lines"
1603 | "par_walk"
1604 | "pipe"
1605 | "pipes"
1606 | "block_devices"
1607 | "char_devices"
1608 | "exe"
1609 | "executables"
1610 | "rate_limit"
1611 | "retry"
1612 | "pcache"
1613 | "pchannel"
1614 | "pfor"
1615 | "pgrep"
1616 | "pgreps"
1617 | "pipeline"
1618 | "pmap_chunked"
1619 | "pmap_reduce"
1620 | "pmap_on"
1621 | "pflat_map_on"
1622 | "pmap"
1623 | "pmaps"
1624 | "pflat_map"
1625 | "pflat_maps"
1626 | "pop"
1627 | "pos"
1628 | "ppool"
1629 | "preduce_init"
1630 | "preduce"
1631 | "pselect"
1632 | "printf"
1633 | "print"
1634 | "pr"
1635 | "psort"
1636 | "push"
1637 | "pwatch"
1638 | "rand"
1639 | "readdir"
1640 | "readlink"
1641 | "reduce"
1642 | "fold"
1643 | "inject"
1644 | "first"
1645 | "detect"
1646 | "find"
1647 | "find_all"
1648 | "ref"
1649 | "rename"
1650 | "require"
1651 | "rev"
1652 | "reverse"
1653 | "reversed"
1654 | "rewinddir"
1655 | "rindex"
1656 | "rmdir"
1657 | "rm"
1658 | "say"
1659 | "scalar"
1660 | "seekdir"
1661 | "shift"
1662 | "sin"
1663 | "slurp"
1664 | "sockets"
1665 | "sort"
1666 | "splice"
1667 | "splice_last"
1668 | "splice1"
1669 | "spl_last"
1670 | "split"
1671 | "sprintf"
1672 | "sqrt"
1673 | "srand"
1674 | "stat"
1675 | "study"
1676 | "substr"
1677 | "symlink"
1678 | "sym_links"
1679 | "system"
1680 | "telldir"
1681 | "timer"
1682 | "trace"
1683 | "ucfirst"
1684 | "uc"
1685 | "undef"
1686 | "umask"
1687 | "unlink"
1688 | "unshift"
1689 | "utime"
1690 | "values"
1691 | "wantarray"
1692 | "warn"
1693 | "watch"
1694 | "yield"
1695 | "sub"
1696 )
1697 }
1698
1699 fn parse_block(&mut self) -> PerlResult<Block> {
1700 self.expect(&Token::LBrace)?;
1701 let saved_pipe_rhs_depth = self.pipe_rhs_depth;
1704 self.pipe_rhs_depth = 0;
1705 let mut stmts = Vec::new();
1706 if let Some(param_stmts) = self.try_parse_block_params()? {
1710 stmts.extend(param_stmts);
1711 }
1712 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
1713 if self.eat(&Token::Semicolon) {
1714 continue;
1715 }
1716 stmts.push(self.parse_statement()?);
1717 }
1718 self.expect(&Token::RBrace)?;
1719 self.pipe_rhs_depth = saved_pipe_rhs_depth;
1720 Self::default_topic_for_sole_bareword(&mut stmts);
1721 Ok(stmts)
1722 }
1723
1724 fn try_parse_block_params(&mut self) -> PerlResult<Option<Vec<Statement>>> {
1729 if !matches!(self.peek(), Token::BitOr) {
1730 return Ok(None);
1731 }
1732 let mut i = 1; loop {
1735 match self.peek_at(i) {
1736 Token::ScalarVar(_) => i += 1,
1737 _ => return Ok(None), }
1739 match self.peek_at(i) {
1740 Token::BitOr => break, Token::Comma => i += 1, _ => return Ok(None), }
1744 }
1745 let line = self.peek_line();
1747 self.advance(); let mut names = Vec::new();
1749 loop {
1750 if let Token::ScalarVar(ref name) = self.peek().clone() {
1751 names.push(name.clone());
1752 self.advance();
1753 }
1754 if self.eat(&Token::BitOr) {
1755 break;
1756 }
1757 self.expect(&Token::Comma)?;
1758 }
1759 let sources: Vec<&str> = match names.len() {
1764 1 => vec!["_"],
1765 2 => vec!["a", "b"],
1766 n => {
1767 let _ = n;
1769 vec![] }
1771 };
1772 let mut stmts = Vec::with_capacity(names.len());
1773 if !sources.is_empty() {
1774 for (name, src) in names.iter().zip(sources.iter()) {
1775 stmts.push(Statement {
1776 label: None,
1777 kind: StmtKind::My(vec![VarDecl {
1778 sigil: Sigil::Scalar,
1779 name: name.clone(),
1780 initializer: Some(Expr {
1781 kind: ExprKind::ScalarVar(src.to_string()),
1782 line,
1783 }),
1784 frozen: false,
1785 type_annotation: None,
1786 }]),
1787 line,
1788 });
1789 }
1790 } else {
1791 for (idx, name) in names.iter().enumerate() {
1793 let src = if idx == 0 {
1794 "_".to_string()
1795 } else {
1796 format!("_{idx}")
1797 };
1798 stmts.push(Statement {
1799 label: None,
1800 kind: StmtKind::My(vec![VarDecl {
1801 sigil: Sigil::Scalar,
1802 name: name.clone(),
1803 initializer: Some(Expr {
1804 kind: ExprKind::ScalarVar(src),
1805 line,
1806 }),
1807 frozen: false,
1808 type_annotation: None,
1809 }]),
1810 line,
1811 });
1812 }
1813 }
1814 Ok(Some(stmts))
1815 }
1816
1817 fn default_topic_for_sole_bareword(stmts: &mut [Statement]) {
1832 let [only] = stmts else { return };
1833 let StmtKind::Expression(ref mut expr) = only.kind else {
1834 return;
1835 };
1836 let topic_line = expr.line;
1837 let topic_arg = || Expr {
1838 kind: ExprKind::ScalarVar("_".to_string()),
1839 line: topic_line,
1840 };
1841 match expr.kind {
1842 ExprKind::FuncCall {
1844 ref name,
1845 ref mut args,
1846 } if args.is_empty()
1847 && (Self::is_known_bareword(name) || Self::is_try_builtin_name(name)) =>
1848 {
1849 args.push(topic_arg());
1850 }
1851 ExprKind::Bareword(ref name)
1855 if (Self::is_known_bareword(name) || Self::is_try_builtin_name(name)) =>
1856 {
1857 let n = name.clone();
1858 expr.kind = ExprKind::FuncCall {
1859 name: n,
1860 args: vec![topic_arg()],
1861 };
1862 }
1863 _ => {}
1864 }
1865 }
1866
1867 fn parse_defer_stmt(&mut self) -> PerlResult<Statement> {
1871 let line = self.peek_line();
1872 self.advance(); let body = self.parse_block()?;
1874 self.eat(&Token::Semicolon);
1875 let coderef = Expr {
1877 kind: ExprKind::CodeRef {
1878 params: vec![],
1879 body,
1880 },
1881 line,
1882 };
1883 Ok(Statement {
1884 label: None,
1885 kind: StmtKind::Expression(Expr {
1886 kind: ExprKind::FuncCall {
1887 name: "defer__internal".to_string(),
1888 args: vec![coderef],
1889 },
1890 line,
1891 }),
1892 line,
1893 })
1894 }
1895
1896 fn parse_try_catch(&mut self) -> PerlResult<Statement> {
1898 let line = self.peek_line();
1899 self.advance(); let try_block = self.parse_block()?;
1901 match self.peek() {
1902 Token::Ident(ref k) if k == "catch" => {
1903 self.advance();
1904 }
1905 _ => {
1906 return Err(self.syntax_err("expected 'catch' after try block", self.peek_line()));
1907 }
1908 }
1909 self.expect(&Token::LParen)?;
1910 let catch_var = self.parse_scalar_var_name()?;
1911 self.expect(&Token::RParen)?;
1912 let catch_block = self.parse_block()?;
1913 let finally_block = match self.peek() {
1914 Token::Ident(ref k) if k == "finally" => {
1915 self.advance();
1916 Some(self.parse_block()?)
1917 }
1918 _ => None,
1919 };
1920 self.eat(&Token::Semicolon);
1921 Ok(Statement {
1922 label: None,
1923 kind: StmtKind::TryCatch {
1924 try_block,
1925 catch_var,
1926 catch_block,
1927 finally_block,
1928 },
1929 line,
1930 })
1931 }
1932
1933 fn parse_thread_macro(&mut self, _line: usize, thread_last: bool) -> PerlResult<Expr> {
1947 let saved_thread_last = self.thread_last_mode;
1949 self.thread_last_mode = thread_last;
1950
1951 let pipe_rhs_wrap = self.in_pipe_rhs();
1952 let mut result = if pipe_rhs_wrap {
1953 Expr {
1954 kind: ExprKind::ArrayElement {
1955 array: "_".to_string(),
1956 index: Box::new(Expr {
1957 kind: ExprKind::Integer(0),
1958 line: _line,
1959 }),
1960 },
1961 line: _line,
1962 }
1963 } else {
1964 self.suppress_parenless_call = self.suppress_parenless_call.saturating_add(1);
1967 let expr = self.parse_thread_input();
1968 self.suppress_parenless_call = self.suppress_parenless_call.saturating_sub(1);
1969 expr?
1970 };
1971
1972 let mut last_stage_end_line = self.prev_line();
1974
1975 loop {
1977 if self.peek_line() > last_stage_end_line {
1982 break;
1983 }
1984
1985 match self.peek() {
1989 Token::Semicolon
1990 | Token::RBrace
1991 | Token::RParen
1992 | Token::RBracket
1993 | Token::PipeForward
1994 | Token::Eof
1995 | Token::ScalarVar(_)
1996 | Token::ArrayVar(_)
1997 | Token::HashVar(_)
1998 | Token::Comma => break,
1999 Token::Ident(ref kw)
2000 if matches!(
2001 kw.as_str(),
2002 "my" | "our"
2003 | "local"
2004 | "state"
2005 | "if"
2006 | "unless"
2007 | "while"
2008 | "until"
2009 | "for"
2010 | "foreach"
2011 | "return"
2012 | "last"
2013 | "next"
2014 | "redo"
2015 ) =>
2016 {
2017 break
2018 }
2019 _ => {}
2020 }
2021
2022 let stage_line = self.peek_line();
2023
2024 match self.peek().clone() {
2026 Token::ArrowBrace => {
2028 self.advance(); let mut stmts = Vec::new();
2030 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
2031 if self.eat(&Token::Semicolon) {
2032 continue;
2033 }
2034 stmts.push(self.parse_statement()?);
2035 }
2036 self.expect(&Token::RBrace)?;
2037 let code_ref = Expr {
2038 kind: ExprKind::CodeRef {
2039 params: vec![],
2040 body: stmts,
2041 },
2042 line: stage_line,
2043 };
2044 result = self.pipe_forward_apply(result, code_ref, stage_line)?;
2045 }
2046 Token::Ident(ref name) if name == "sub" => {
2048 if crate::no_interop_mode() {
2049 return Err(self.syntax_err(
2050 "stryke uses `fn {}` instead of `sub {}` (--no-interop)",
2051 stage_line,
2052 ));
2053 }
2054 self.advance(); let (params, _prototype) = self.parse_sub_sig_or_prototype_opt()?;
2056 let body = self.parse_block()?;
2057 let code_ref = Expr {
2058 kind: ExprKind::CodeRef { params, body },
2059 line: stage_line,
2060 };
2061 result = self.pipe_forward_apply(result, code_ref, stage_line)?;
2062 }
2063 Token::Ident(ref name) if name == "fn" => {
2065 self.advance(); let (params, _prototype) = self.parse_sub_sig_or_prototype_opt()?;
2067 self.parse_sub_attributes()?;
2068 let body = self.parse_fn_eq_body_or_block(false)?;
2069 let code_ref = Expr {
2070 kind: ExprKind::CodeRef { params, body },
2071 line: stage_line,
2072 };
2073 result = self.pipe_forward_apply(result, code_ref, stage_line)?;
2074 }
2075 Token::Ident(ref name) => {
2077 let mut func_name = name.clone();
2078 self.advance();
2079
2080 while matches!(self.peek(), Token::PackageSep) {
2082 self.advance(); if let Token::Ident(ref part) = self.peek().clone() {
2084 func_name.push_str("::");
2085 func_name.push_str(part);
2086 self.advance();
2087 } else {
2088 return Err(self.syntax_err(
2089 format!(
2090 "Expected identifier after `::` in thread stage, got {:?}",
2091 self.peek()
2092 ),
2093 stage_line,
2094 ));
2095 }
2096 }
2097
2098 if func_name.starts_with('\x00') {
2100 let parts: Vec<&str> = func_name.split('\x00').collect();
2101 if parts.len() >= 4 && parts[1] == "s" {
2102 let delim = parts.get(5).and_then(|s| s.chars().next()).unwrap_or('/');
2103 let stage = Expr {
2104 kind: ExprKind::Substitution {
2105 expr: Box::new(result.clone()),
2106 pattern: parts[2].to_string(),
2107 replacement: parts[3].to_string(),
2108 flags: format!("{}r", parts.get(4).unwrap_or(&"")),
2109 delim,
2110 },
2111 line: stage_line,
2112 };
2113 result = stage;
2114 last_stage_end_line = self.prev_line();
2115 continue;
2116 }
2117 if parts.len() >= 4 && parts[1] == "tr" {
2118 let delim = parts.get(5).and_then(|s| s.chars().next()).unwrap_or('/');
2119 let stage = Expr {
2120 kind: ExprKind::Transliterate {
2121 expr: Box::new(result.clone()),
2122 from: parts[2].to_string(),
2123 to: parts[3].to_string(),
2124 flags: format!("{}r", parts.get(4).unwrap_or(&"")),
2125 delim,
2126 },
2127 line: stage_line,
2128 };
2129 result = stage;
2130 last_stage_end_line = self.prev_line();
2131 continue;
2132 }
2133 return Err(
2134 self.syntax_err("Unexpected encoded token in thread", stage_line)
2135 );
2136 }
2137
2138 if matches!(self.peek(), Token::Plus)
2143 && matches!(self.peek_at(1), Token::LBrace)
2144 {
2145 self.advance(); self.expect(&Token::LBrace)?;
2147 let pairs = self.try_parse_hash_ref()?;
2149 let hashref_expr = Expr {
2150 kind: ExprKind::HashRef(pairs),
2151 line: stage_line,
2152 };
2153 let flatten_array_refs =
2154 matches!(func_name.as_str(), "flat_map" | "flat_maps");
2155 let stream = matches!(func_name.as_str(), "maps" | "flat_maps");
2156 let placeholder = Expr {
2158 kind: ExprKind::Undef,
2159 line: stage_line,
2160 };
2161 let map_node = Expr {
2162 kind: ExprKind::MapExprComma {
2163 expr: Box::new(hashref_expr),
2164 list: Box::new(placeholder),
2165 flatten_array_refs,
2166 stream,
2167 },
2168 line: stage_line,
2169 };
2170 result = self.pipe_forward_apply(result, map_node, stage_line)?;
2171 } else if func_name == "pmap_chunked" {
2173 let chunk_size = self.parse_assign_expr()?;
2174 let block = self.parse_block_or_bareword_block()?;
2175 let placeholder = self.pipe_placeholder_list(stage_line);
2176 let stage = Expr {
2177 kind: ExprKind::PMapChunkedExpr {
2178 chunk_size: Box::new(chunk_size),
2179 block,
2180 list: Box::new(placeholder),
2181 progress: None,
2182 },
2183 line: stage_line,
2184 };
2185 result = self.pipe_forward_apply(result, stage, stage_line)?;
2186 } else if func_name == "preduce_init" {
2188 let init = self.parse_assign_expr()?;
2189 let block = self.parse_block_or_bareword_block()?;
2190 let placeholder = self.pipe_placeholder_list(stage_line);
2191 let stage = Expr {
2192 kind: ExprKind::PReduceInitExpr {
2193 init: Box::new(init),
2194 block,
2195 list: Box::new(placeholder),
2196 progress: None,
2197 },
2198 line: stage_line,
2199 };
2200 result = self.pipe_forward_apply(result, stage, stage_line)?;
2201 } else if func_name == "pmap_reduce" {
2203 let map_block = self.parse_block_or_bareword_block()?;
2204 let reduce_block = if matches!(self.peek(), Token::LBrace) {
2205 self.parse_block()?
2206 } else {
2207 self.expect(&Token::Comma)?;
2208 self.parse_block_or_bareword_cmp_block()?
2209 };
2210 let placeholder = self.pipe_placeholder_list(stage_line);
2211 let stage = Expr {
2212 kind: ExprKind::PMapReduceExpr {
2213 map_block,
2214 reduce_block,
2215 list: Box::new(placeholder),
2216 progress: None,
2217 },
2218 line: stage_line,
2219 };
2220 result = self.pipe_forward_apply(result, stage, stage_line)?;
2221 } else if func_name == "pmap_on" || func_name == "pflat_map_on" {
2226 self.suppress_scalar_hash_brace =
2229 self.suppress_scalar_hash_brace.saturating_add(1);
2230 let cluster = self.parse_assign_expr();
2231 self.suppress_scalar_hash_brace =
2232 self.suppress_scalar_hash_brace.saturating_sub(1);
2233 let cluster = cluster?;
2234 self.eat(&Token::Comma);
2237 let block = self.parse_block_or_bareword_block()?;
2238 let placeholder = self.pipe_placeholder_list(stage_line);
2239 let stage = Expr {
2240 kind: ExprKind::PMapExpr {
2241 block,
2242 list: Box::new(placeholder),
2243 progress: None,
2244 flat_outputs: func_name == "pflat_map_on",
2245 on_cluster: Some(Box::new(cluster)),
2246 stream: false,
2247 },
2248 line: stage_line,
2249 };
2250 result = self.pipe_forward_apply(result, stage, stage_line)?;
2251 } else if matches!(self.peek(), Token::LBrace) {
2253 self.pipe_rhs_depth = self.pipe_rhs_depth.saturating_add(1);
2255 let stage = self.parse_thread_stage_with_block(&func_name, stage_line)?;
2256 self.pipe_rhs_depth = self.pipe_rhs_depth.saturating_sub(1);
2257 result = self.pipe_forward_apply(result, stage, stage_line)?;
2258 } else if matches!(self.peek(), Token::LParen) {
2259 if func_name == "join" {
2262 self.advance(); let separator = self.parse_assign_expr()?;
2264 self.expect(&Token::RParen)?;
2265 let placeholder = self.pipe_placeholder_list(stage_line);
2266 let stage = Expr {
2267 kind: ExprKind::JoinExpr {
2268 separator: Box::new(separator),
2269 list: Box::new(placeholder),
2270 },
2271 line: stage_line,
2272 };
2273 result = self.pipe_forward_apply(result, stage, stage_line)?;
2274 } else if func_name == "split" {
2275 self.advance(); let pattern = self.parse_assign_expr()?;
2277 let limit = if self.eat(&Token::Comma) {
2278 Some(Box::new(self.parse_assign_expr()?))
2279 } else {
2280 None
2281 };
2282 self.expect(&Token::RParen)?;
2283 let placeholder = Expr {
2284 kind: ExprKind::ScalarVar("_".to_string()),
2285 line: stage_line,
2286 };
2287 let stage = Expr {
2288 kind: ExprKind::SplitExpr {
2289 pattern: Box::new(pattern),
2290 string: Box::new(placeholder),
2291 limit,
2292 },
2293 line: stage_line,
2294 };
2295 result = self.pipe_forward_apply(result, stage, stage_line)?;
2296 } else {
2297 self.advance(); let mut call_args = Vec::new();
2308 while !matches!(self.peek(), Token::RParen | Token::Eof) {
2309 call_args.push(self.parse_assign_expr()?);
2310 if !self.eat(&Token::Comma) {
2311 break;
2312 }
2313 }
2314 self.expect(&Token::RParen)?;
2315 if !call_args.iter().any(Self::expr_contains_topic_var) {
2319 let topic = Expr {
2320 kind: ExprKind::ScalarVar("_".to_string()),
2321 line: stage_line,
2322 };
2323 if self.thread_last_mode {
2324 call_args.push(topic);
2325 } else {
2326 call_args.insert(0, topic);
2327 }
2328 }
2329 let call_expr = Expr {
2330 kind: ExprKind::FuncCall {
2331 name: func_name.clone(),
2332 args: call_args,
2333 },
2334 line: stage_line,
2335 };
2336 let code_ref = Expr {
2337 kind: ExprKind::CodeRef {
2338 params: vec![],
2339 body: vec![Statement {
2340 label: None,
2341 kind: StmtKind::Expression(call_expr),
2342 line: stage_line,
2343 }],
2344 },
2345 line: stage_line,
2346 };
2347 result = self.pipe_forward_apply(result, code_ref, stage_line)?;
2348 }
2349 } else {
2350 result = self.thread_apply_bare_func(&func_name, result, stage_line)?;
2352 }
2353 }
2354 Token::Regex(ref pattern, ref flags, delim) => {
2356 let pattern = pattern.clone();
2357 let flags = flags.clone();
2358 self.advance();
2359 result =
2360 self.thread_regex_grep_stage(result, pattern, flags, delim, stage_line);
2361 }
2362 Token::Slash => {
2365 self.advance(); if let Token::Ident(ref ident_s) = self.peek().clone() {
2371 if matches!(ident_s.as_str(), "m" | "s" | "tr" | "y" | "qr")
2372 && matches!(self.peek_at(1), Token::Regex(..))
2373 {
2374 self.advance(); if let Token::Regex(ref misparsed_pattern, ref misparsed_flags, _) =
2381 self.peek().clone()
2382 {
2383 let _ = (misparsed_pattern, misparsed_flags);
2393 }
2394 }
2395 }
2396
2397 let mut pattern = String::new();
2399 loop {
2400 match self.peek().clone() {
2401 Token::Slash => {
2402 self.advance(); break;
2404 }
2405 Token::Eof | Token::Semicolon | Token::Newline => {
2406 return Err(self
2407 .syntax_err("Unterminated regex in thread stage", stage_line));
2408 }
2409 Token::Regex(ref inner_pattern, ref inner_flags, delim) => {
2411 if pattern.is_empty()
2419 || matches!(pattern.as_str(), "m" | "s" | "tr" | "y" | "qr")
2420 {
2421 let _ = (inner_pattern, inner_flags, delim);
2427 }
2428 return Err(self.syntax_err(
2430 "Complex regex in thread stage - use m/pattern/ syntax instead",
2431 stage_line,
2432 ));
2433 }
2434 Token::Ident(ref s) => {
2435 pattern.push_str(s);
2436 self.advance();
2437 }
2438 Token::Integer(n) => {
2439 pattern.push_str(&n.to_string());
2440 self.advance();
2441 }
2442 Token::ScalarVar(ref v) => {
2443 pattern.push('$');
2444 pattern.push_str(v);
2445 self.advance();
2446 }
2447 Token::Dot => {
2448 pattern.push('.');
2449 self.advance();
2450 }
2451 Token::Star => {
2452 pattern.push('*');
2453 self.advance();
2454 }
2455 Token::Plus => {
2456 pattern.push('+');
2457 self.advance();
2458 }
2459 Token::Question => {
2460 pattern.push('?');
2461 self.advance();
2462 }
2463 Token::LParen => {
2464 pattern.push('(');
2465 self.advance();
2466 }
2467 Token::RParen => {
2468 pattern.push(')');
2469 self.advance();
2470 }
2471 Token::LBracket => {
2472 pattern.push('[');
2473 self.advance();
2474 }
2475 Token::RBracket => {
2476 pattern.push(']');
2477 self.advance();
2478 }
2479 Token::Backslash => {
2480 pattern.push('\\');
2481 self.advance();
2482 }
2483 Token::BitOr => {
2484 pattern.push('|');
2485 self.advance();
2486 }
2487 Token::Power => {
2488 pattern.push_str("**");
2489 self.advance();
2490 }
2491 Token::BitXor => {
2492 pattern.push('^');
2493 self.advance();
2494 }
2495 Token::Minus => {
2496 pattern.push('-');
2497 self.advance();
2498 }
2499 _ => {
2500 return Err(self.syntax_err(
2501 format!("Unexpected token in regex pattern: {:?}", self.peek()),
2502 stage_line,
2503 ));
2504 }
2505 }
2506 }
2507 let mut flags = String::new();
2511 if let Token::Ident(ref s) = self.peek().clone() {
2512 let is_flag_only =
2513 s.chars().all(|c| "gimsxecor".contains(c)) && s.len() <= 6;
2514 let followed_by_brace = matches!(self.peek_at(1), Token::LBrace);
2515 if is_flag_only && !followed_by_brace {
2516 flags.push_str(s);
2517 self.advance();
2518 }
2519 }
2520 result = self.thread_regex_grep_stage(result, pattern, flags, '/', stage_line);
2521 }
2522 tok => {
2523 return Err(self.syntax_err(
2524 format!(
2525 "thread: expected stage (ident, fn {{}}, s///, tr///, or /re/), got {:?}",
2526 tok
2527 ),
2528 stage_line,
2529 ));
2530 }
2531 };
2532 last_stage_end_line = self.prev_line();
2533 }
2534
2535 self.thread_last_mode = saved_thread_last;
2537
2538 if pipe_rhs_wrap {
2539 let body_line = result.line;
2542 return Ok(Expr {
2543 kind: ExprKind::CodeRef {
2544 params: vec![],
2545 body: vec![Statement {
2546 label: None,
2547 kind: StmtKind::Expression(result),
2548 line: body_line,
2549 }],
2550 },
2551 line: _line,
2552 });
2553 }
2554 Ok(result)
2555 }
2556
2557 fn thread_regex_grep_stage(
2559 &self,
2560 list: Expr,
2561 pattern: String,
2562 flags: String,
2563 delim: char,
2564 line: usize,
2565 ) -> Expr {
2566 let topic = Expr {
2567 kind: ExprKind::ScalarVar("_".to_string()),
2568 line,
2569 };
2570 let match_expr = Expr {
2571 kind: ExprKind::Match {
2572 expr: Box::new(topic),
2573 pattern,
2574 flags,
2575 scalar_g: false,
2576 delim,
2577 },
2578 line,
2579 };
2580 let block = vec![Statement {
2581 label: None,
2582 kind: StmtKind::Expression(match_expr),
2583 line,
2584 }];
2585 Expr {
2586 kind: ExprKind::GrepExpr {
2587 block,
2588 list: Box::new(list),
2589 keyword: crate::ast::GrepBuiltinKeyword::Grep,
2590 },
2591 line,
2592 }
2593 }
2594
2595 fn expr_contains_topic_var(e: &Expr) -> bool {
2606 format!("{:?}", e).contains("ScalarVar(\"_\")")
2607 }
2608
2609 fn thread_apply_bare_func(&self, name: &str, arg: Expr, line: usize) -> PerlResult<Expr> {
2611 let kind = match name {
2612 "uc" => ExprKind::Uc(Box::new(arg)),
2614 "lc" => ExprKind::Lc(Box::new(arg)),
2615 "ucfirst" | "ufc" => ExprKind::Ucfirst(Box::new(arg)),
2616 "lcfirst" | "lfc" => ExprKind::Lcfirst(Box::new(arg)),
2617 "fc" => ExprKind::Fc(Box::new(arg)),
2618 "chomp" => ExprKind::Chomp(Box::new(arg)),
2619 "chop" => ExprKind::Chop(Box::new(arg)),
2620 "length" => ExprKind::Length(Box::new(arg)),
2621 "len" | "cnt" => ExprKind::FuncCall {
2622 name: "count".to_string(),
2623 args: vec![arg],
2624 },
2625 "quotemeta" | "qm" => ExprKind::FuncCall {
2626 name: "quotemeta".to_string(),
2627 args: vec![arg],
2628 },
2629 "abs" => ExprKind::Abs(Box::new(arg)),
2631 "int" => ExprKind::Int(Box::new(arg)),
2632 "sqrt" | "sq" => ExprKind::Sqrt(Box::new(arg)),
2633 "sin" => ExprKind::Sin(Box::new(arg)),
2634 "cos" => ExprKind::Cos(Box::new(arg)),
2635 "exp" => ExprKind::Exp(Box::new(arg)),
2636 "log" => ExprKind::Log(Box::new(arg)),
2637 "hex" => ExprKind::Hex(Box::new(arg)),
2638 "oct" => ExprKind::Oct(Box::new(arg)),
2639 "chr" => ExprKind::Chr(Box::new(arg)),
2640 "ord" => ExprKind::Ord(Box::new(arg)),
2641 "defined" | "def" => ExprKind::Defined(Box::new(arg)),
2643 "ref" => ExprKind::Ref(Box::new(arg)),
2644 "scalar" => {
2645 if crate::no_interop_mode() {
2646 return Err(self.syntax_err(
2647 "stryke uses `len` (also `cnt` / `count`) instead of `scalar` (--no-interop)",
2648 line,
2649 ));
2650 }
2651 ExprKind::ScalarContext(Box::new(arg))
2652 }
2653 "keys" => ExprKind::Keys(Box::new(arg)),
2655 "values" => ExprKind::Values(Box::new(arg)),
2656 "each" => ExprKind::Each(Box::new(arg)),
2657 "pop" => ExprKind::Pop(Box::new(arg)),
2658 "shift" => ExprKind::Shift(Box::new(arg)),
2659 "reverse" => {
2660 if crate::no_interop_mode() {
2661 return Err(self.syntax_err(
2662 "stryke uses `rev` instead of `reverse` (--no-interop)",
2663 line,
2664 ));
2665 }
2666 ExprKind::ReverseExpr(Box::new(arg))
2667 }
2668 "reversed" | "rv" | "rev" => ExprKind::Rev(Box::new(arg)),
2669 "sort" | "so" => ExprKind::SortExpr {
2670 cmp: None,
2671 list: Box::new(arg),
2672 },
2673 "psort" => ExprKind::PSortExpr {
2674 cmp: None,
2675 list: Box::new(arg),
2676 progress: None,
2677 },
2678 "uniq" | "distinct" | "uq" => ExprKind::FuncCall {
2679 name: "uniq".to_string(),
2680 args: vec![arg],
2681 },
2682 "trim" | "tm" => ExprKind::FuncCall {
2683 name: "trim".to_string(),
2684 args: vec![arg],
2685 },
2686 "flatten" | "fl" => ExprKind::FuncCall {
2687 name: "flatten".to_string(),
2688 args: vec![arg],
2689 },
2690 "compact" | "cpt" => ExprKind::FuncCall {
2691 name: "compact".to_string(),
2692 args: vec![arg],
2693 },
2694 "shuffle" | "shuf" => ExprKind::FuncCall {
2695 name: "shuffle".to_string(),
2696 args: vec![arg],
2697 },
2698 "frequencies" | "freq" | "frq" => ExprKind::FuncCall {
2699 name: "frequencies".to_string(),
2700 args: vec![arg],
2701 },
2702 "dedup" | "dup" => ExprKind::FuncCall {
2703 name: "dedup".to_string(),
2704 args: vec![arg],
2705 },
2706 "enumerate" | "en" => ExprKind::FuncCall {
2707 name: "enumerate".to_string(),
2708 args: vec![arg],
2709 },
2710 "lines" | "ln" => ExprKind::FuncCall {
2711 name: "lines".to_string(),
2712 args: vec![arg],
2713 },
2714 "words" | "wd" => ExprKind::FuncCall {
2715 name: "words".to_string(),
2716 args: vec![arg],
2717 },
2718 "chars" | "ch" => ExprKind::FuncCall {
2719 name: "chars".to_string(),
2720 args: vec![arg],
2721 },
2722 "digits" | "dg" => ExprKind::FuncCall {
2723 name: "digits".to_string(),
2724 args: vec![arg],
2725 },
2726 "letters" | "lts" => ExprKind::FuncCall {
2727 name: "letters".to_string(),
2728 args: vec![arg],
2729 },
2730 "letters_uc" => ExprKind::FuncCall {
2731 name: "letters_uc".to_string(),
2732 args: vec![arg],
2733 },
2734 "letters_lc" => ExprKind::FuncCall {
2735 name: "letters_lc".to_string(),
2736 args: vec![arg],
2737 },
2738 "punctuation" | "punct" => ExprKind::FuncCall {
2739 name: "punctuation".to_string(),
2740 args: vec![arg],
2741 },
2742 "sentences" | "sents" => ExprKind::FuncCall {
2743 name: "sentences".to_string(),
2744 args: vec![arg],
2745 },
2746 "paragraphs" | "paras" => ExprKind::FuncCall {
2747 name: "paragraphs".to_string(),
2748 args: vec![arg],
2749 },
2750 "sections" | "sects" => ExprKind::FuncCall {
2751 name: "sections".to_string(),
2752 args: vec![arg],
2753 },
2754 "numbers" | "nums" => ExprKind::FuncCall {
2755 name: "numbers".to_string(),
2756 args: vec![arg],
2757 },
2758 "graphemes" | "grs" => ExprKind::FuncCall {
2759 name: "graphemes".to_string(),
2760 args: vec![arg],
2761 },
2762 "columns" | "cols" => ExprKind::FuncCall {
2763 name: "columns".to_string(),
2764 args: vec![arg],
2765 },
2766 "slurp" | "sl" => ExprKind::Slurp(Box::new(arg)),
2768 "chdir" => ExprKind::Chdir(Box::new(arg)),
2769 "stat" => ExprKind::Stat(Box::new(arg)),
2770 "lstat" => ExprKind::Lstat(Box::new(arg)),
2771 "readlink" => ExprKind::Readlink(Box::new(arg)),
2772 "readdir" => ExprKind::Readdir(Box::new(arg)),
2773 "close" => ExprKind::Close(Box::new(arg)),
2774 "basename" | "bn" => ExprKind::FuncCall {
2775 name: "basename".to_string(),
2776 args: vec![arg],
2777 },
2778 "dirname" | "dn" => ExprKind::FuncCall {
2779 name: "dirname".to_string(),
2780 args: vec![arg],
2781 },
2782 "realpath" | "rp" => ExprKind::FuncCall {
2783 name: "realpath".to_string(),
2784 args: vec![arg],
2785 },
2786 "which" | "wh" => ExprKind::FuncCall {
2787 name: "which".to_string(),
2788 args: vec![arg],
2789 },
2790 "eval" => ExprKind::Eval(Box::new(arg)),
2792 "require" => ExprKind::Require(Box::new(arg)),
2793 "study" => ExprKind::Study(Box::new(arg)),
2794 "snake_case" | "sc" => ExprKind::FuncCall {
2796 name: "snake_case".to_string(),
2797 args: vec![arg],
2798 },
2799 "camel_case" | "cc" => ExprKind::FuncCall {
2800 name: "camel_case".to_string(),
2801 args: vec![arg],
2802 },
2803 "kebab_case" | "kc" => ExprKind::FuncCall {
2804 name: "kebab_case".to_string(),
2805 args: vec![arg],
2806 },
2807 "to_json" | "tj" => ExprKind::FuncCall {
2809 name: "to_json".to_string(),
2810 args: vec![arg],
2811 },
2812 "to_yaml" | "ty" => ExprKind::FuncCall {
2813 name: "to_yaml".to_string(),
2814 args: vec![arg],
2815 },
2816 "to_toml" | "tt" => ExprKind::FuncCall {
2817 name: "to_toml".to_string(),
2818 args: vec![arg],
2819 },
2820 "to_csv" | "tc" => ExprKind::FuncCall {
2821 name: "to_csv".to_string(),
2822 args: vec![arg],
2823 },
2824 "to_xml" | "tx" => ExprKind::FuncCall {
2825 name: "to_xml".to_string(),
2826 args: vec![arg],
2827 },
2828 "to_html" | "th" => ExprKind::FuncCall {
2829 name: "to_html".to_string(),
2830 args: vec![arg],
2831 },
2832 "to_markdown" | "to_md" | "tmd" => ExprKind::FuncCall {
2833 name: "to_markdown".to_string(),
2834 args: vec![arg],
2835 },
2836 "xopen" | "xo" => ExprKind::FuncCall {
2837 name: "xopen".to_string(),
2838 args: vec![arg],
2839 },
2840 "clip" | "clipboard" | "pbcopy" => ExprKind::FuncCall {
2841 name: "clip".to_string(),
2842 args: vec![arg],
2843 },
2844 "to_table" | "table" | "tbl" => ExprKind::FuncCall {
2845 name: "to_table".to_string(),
2846 args: vec![arg],
2847 },
2848 "sparkline" | "spark" => ExprKind::FuncCall {
2849 name: "sparkline".to_string(),
2850 args: vec![arg],
2851 },
2852 "bar_chart" | "bars" => ExprKind::FuncCall {
2853 name: "bar_chart".to_string(),
2854 args: vec![arg],
2855 },
2856 "flame" | "flamechart" => ExprKind::FuncCall {
2857 name: "flame".to_string(),
2858 args: vec![arg],
2859 },
2860 "ddump" | "dd" => ExprKind::FuncCall {
2861 name: "ddump".to_string(),
2862 args: vec![arg],
2863 },
2864 "say" => {
2865 if crate::no_interop_mode() {
2866 return Err(
2867 self.syntax_err("stryke uses `p` instead of `say` (--no-interop)", line)
2868 );
2869 }
2870 ExprKind::Say {
2871 handle: None,
2872 args: vec![arg],
2873 }
2874 }
2875 "p" => ExprKind::Say {
2876 handle: None,
2877 args: vec![arg],
2878 },
2879 "print" => ExprKind::Print {
2880 handle: None,
2881 args: vec![arg],
2882 },
2883 "warn" => ExprKind::Warn(vec![arg]),
2884 "die" => ExprKind::Die(vec![arg]),
2885 "stringify" | "str" => ExprKind::FuncCall {
2886 name: "stringify".to_string(),
2887 args: vec![arg],
2888 },
2889 "json_decode" | "jd" => ExprKind::FuncCall {
2890 name: "json_decode".to_string(),
2891 args: vec![arg],
2892 },
2893 "yaml_decode" | "yd" => ExprKind::FuncCall {
2894 name: "yaml_decode".to_string(),
2895 args: vec![arg],
2896 },
2897 "toml_decode" | "td" => ExprKind::FuncCall {
2898 name: "toml_decode".to_string(),
2899 args: vec![arg],
2900 },
2901 "xml_decode" | "xd" => ExprKind::FuncCall {
2902 name: "xml_decode".to_string(),
2903 args: vec![arg],
2904 },
2905 "json_encode" | "je" => ExprKind::FuncCall {
2906 name: "json_encode".to_string(),
2907 args: vec![arg],
2908 },
2909 "yaml_encode" | "ye" => ExprKind::FuncCall {
2910 name: "yaml_encode".to_string(),
2911 args: vec![arg],
2912 },
2913 "toml_encode" | "te" => ExprKind::FuncCall {
2914 name: "toml_encode".to_string(),
2915 args: vec![arg],
2916 },
2917 "xml_encode" | "xe" => ExprKind::FuncCall {
2918 name: "xml_encode".to_string(),
2919 args: vec![arg],
2920 },
2921 "base64_encode" | "b64e" => ExprKind::FuncCall {
2923 name: "base64_encode".to_string(),
2924 args: vec![arg],
2925 },
2926 "base64_decode" | "b64d" => ExprKind::FuncCall {
2927 name: "base64_decode".to_string(),
2928 args: vec![arg],
2929 },
2930 "hex_encode" | "hxe" => ExprKind::FuncCall {
2931 name: "hex_encode".to_string(),
2932 args: vec![arg],
2933 },
2934 "hex_decode" | "hxd" => ExprKind::FuncCall {
2935 name: "hex_decode".to_string(),
2936 args: vec![arg],
2937 },
2938 "url_encode" | "uri_escape" | "ue" => ExprKind::FuncCall {
2939 name: "url_encode".to_string(),
2940 args: vec![arg],
2941 },
2942 "url_decode" | "uri_unescape" | "ud" => ExprKind::FuncCall {
2943 name: "url_decode".to_string(),
2944 args: vec![arg],
2945 },
2946 "gzip" | "gz" => ExprKind::FuncCall {
2947 name: "gzip".to_string(),
2948 args: vec![arg],
2949 },
2950 "gunzip" | "ugz" => ExprKind::FuncCall {
2951 name: "gunzip".to_string(),
2952 args: vec![arg],
2953 },
2954 "zstd" | "zst" => ExprKind::FuncCall {
2955 name: "zstd".to_string(),
2956 args: vec![arg],
2957 },
2958 "zstd_decode" | "uzst" => ExprKind::FuncCall {
2959 name: "zstd_decode".to_string(),
2960 args: vec![arg],
2961 },
2962 "sha256" | "s256" => ExprKind::FuncCall {
2964 name: "sha256".to_string(),
2965 args: vec![arg],
2966 },
2967 "sha1" | "s1" => ExprKind::FuncCall {
2968 name: "sha1".to_string(),
2969 args: vec![arg],
2970 },
2971 "md5" | "m5" => ExprKind::FuncCall {
2972 name: "md5".to_string(),
2973 args: vec![arg],
2974 },
2975 "uuid" | "uid" => ExprKind::FuncCall {
2976 name: "uuid".to_string(),
2977 args: vec![arg],
2978 },
2979 "datetime_utc" | "utc" => ExprKind::FuncCall {
2981 name: "datetime_utc".to_string(),
2982 args: vec![arg],
2983 },
2984 "e" | "fore" | "ep" => ExprKind::ForEachExpr {
2987 block: vec![Statement {
2988 label: None,
2989 kind: StmtKind::Expression(Expr {
2990 kind: ExprKind::Say {
2991 handle: None,
2992 args: vec![Expr {
2993 kind: ExprKind::ScalarVar("_".into()),
2994 line,
2995 }],
2996 },
2997 line,
2998 }),
2999 line,
3000 }],
3001 list: Box::new(arg),
3002 },
3003 _ => ExprKind::FuncCall {
3005 name: name.to_string(),
3006 args: vec![arg],
3007 },
3008 };
3009 Ok(Expr { kind, line })
3010 }
3011
3012 fn parse_thread_stage_with_block(&mut self, name: &str, line: usize) -> PerlResult<Expr> {
3015 let block = self.parse_block()?;
3016 let placeholder = self.pipe_placeholder_list(line);
3018
3019 match name {
3020 "map" | "flat_map" | "maps" | "flat_maps" => {
3021 let flatten_array_refs = matches!(name, "flat_map" | "flat_maps");
3022 let stream = matches!(name, "maps" | "flat_maps");
3023 Ok(Expr {
3024 kind: ExprKind::MapExpr {
3025 block,
3026 list: Box::new(placeholder),
3027 flatten_array_refs,
3028 stream,
3029 },
3030 line,
3031 })
3032 }
3033 "grep" | "greps" | "filter" | "fi" | "find_all" | "gr" => {
3034 let keyword = match name {
3035 "grep" | "gr" => crate::ast::GrepBuiltinKeyword::Grep,
3036 "greps" => crate::ast::GrepBuiltinKeyword::Greps,
3037 "filter" | "fi" => crate::ast::GrepBuiltinKeyword::Filter,
3038 "find_all" => crate::ast::GrepBuiltinKeyword::FindAll,
3039 _ => unreachable!(),
3040 };
3041 Ok(Expr {
3042 kind: ExprKind::GrepExpr {
3043 block,
3044 list: Box::new(placeholder),
3045 keyword,
3046 },
3047 line,
3048 })
3049 }
3050 "sort" | "so" => Ok(Expr {
3051 kind: ExprKind::SortExpr {
3052 cmp: Some(SortComparator::Block(block)),
3053 list: Box::new(placeholder),
3054 },
3055 line,
3056 }),
3057 "reduce" | "rd" => Ok(Expr {
3058 kind: ExprKind::ReduceExpr {
3059 block,
3060 list: Box::new(placeholder),
3061 },
3062 line,
3063 }),
3064 "fore" | "e" | "ep" => Ok(Expr {
3065 kind: ExprKind::ForEachExpr {
3066 block,
3067 list: Box::new(placeholder),
3068 },
3069 line,
3070 }),
3071 "pmap" | "pflat_map" | "pmaps" | "pflat_maps" => Ok(Expr {
3072 kind: ExprKind::PMapExpr {
3073 block,
3074 list: Box::new(placeholder),
3075 progress: None,
3076 flat_outputs: name == "pflat_map" || name == "pflat_maps",
3077 on_cluster: None,
3078 stream: name == "pmaps" || name == "pflat_maps",
3079 },
3080 line,
3081 }),
3082 "pgrep" | "pgreps" => Ok(Expr {
3083 kind: ExprKind::PGrepExpr {
3084 block,
3085 list: Box::new(placeholder),
3086 progress: None,
3087 stream: name == "pgreps",
3088 },
3089 line,
3090 }),
3091 "pfor" => Ok(Expr {
3092 kind: ExprKind::PForExpr {
3093 block,
3094 list: Box::new(placeholder),
3095 progress: None,
3096 },
3097 line,
3098 }),
3099 "preduce" => Ok(Expr {
3100 kind: ExprKind::PReduceExpr {
3101 block,
3102 list: Box::new(placeholder),
3103 progress: None,
3104 },
3105 line,
3106 }),
3107 "pcache" => Ok(Expr {
3108 kind: ExprKind::PcacheExpr {
3109 block,
3110 list: Box::new(placeholder),
3111 progress: None,
3112 },
3113 line,
3114 }),
3115 "psort" => Ok(Expr {
3116 kind: ExprKind::PSortExpr {
3117 cmp: Some(block),
3118 list: Box::new(placeholder),
3119 progress: None,
3120 },
3121 line,
3122 }),
3123 _ => {
3124 let code_ref = Expr {
3131 kind: ExprKind::CodeRef {
3132 params: vec![],
3133 body: block,
3134 },
3135 line,
3136 };
3137 let args = if Self::is_block_then_list_pipe_builtin(name) {
3138 vec![code_ref, placeholder]
3139 } else {
3140 vec![code_ref]
3141 };
3142 Ok(Expr {
3143 kind: ExprKind::FuncCall {
3144 name: name.to_string(),
3145 args,
3146 },
3147 line,
3148 })
3149 }
3150 }
3151 }
3152
3153 fn parse_tie_stmt(&mut self) -> PerlResult<Statement> {
3155 let line = self.peek_line();
3156 self.advance(); let target = match self.peek().clone() {
3158 Token::HashVar(h) => {
3159 self.advance();
3160 TieTarget::Hash(h)
3161 }
3162 Token::ArrayVar(a) => {
3163 self.advance();
3164 TieTarget::Array(a)
3165 }
3166 Token::ScalarVar(s) => {
3167 self.advance();
3168 TieTarget::Scalar(s)
3169 }
3170 tok => {
3171 return Err(self.syntax_err(
3172 format!("tie expects $scalar, @array, or %hash, got {:?}", tok),
3173 self.peek_line(),
3174 ));
3175 }
3176 };
3177 self.expect(&Token::Comma)?;
3178 let class = self.parse_assign_expr()?;
3179 let mut args = Vec::new();
3180 while self.eat(&Token::Comma) {
3181 if matches!(self.peek(), Token::Semicolon | Token::RBrace | Token::Eof) {
3182 break;
3183 }
3184 args.push(self.parse_assign_expr()?);
3185 }
3186 self.eat(&Token::Semicolon);
3187 Ok(Statement {
3188 label: None,
3189 kind: StmtKind::Tie {
3190 target,
3191 class,
3192 args,
3193 },
3194 line,
3195 })
3196 }
3197
3198 fn parse_given(&mut self) -> PerlResult<Statement> {
3200 let line = self.peek_line();
3201 self.advance();
3202 self.expect(&Token::LParen)?;
3203 let topic = self.parse_expression()?;
3204 self.expect(&Token::RParen)?;
3205 let body = self.parse_block()?;
3206 self.eat(&Token::Semicolon);
3207 Ok(Statement {
3208 label: None,
3209 kind: StmtKind::Given { topic, body },
3210 line,
3211 })
3212 }
3213
3214 fn parse_when_stmt(&mut self) -> PerlResult<Statement> {
3216 let line = self.peek_line();
3217 self.advance();
3218 self.expect(&Token::LParen)?;
3219 let cond = self.parse_expression()?;
3220 self.expect(&Token::RParen)?;
3221 let body = self.parse_block()?;
3222 self.eat(&Token::Semicolon);
3223 Ok(Statement {
3224 label: None,
3225 kind: StmtKind::When { cond, body },
3226 line,
3227 })
3228 }
3229
3230 fn parse_default_stmt(&mut self) -> PerlResult<Statement> {
3232 let line = self.peek_line();
3233 self.advance();
3234 let body = self.parse_block()?;
3235 self.eat(&Token::Semicolon);
3236 Ok(Statement {
3237 label: None,
3238 kind: StmtKind::DefaultCase { body },
3239 line,
3240 })
3241 }
3242
3243 fn parse_cond_expr(&mut self, line: usize) -> PerlResult<Expr> {
3249 self.expect(&Token::LBrace)?;
3250
3251 let mut arms: Vec<(Expr, Block)> = Vec::new();
3252 let mut else_block: Option<Block> = None;
3253
3254 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
3255 let arm_line = self.peek_line();
3256
3257 let is_default = matches!(self.peek(), Token::Ident(ref s) if s == "default")
3259 && matches!(self.peek_at(1), Token::FatArrow);
3260
3261 if is_default {
3262 self.advance(); self.advance(); let body = if matches!(self.peek(), Token::LBrace) {
3265 self.parse_block()?
3266 } else {
3267 let expr = self.parse_assign_expr()?;
3268 vec![Statement {
3269 label: None,
3270 kind: StmtKind::Expression(expr),
3271 line: arm_line,
3272 }]
3273 };
3274 else_block = Some(body);
3275 self.eat(&Token::Comma);
3276 break; }
3278
3279 let condition = self.parse_assign_expr()?;
3281 self.expect(&Token::FatArrow)?;
3282
3283 let body = if matches!(self.peek(), Token::LBrace) {
3284 self.parse_block()?
3285 } else {
3286 let expr = self.parse_assign_expr()?;
3287 vec![Statement {
3288 label: None,
3289 kind: StmtKind::Expression(expr),
3290 line: arm_line,
3291 }]
3292 };
3293
3294 arms.push((condition, body));
3295 self.eat(&Token::Comma);
3296 }
3297
3298 self.expect(&Token::RBrace)?;
3299
3300 if arms.is_empty() {
3301 return Err(self.syntax_err("cond requires at least one condition arm", line));
3302 }
3303
3304 let (first_cond, first_body) = arms.remove(0);
3306 let elsifs: Vec<(Expr, Block)> = arms;
3307
3308 let if_stmt = Statement {
3310 label: None,
3311 kind: StmtKind::If {
3312 condition: first_cond,
3313 body: first_body,
3314 elsifs,
3315 else_block,
3316 },
3317 line,
3318 };
3319 let inner = Expr {
3320 kind: ExprKind::CodeRef {
3321 params: vec![],
3322 body: vec![if_stmt],
3323 },
3324 line,
3325 };
3326 Ok(Expr {
3327 kind: ExprKind::Do(Box::new(inner)),
3328 line,
3329 })
3330 }
3331
3332 fn parse_algebraic_match_expr(&mut self, line: usize) -> PerlResult<Expr> {
3334 self.expect(&Token::LParen)?;
3335 let subject = self.parse_expression()?;
3336 self.expect(&Token::RParen)?;
3337 self.expect(&Token::LBrace)?;
3338 let mut arms = Vec::new();
3339 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
3340 if self.eat(&Token::Semicolon) {
3341 continue;
3342 }
3343 let pattern = self.parse_match_pattern()?;
3344 let guard = if matches!(self.peek(), Token::Ident(ref s) if s == "if") {
3345 self.advance();
3346 Some(Box::new(self.parse_assign_expr()?))
3349 } else {
3350 None
3351 };
3352 self.expect(&Token::FatArrow)?;
3353 let body = self.parse_assign_expr()?;
3355 arms.push(MatchArm {
3356 pattern,
3357 guard,
3358 body,
3359 });
3360 self.eat(&Token::Comma);
3361 }
3362 self.expect(&Token::RBrace)?;
3363 Ok(Expr {
3364 kind: ExprKind::AlgebraicMatch {
3365 subject: Box::new(subject),
3366 arms,
3367 },
3368 line,
3369 })
3370 }
3371
3372 fn parse_match_pattern(&mut self) -> PerlResult<MatchPattern> {
3373 match self.peek().clone() {
3374 Token::Regex(pattern, flags, _delim) => {
3375 self.advance();
3376 Ok(MatchPattern::Regex { pattern, flags })
3377 }
3378 Token::Ident(ref s) if s == "_" => {
3379 self.advance();
3380 Ok(MatchPattern::Any)
3381 }
3382 Token::Ident(ref s) if s == "Some" => {
3383 self.advance();
3384 self.expect(&Token::LParen)?;
3385 let name = self.parse_scalar_var_name()?;
3386 self.expect(&Token::RParen)?;
3387 Ok(MatchPattern::OptionSome(name))
3388 }
3389 Token::LBracket => self.parse_match_array_pattern(),
3390 Token::LBrace => self.parse_match_hash_pattern(),
3391 Token::LParen => {
3392 self.advance();
3393 let e = self.parse_expression()?;
3394 self.expect(&Token::RParen)?;
3395 Ok(MatchPattern::Value(Box::new(e)))
3396 }
3397 _ => {
3398 let e = self.parse_assign_expr()?;
3399 Ok(MatchPattern::Value(Box::new(e)))
3400 }
3401 }
3402 }
3403
3404 fn parse_match_array_elems_until_rbracket(&mut self) -> PerlResult<Vec<MatchArrayElem>> {
3406 let mut elems = Vec::new();
3407 if self.eat(&Token::RBracket) {
3408 return Ok(vec![]);
3409 }
3410 loop {
3411 if matches!(self.peek(), Token::Star) {
3412 self.advance();
3413 elems.push(MatchArrayElem::Rest);
3414 self.eat(&Token::Comma);
3415 if !matches!(self.peek(), Token::RBracket) {
3416 return Err(self.syntax_err(
3417 "`*` must be the last element in an array match pattern",
3418 self.peek_line(),
3419 ));
3420 }
3421 self.expect(&Token::RBracket)?;
3422 return Ok(elems);
3423 }
3424 if let Token::ArrayVar(name) = self.peek().clone() {
3425 self.advance();
3426 elems.push(MatchArrayElem::RestBind(name));
3427 self.eat(&Token::Comma);
3428 if !matches!(self.peek(), Token::RBracket) {
3429 return Err(self.syntax_err(
3430 "`@name` rest bind must be the last element in an array match pattern",
3431 self.peek_line(),
3432 ));
3433 }
3434 self.expect(&Token::RBracket)?;
3435 return Ok(elems);
3436 }
3437 if let Token::ScalarVar(name) = self.peek().clone() {
3438 self.advance();
3439 elems.push(MatchArrayElem::CaptureScalar(name));
3440 if self.eat(&Token::Comma) {
3441 if matches!(self.peek(), Token::RBracket) {
3442 break;
3443 }
3444 continue;
3445 }
3446 break;
3447 }
3448 let e = self.parse_assign_expr()?;
3449 elems.push(MatchArrayElem::Expr(e));
3450 if self.eat(&Token::Comma) {
3451 if matches!(self.peek(), Token::RBracket) {
3452 break;
3453 }
3454 continue;
3455 }
3456 break;
3457 }
3458 self.expect(&Token::RBracket)?;
3459 Ok(elems)
3460 }
3461
3462 fn parse_match_array_pattern(&mut self) -> PerlResult<MatchPattern> {
3463 self.expect(&Token::LBracket)?;
3464 let elems = self.parse_match_array_elems_until_rbracket()?;
3465 Ok(MatchPattern::Array(elems))
3466 }
3467
3468 fn parse_match_hash_pattern(&mut self) -> PerlResult<MatchPattern> {
3469 self.expect(&Token::LBrace)?;
3470 let mut pairs = Vec::new();
3471 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
3472 if self.eat(&Token::Semicolon) {
3473 continue;
3474 }
3475 let key = self.parse_assign_expr()?;
3476 self.expect(&Token::FatArrow)?;
3477 match self.advance().0 {
3478 Token::Ident(ref s) if s == "_" => {
3479 pairs.push(MatchHashPair::KeyOnly { key });
3480 }
3481 Token::ScalarVar(name) => {
3482 pairs.push(MatchHashPair::Capture { key, name });
3483 }
3484 tok => {
3485 return Err(self.syntax_err(
3486 format!(
3487 "hash match pattern must bind with `=> $name` or `=> _`, got {:?}",
3488 tok
3489 ),
3490 self.peek_line(),
3491 ));
3492 }
3493 }
3494 self.eat(&Token::Comma);
3495 }
3496 self.expect(&Token::RBrace)?;
3497 Ok(MatchPattern::Hash(pairs))
3498 }
3499
3500 fn parse_eval_timeout(&mut self) -> PerlResult<Statement> {
3502 let line = self.peek_line();
3503 self.advance();
3504 let timeout = self.parse_postfix()?;
3505 let body = self.parse_block_or_bareword_block_no_args()?;
3506 self.eat(&Token::Semicolon);
3507 Ok(Statement {
3508 label: None,
3509 kind: StmtKind::EvalTimeout { timeout, body },
3510 line,
3511 })
3512 }
3513
3514 fn mark_match_scalar_g_for_boolean_condition(cond: &mut Expr) {
3515 match &mut cond.kind {
3516 ExprKind::Match {
3517 flags, scalar_g, ..
3518 } if flags.contains('g') => {
3519 *scalar_g = true;
3520 }
3521 ExprKind::UnaryOp {
3522 op: UnaryOp::LogNot,
3523 expr,
3524 } => {
3525 if let ExprKind::Match {
3526 flags, scalar_g, ..
3527 } = &mut expr.kind
3528 {
3529 if flags.contains('g') {
3530 *scalar_g = true;
3531 }
3532 }
3533 }
3534 _ => {}
3535 }
3536 }
3537
3538 fn parse_if(&mut self) -> PerlResult<Statement> {
3539 let line = self.peek_line();
3540 self.advance(); if matches!(self.peek(), Token::Ident(ref s) if s == "let") {
3542 if crate::compat_mode() {
3543 return Err(self.syntax_err(
3544 "`if let` is a stryke extension (disabled by --compat)",
3545 line,
3546 ));
3547 }
3548 return self.parse_if_let(line);
3549 }
3550 self.expect(&Token::LParen)?;
3551 let mut cond = self.parse_expression()?;
3552 Self::mark_match_scalar_g_for_boolean_condition(&mut cond);
3553 self.expect(&Token::RParen)?;
3554 let body = self.parse_block()?;
3555
3556 let mut elsifs = Vec::new();
3557 let mut else_block = None;
3558
3559 loop {
3560 if let Token::Ident(ref kw) = self.peek().clone() {
3561 if kw == "elsif" {
3562 self.advance();
3563 self.expect(&Token::LParen)?;
3564 let mut c = self.parse_expression()?;
3565 Self::mark_match_scalar_g_for_boolean_condition(&mut c);
3566 self.expect(&Token::RParen)?;
3567 let b = self.parse_block()?;
3568 elsifs.push((c, b));
3569 continue;
3570 }
3571 if kw == "else" {
3572 self.advance();
3573 else_block = Some(self.parse_block()?);
3574 }
3575 }
3576 break;
3577 }
3578
3579 Ok(Statement {
3580 label: None,
3581 kind: StmtKind::If {
3582 condition: cond,
3583 body,
3584 elsifs,
3585 else_block,
3586 },
3587 line,
3588 })
3589 }
3590
3591 fn parse_if_let(&mut self, line: usize) -> PerlResult<Statement> {
3593 self.advance(); let pattern = self.parse_match_pattern()?;
3595 self.expect(&Token::Assign)?;
3596 self.suppress_scalar_hash_brace = self.suppress_scalar_hash_brace.saturating_add(1);
3598 let rhs = self.parse_assign_expr();
3599 self.suppress_scalar_hash_brace = self.suppress_scalar_hash_brace.saturating_sub(1);
3600 let rhs = rhs?;
3601 let then_block = self.parse_block()?;
3602 let else_block_opt = match self.peek().clone() {
3603 Token::Ident(ref kw) if kw == "else" => {
3604 self.advance();
3605 Some(self.parse_block()?)
3606 }
3607 Token::Ident(ref kw) if kw == "elsif" => {
3608 return Err(self.syntax_err(
3609 "`if let` does not support `elsif`; use `else { }` or a full `match`",
3610 self.peek_line(),
3611 ));
3612 }
3613 _ => None,
3614 };
3615 let then_expr = Self::expr_do_anon_block(then_block, line);
3616 let else_expr = if let Some(eb) = else_block_opt {
3617 Self::expr_do_anon_block(eb, line)
3618 } else {
3619 Expr {
3620 kind: ExprKind::Undef,
3621 line,
3622 }
3623 };
3624 let arms = vec![
3625 MatchArm {
3626 pattern,
3627 guard: None,
3628 body: then_expr,
3629 },
3630 MatchArm {
3631 pattern: MatchPattern::Any,
3632 guard: None,
3633 body: else_expr,
3634 },
3635 ];
3636 Ok(Statement {
3637 label: None,
3638 kind: StmtKind::Expression(Expr {
3639 kind: ExprKind::AlgebraicMatch {
3640 subject: Box::new(rhs),
3641 arms,
3642 },
3643 line,
3644 }),
3645 line,
3646 })
3647 }
3648
3649 fn expr_do_anon_block(block: Block, outer_line: usize) -> Expr {
3650 let inner_line = block.first().map(|s| s.line).unwrap_or(outer_line);
3651 Expr {
3652 kind: ExprKind::Do(Box::new(Expr {
3653 kind: ExprKind::CodeRef {
3654 params: vec![],
3655 body: block,
3656 },
3657 line: inner_line,
3658 })),
3659 line: outer_line,
3660 }
3661 }
3662
3663 fn parse_unless(&mut self) -> PerlResult<Statement> {
3664 let line = self.peek_line();
3665 self.advance(); self.expect(&Token::LParen)?;
3667 let mut cond = self.parse_expression()?;
3668 Self::mark_match_scalar_g_for_boolean_condition(&mut cond);
3669 self.expect(&Token::RParen)?;
3670 let body = self.parse_block()?;
3671 let else_block = if let Token::Ident(ref kw) = self.peek().clone() {
3672 if kw == "else" {
3673 self.advance();
3674 Some(self.parse_block()?)
3675 } else {
3676 None
3677 }
3678 } else {
3679 None
3680 };
3681 Ok(Statement {
3682 label: None,
3683 kind: StmtKind::Unless {
3684 condition: cond,
3685 body,
3686 else_block,
3687 },
3688 line,
3689 })
3690 }
3691
3692 fn parse_while(&mut self) -> PerlResult<Statement> {
3693 let line = self.peek_line();
3694 self.advance(); if matches!(self.peek(), Token::Ident(ref s) if s == "let") {
3696 if crate::compat_mode() {
3697 return Err(self.syntax_err(
3698 "`while let` is a stryke extension (disabled by --compat)",
3699 line,
3700 ));
3701 }
3702 return self.parse_while_let(line);
3703 }
3704 self.expect(&Token::LParen)?;
3705 let mut cond = self.parse_expression()?;
3706 Self::mark_match_scalar_g_for_boolean_condition(&mut cond);
3707 self.expect(&Token::RParen)?;
3708 let body = self.parse_block()?;
3709 let continue_block = self.parse_optional_continue_block()?;
3710 Ok(Statement {
3711 label: None,
3712 kind: StmtKind::While {
3713 condition: cond,
3714 body,
3715 label: None,
3716 continue_block,
3717 },
3718 line,
3719 })
3720 }
3721
3722 fn parse_while_let(&mut self, line: usize) -> PerlResult<Statement> {
3725 self.advance(); let pattern = self.parse_match_pattern()?;
3727 self.expect(&Token::Assign)?;
3728 self.suppress_scalar_hash_brace = self.suppress_scalar_hash_brace.saturating_add(1);
3729 let rhs = self.parse_assign_expr();
3730 self.suppress_scalar_hash_brace = self.suppress_scalar_hash_brace.saturating_sub(1);
3731 let rhs = rhs?;
3732 let mut user_body = self.parse_block()?;
3733 let continue_block = self.parse_optional_continue_block()?;
3734 user_body.push(Statement::new(
3735 StmtKind::Expression(Expr {
3736 kind: ExprKind::Integer(1),
3737 line,
3738 }),
3739 line,
3740 ));
3741 let tmp = format!("__while_let_{}", self.alloc_desugar_tmp());
3742 let match_expr = Expr {
3743 kind: ExprKind::AlgebraicMatch {
3744 subject: Box::new(rhs),
3745 arms: vec![
3746 MatchArm {
3747 pattern,
3748 guard: None,
3749 body: Self::expr_do_anon_block(user_body, line),
3750 },
3751 MatchArm {
3752 pattern: MatchPattern::Any,
3753 guard: None,
3754 body: Expr {
3755 kind: ExprKind::Integer(0),
3756 line,
3757 },
3758 },
3759 ],
3760 },
3761 line,
3762 };
3763 let my_stmt = Statement::new(
3764 StmtKind::My(vec![VarDecl {
3765 sigil: Sigil::Scalar,
3766 name: tmp.clone(),
3767 initializer: Some(match_expr),
3768 frozen: false,
3769 type_annotation: None,
3770 }]),
3771 line,
3772 );
3773 let unless_last = Statement::new(
3774 StmtKind::Unless {
3775 condition: Expr {
3776 kind: ExprKind::ScalarVar(tmp),
3777 line,
3778 },
3779 body: vec![Statement::new(StmtKind::Last(None), line)],
3780 else_block: None,
3781 },
3782 line,
3783 );
3784 Ok(Statement::new(
3785 StmtKind::While {
3786 condition: Expr {
3787 kind: ExprKind::Integer(1),
3788 line,
3789 },
3790 body: vec![my_stmt, unless_last],
3791 label: None,
3792 continue_block,
3793 },
3794 line,
3795 ))
3796 }
3797
3798 fn parse_until(&mut self) -> PerlResult<Statement> {
3799 let line = self.peek_line();
3800 self.advance(); self.expect(&Token::LParen)?;
3802 let mut cond = self.parse_expression()?;
3803 Self::mark_match_scalar_g_for_boolean_condition(&mut cond);
3804 self.expect(&Token::RParen)?;
3805 let body = self.parse_block()?;
3806 let continue_block = self.parse_optional_continue_block()?;
3807 Ok(Statement {
3808 label: None,
3809 kind: StmtKind::Until {
3810 condition: cond,
3811 body,
3812 label: None,
3813 continue_block,
3814 },
3815 line,
3816 })
3817 }
3818
3819 fn parse_optional_continue_block(&mut self) -> PerlResult<Option<Block>> {
3821 if let Token::Ident(ref kw) = self.peek().clone() {
3822 if kw == "continue" {
3823 self.advance();
3824 return Ok(Some(self.parse_block()?));
3825 }
3826 }
3827 Ok(None)
3828 }
3829
3830 fn parse_for_or_foreach(&mut self) -> PerlResult<Statement> {
3831 let line = self.peek_line();
3832 self.advance(); match self.peek() {
3838 Token::LParen => {
3839 let saved = self.pos;
3844 self.advance(); let mut depth = 1;
3847 let mut has_semi = false;
3848 let mut scan = self.pos;
3849 while scan < self.tokens.len() {
3850 match &self.tokens[scan].0 {
3851 Token::LParen => depth += 1,
3852 Token::RParen => {
3853 depth -= 1;
3854 if depth == 0 {
3855 break;
3856 }
3857 }
3858 Token::Semicolon if depth == 1 => {
3859 has_semi = true;
3860 break;
3861 }
3862 _ => {}
3863 }
3864 scan += 1;
3865 }
3866 self.pos = saved;
3867
3868 if has_semi {
3869 self.parse_c_style_for(line)
3870 } else {
3871 self.expect(&Token::LParen)?;
3873 let list = self.parse_expression()?;
3874 self.expect(&Token::RParen)?;
3875 let body = self.parse_block()?;
3876 let continue_block = self.parse_optional_continue_block()?;
3877 Ok(Statement {
3878 label: None,
3879 kind: StmtKind::Foreach {
3880 var: "_".to_string(),
3881 list,
3882 body,
3883 label: None,
3884 continue_block,
3885 },
3886 line,
3887 })
3888 }
3889 }
3890 Token::Ident(ref kw) if kw == "my" => {
3891 self.advance(); let var = self.parse_scalar_var_name()?;
3893 self.expect(&Token::LParen)?;
3894 let list = self.parse_expression()?;
3895 self.expect(&Token::RParen)?;
3896 let body = self.parse_block()?;
3897 let continue_block = self.parse_optional_continue_block()?;
3898 Ok(Statement {
3899 label: None,
3900 kind: StmtKind::Foreach {
3901 var,
3902 list,
3903 body,
3904 label: None,
3905 continue_block,
3906 },
3907 line,
3908 })
3909 }
3910 Token::ScalarVar(_) => {
3911 let var = self.parse_scalar_var_name()?;
3912 self.expect(&Token::LParen)?;
3913 let list = self.parse_expression()?;
3914 self.expect(&Token::RParen)?;
3915 let body = self.parse_block()?;
3916 let continue_block = self.parse_optional_continue_block()?;
3917 Ok(Statement {
3918 label: None,
3919 kind: StmtKind::Foreach {
3920 var,
3921 list,
3922 body,
3923 label: None,
3924 continue_block,
3925 },
3926 line,
3927 })
3928 }
3929 _ => self.parse_c_style_for(line),
3930 }
3931 }
3932
3933 fn parse_c_style_for(&mut self, line: usize) -> PerlResult<Statement> {
3934 self.expect(&Token::LParen)?;
3935 let init = if self.eat(&Token::Semicolon) {
3936 None
3937 } else {
3938 let s = self.parse_statement()?;
3939 self.eat(&Token::Semicolon);
3940 Some(Box::new(s))
3941 };
3942 let mut condition = if matches!(self.peek(), Token::Semicolon) {
3943 None
3944 } else {
3945 Some(self.parse_expression()?)
3946 };
3947 if let Some(ref mut c) = condition {
3948 Self::mark_match_scalar_g_for_boolean_condition(c);
3949 }
3950 self.expect(&Token::Semicolon)?;
3951 let step = if matches!(self.peek(), Token::RParen) {
3952 None
3953 } else {
3954 Some(self.parse_expression()?)
3955 };
3956 self.expect(&Token::RParen)?;
3957 let body = self.parse_block()?;
3958 let continue_block = self.parse_optional_continue_block()?;
3959 Ok(Statement {
3960 label: None,
3961 kind: StmtKind::For {
3962 init,
3963 condition,
3964 step,
3965 body,
3966 label: None,
3967 continue_block,
3968 },
3969 line,
3970 })
3971 }
3972
3973 fn parse_foreach(&mut self) -> PerlResult<Statement> {
3974 let line = self.peek_line();
3975 self.advance(); let var = match self.peek() {
3977 Token::Ident(ref kw) if kw == "my" => {
3978 self.advance();
3979 self.parse_scalar_var_name()?
3980 }
3981 Token::ScalarVar(_) => self.parse_scalar_var_name()?,
3982 _ => "_".to_string(),
3983 };
3984 self.expect(&Token::LParen)?;
3985 let list = self.parse_expression()?;
3986 self.expect(&Token::RParen)?;
3987 let body = self.parse_block()?;
3988 let continue_block = self.parse_optional_continue_block()?;
3989 Ok(Statement {
3990 label: None,
3991 kind: StmtKind::Foreach {
3992 var,
3993 list,
3994 body,
3995 label: None,
3996 continue_block,
3997 },
3998 line,
3999 })
4000 }
4001
4002 fn parse_scalar_var_name(&mut self) -> PerlResult<String> {
4003 match self.advance() {
4004 (Token::ScalarVar(name), _) => Ok(name),
4005 (tok, line) => {
4006 Err(self.syntax_err(format!("Expected scalar variable, got {:?}", tok), line))
4007 }
4008 }
4009 }
4010
4011 fn parse_legacy_sub_prototype_tail(&mut self) -> PerlResult<String> {
4013 let mut s = String::new();
4014 loop {
4015 match self.peek().clone() {
4016 Token::RParen => {
4017 self.advance();
4018 break;
4019 }
4020 Token::Eof => {
4021 return Err(self.syntax_err(
4022 "Unterminated sub prototype (expected ')' before end of input)",
4023 self.peek_line(),
4024 ));
4025 }
4026 Token::ScalarVar(v) if v == ")" => {
4027 self.advance();
4030 s.push('$');
4031 if matches!(self.peek(), Token::LBrace) {
4032 break;
4033 }
4034 }
4035 Token::Ident(i) => {
4036 let i = i.clone();
4037 self.advance();
4038 s.push_str(&i);
4039 }
4040 Token::Semicolon => {
4041 self.advance();
4042 s.push(';');
4043 }
4044 Token::LParen => {
4045 self.advance();
4046 s.push('(');
4047 }
4048 Token::LBracket => {
4049 self.advance();
4050 s.push('[');
4051 }
4052 Token::RBracket => {
4053 self.advance();
4054 s.push(']');
4055 }
4056 Token::Backslash => {
4057 self.advance();
4058 s.push('\\');
4059 }
4060 Token::Comma => {
4061 self.advance();
4062 s.push(',');
4063 }
4064 Token::ScalarVar(v) => {
4065 let v = v.clone();
4066 self.advance();
4067 s.push('$');
4068 s.push_str(&v);
4069 }
4070 Token::ArrayVar(v) => {
4071 let v = v.clone();
4072 self.advance();
4073 s.push('@');
4074 s.push_str(&v);
4075 }
4076 Token::ArrayAt => {
4078 self.advance();
4079 s.push('@');
4080 }
4081 Token::HashVar(v) => {
4082 let v = v.clone();
4083 self.advance();
4084 s.push('%');
4085 s.push_str(&v);
4086 }
4087 Token::HashPercent => {
4088 self.advance();
4089 s.push('%');
4090 }
4091 Token::Plus => {
4092 self.advance();
4093 s.push('+');
4094 }
4095 Token::Minus => {
4096 self.advance();
4097 s.push('-');
4098 }
4099 Token::BitAnd => {
4100 self.advance();
4101 s.push('&');
4102 }
4103 tok => {
4104 return Err(self.syntax_err(
4105 format!("Unexpected token in sub prototype: {:?}", tok),
4106 self.peek_line(),
4107 ));
4108 }
4109 }
4110 }
4111 Ok(s)
4112 }
4113
4114 fn sub_signature_list_starts_here(&self) -> bool {
4115 match self.peek() {
4116 Token::LBrace | Token::LBracket => true,
4117 Token::ScalarVar(name) if name != "$$" && name != ")" => true,
4118 Token::ArrayVar(_) | Token::HashVar(_) => true,
4119 _ => false,
4120 }
4121 }
4122
4123 fn parse_sub_signature_hash_key(&mut self) -> PerlResult<String> {
4124 let (tok, line) = self.advance();
4125 match tok {
4126 Token::Ident(i) => Ok(i),
4127 Token::SingleString(s) | Token::DoubleString(s) => Ok(s),
4128 tok => Err(self.syntax_err(
4129 format!(
4130 "sub signature: expected hash key (identifier or string), got {:?}",
4131 tok
4132 ),
4133 line,
4134 )),
4135 }
4136 }
4137
4138 fn parse_sub_signature_param_list(&mut self) -> PerlResult<Vec<SubSigParam>> {
4139 let mut params = Vec::new();
4140 loop {
4141 if matches!(self.peek(), Token::RParen) {
4142 break;
4143 }
4144 match self.peek().clone() {
4145 Token::ScalarVar(name) => {
4146 if name == "$$" || name == ")" {
4147 return Err(self.syntax_err(
4148 format!(
4149 "`{name}` cannot start a stryke sub signature (use legacy prototype `($$)` etc.)"
4150 ),
4151 self.peek_line(),
4152 ));
4153 }
4154 self.advance();
4155 let ty = if self.eat(&Token::Colon) {
4156 match self.peek() {
4157 Token::Ident(ref tname) => {
4158 let tname = tname.clone();
4159 self.advance();
4160 Some(match tname.as_str() {
4161 "Int" => PerlTypeName::Int,
4162 "Str" => PerlTypeName::Str,
4163 "Float" => PerlTypeName::Float,
4164 "Bool" => PerlTypeName::Bool,
4165 "Array" => PerlTypeName::Array,
4166 "Hash" => PerlTypeName::Hash,
4167 "Ref" => PerlTypeName::Ref,
4168 "Any" => PerlTypeName::Any,
4169 _ => PerlTypeName::Struct(tname),
4170 })
4171 }
4172 _ => {
4173 return Err(self.syntax_err(
4174 "expected type name after `:` in sub signature",
4175 self.peek_line(),
4176 ));
4177 }
4178 }
4179 } else {
4180 None
4181 };
4182 let default = if self.eat(&Token::Assign) {
4184 Some(Box::new(self.parse_ternary()?))
4185 } else {
4186 None
4187 };
4188 params.push(SubSigParam::Scalar(name, ty, default));
4189 }
4190 Token::ArrayVar(name) => {
4191 self.advance();
4192 let default = if self.eat(&Token::Assign) {
4193 Some(Box::new(self.parse_ternary()?))
4194 } else {
4195 None
4196 };
4197 params.push(SubSigParam::Array(name, default));
4198 }
4199 Token::HashVar(name) => {
4200 self.advance();
4201 let default = if self.eat(&Token::Assign) {
4202 Some(Box::new(self.parse_ternary()?))
4203 } else {
4204 None
4205 };
4206 params.push(SubSigParam::Hash(name, default));
4207 }
4208 Token::LBracket => {
4209 self.advance();
4210 let elems = self.parse_match_array_elems_until_rbracket()?;
4211 params.push(SubSigParam::ArrayDestruct(elems));
4212 }
4213 Token::LBrace => {
4214 self.advance();
4215 let mut pairs = Vec::new();
4216 loop {
4217 if matches!(self.peek(), Token::RBrace | Token::Eof) {
4218 break;
4219 }
4220 if self.eat(&Token::Comma) {
4221 continue;
4222 }
4223 let key = self.parse_sub_signature_hash_key()?;
4224 self.expect(&Token::FatArrow)?;
4225 let bind = self.parse_scalar_var_name()?;
4226 pairs.push((key, bind));
4227 self.eat(&Token::Comma);
4228 }
4229 self.expect(&Token::RBrace)?;
4230 params.push(SubSigParam::HashDestruct(pairs));
4231 }
4232 tok => {
4233 return Err(self.syntax_err(
4234 format!(
4235 "expected `$name`, `[ ... ]`, or `{{ ... }}` in sub signature, got {:?}",
4236 tok
4237 ),
4238 self.peek_line(),
4239 ));
4240 }
4241 }
4242 match self.peek() {
4243 Token::Comma => {
4244 self.advance();
4245 if matches!(self.peek(), Token::RParen) {
4246 return Err(self.syntax_err(
4247 "trailing `,` before `)` in sub signature",
4248 self.peek_line(),
4249 ));
4250 }
4251 }
4252 Token::RParen => break,
4253 _ => {
4254 return Err(self.syntax_err(
4255 format!(
4256 "expected `,` or `)` after sub signature parameter, got {:?}",
4257 self.peek()
4258 ),
4259 self.peek_line(),
4260 ));
4261 }
4262 }
4263 }
4264 Ok(params)
4265 }
4266
4267 fn parse_sub_sig_or_prototype_opt(&mut self) -> PerlResult<(Vec<SubSigParam>, Option<String>)> {
4269 if !matches!(self.peek(), Token::LParen) {
4270 return Ok((vec![], None));
4271 }
4272 self.advance();
4273 if matches!(self.peek(), Token::RParen) {
4274 self.advance();
4275 return Ok((vec![], Some(String::new())));
4276 }
4277 if self.sub_signature_list_starts_here() {
4278 let params = self.parse_sub_signature_param_list()?;
4279 self.expect(&Token::RParen)?;
4280 return Ok((params, None));
4281 }
4282 let proto = self.parse_legacy_sub_prototype_tail()?;
4283 Ok((vec![], Some(proto)))
4284 }
4285
4286 fn parse_sub_attributes(&mut self) -> PerlResult<()> {
4288 while self.eat(&Token::Colon) {
4289 match self.advance() {
4290 (Token::Ident(_), _) => {}
4291 (tok, line) => {
4292 return Err(self.syntax_err(
4293 format!("Expected attribute name after `:`, got {:?}", tok),
4294 line,
4295 ));
4296 }
4297 }
4298 if self.eat(&Token::LParen) {
4299 let mut depth = 1usize;
4300 while depth > 0 {
4301 match self.advance().0 {
4302 Token::LParen => depth += 1,
4303 Token::RParen => {
4304 depth -= 1;
4305 }
4306 Token::Eof => {
4307 return Err(self.syntax_err(
4308 "Unterminated sub attribute argument list",
4309 self.peek_line(),
4310 ));
4311 }
4312 _ => {}
4313 }
4314 }
4315 }
4316 }
4317 Ok(())
4318 }
4319
4320 fn parse_fn_eq_body_or_block(&mut self, is_sub_keyword: bool) -> PerlResult<Block> {
4323 if !is_sub_keyword && self.eat(&Token::Assign) {
4324 let expr = self.parse_assign_expr()?;
4325 if matches!(self.peek(), Token::Comma) {
4326 return Err(self.syntax_err(
4327 "`fn ... =` allows only a single expression; use `fn ... { ... }` for multiple statements",
4328 self.peek_line(),
4329 ));
4330 }
4331 let eline = expr.line;
4332 self.eat(&Token::Semicolon);
4333 let mut body = vec![Statement {
4334 label: None,
4335 kind: StmtKind::Expression(expr),
4336 line: eline,
4337 }];
4338 Self::default_topic_for_sole_bareword(&mut body);
4339 Ok(body)
4340 } else {
4341 self.parse_block()
4342 }
4343 }
4344
4345 fn parse_sub_decl(&mut self, is_sub_keyword: bool) -> PerlResult<Statement> {
4346 let line = self.peek_line();
4347 self.advance(); match self.peek().clone() {
4349 Token::Ident(_) => {
4350 let name = self.parse_package_qualified_identifier()?;
4351 let bare = name.rsplit("::").next().unwrap_or(&name);
4360 if Self::is_underscore_topic_slot(bare) {
4361 return Err(self.syntax_err(
4362 format!(
4363 "`fn {}` would shadow the topic-slot scalar; pick a different name",
4364 name
4365 ),
4366 line,
4367 ));
4368 }
4369 let allow_shadow =
4376 crate::compat_mode() || (self.parsing_module && !crate::no_interop_mode());
4377 if !allow_shadow {
4378 self.check_udf_shadows_builtin(&name, line)?;
4379 }
4380 self.declared_subs.insert(name.clone());
4381 let (params, prototype) = self.parse_sub_sig_or_prototype_opt()?;
4382 self.parse_sub_attributes()?;
4383 let body = self.parse_fn_eq_body_or_block(is_sub_keyword)?;
4384 Ok(Statement {
4385 label: None,
4386 kind: StmtKind::SubDecl {
4387 name,
4388 params,
4389 body,
4390 prototype,
4391 },
4392 line,
4393 })
4394 }
4395 Token::LParen | Token::LBrace | Token::Colon => {
4396 if is_sub_keyword && crate::no_interop_mode() {
4398 return Err(self.syntax_err(
4399 "stryke uses `fn {}` instead of `sub {}` (--no-interop)",
4400 line,
4401 ));
4402 }
4403 let (params, _prototype) = self.parse_sub_sig_or_prototype_opt()?;
4405 self.parse_sub_attributes()?;
4406 let body = self.parse_fn_eq_body_or_block(is_sub_keyword)?;
4407 Ok(Statement {
4408 label: None,
4409 kind: StmtKind::Expression(Expr {
4410 kind: ExprKind::CodeRef { params, body },
4411 line,
4412 }),
4413 line,
4414 })
4415 }
4416 tok => {
4417 let topic_name = match &tok {
4422 Token::ScalarVar(n) | Token::ArrayVar(n) | Token::HashVar(n)
4423 if Self::is_underscore_topic_slot(n) =>
4424 {
4425 Some((
4426 match &tok {
4427 Token::ScalarVar(_) => '$',
4428 Token::ArrayVar(_) => '@',
4429 Token::HashVar(_) => '%',
4430 _ => unreachable!(),
4431 },
4432 n.clone(),
4433 ))
4434 }
4435 _ => None,
4436 };
4437 if let Some((sigil, n)) = topic_name {
4438 return Err(self.syntax_err(
4439 format!(
4440 "`fn {}{}` would shadow the topic-slot scalar; pick a different name",
4441 sigil, n
4442 ),
4443 self.peek_line(),
4444 ));
4445 }
4446 Err(self.syntax_err(
4447 format!("Expected sub name, `(`, `{{`, or `:`, got {:?}", tok),
4448 self.peek_line(),
4449 ))
4450 }
4451 }
4452 }
4453
4454 fn parse_advice_decl(&mut self, kind: crate::ast::AdviceKind) -> PerlResult<Statement> {
4457 let line = self.peek_line();
4458 self.advance(); let pattern = match self.advance() {
4460 (Token::SingleString(s), _) | (Token::DoubleString(s), _) => s,
4461 (tok, err_line) => {
4462 return Err(self.syntax_err(
4463 format!(
4464 "Expected string-literal pattern after `{}`, got {:?}",
4465 match kind {
4466 crate::ast::AdviceKind::Before => "before",
4467 crate::ast::AdviceKind::After => "after",
4468 crate::ast::AdviceKind::Around => "around",
4469 },
4470 tok
4471 ),
4472 err_line,
4473 ));
4474 }
4475 };
4476 let body = self.parse_block()?;
4477 Ok(Statement {
4478 label: None,
4479 kind: StmtKind::AdviceDecl {
4480 kind,
4481 pattern,
4482 body,
4483 },
4484 line,
4485 })
4486 }
4487
4488 fn parse_struct_decl(&mut self) -> PerlResult<Statement> {
4490 let line = self.peek_line();
4491 self.advance(); let name = self.parse_package_qualified_identifier().map_err(|_| {
4493 self.syntax_err(
4494 format!("Expected struct name, got {:?}", self.peek()),
4495 self.peek_line(),
4496 )
4497 })?;
4498 self.expect(&Token::LBrace)?;
4499 let mut fields = Vec::new();
4500 let mut methods = Vec::new();
4501 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
4502 let is_method = match self.peek() {
4504 Token::Ident(s) => s == "fn" || s == "sub",
4505 _ => false,
4506 };
4507 if is_method {
4508 self.advance(); let method_name = match self.advance() {
4510 (Token::Ident(n), _) => n,
4511 (tok, err_line) => {
4512 return Err(self
4513 .syntax_err(format!("Expected method name, got {:?}", tok), err_line))
4514 }
4515 };
4516 let params = if self.eat(&Token::LParen) {
4518 let p = self.parse_sub_signature_param_list()?;
4519 self.expect(&Token::RParen)?;
4520 p
4521 } else {
4522 Vec::new()
4523 };
4524 let body = self.parse_block()?;
4526 methods.push(crate::ast::StructMethod {
4527 name: method_name,
4528 params,
4529 body,
4530 });
4531 self.eat(&Token::Comma);
4533 self.eat(&Token::Semicolon);
4534 continue;
4535 }
4536
4537 let field_name = match self.advance() {
4538 (Token::Ident(n), _) => n,
4539 (tok, err_line) => {
4540 return Err(
4541 self.syntax_err(format!("Expected field name, got {:?}", tok), err_line)
4542 )
4543 }
4544 };
4545 let ty = if self.eat(&Token::FatArrow) {
4547 self.parse_type_name()?
4548 } else {
4549 crate::ast::PerlTypeName::Any
4550 };
4551 let default = if self.eat(&Token::Assign) {
4552 Some(self.parse_ternary()?)
4554 } else {
4555 None
4556 };
4557 fields.push(StructField {
4558 name: field_name,
4559 ty,
4560 default,
4561 });
4562 if !self.eat(&Token::Comma) {
4563 self.eat(&Token::Semicolon);
4565 }
4566 }
4567 self.expect(&Token::RBrace)?;
4568 self.eat(&Token::Semicolon);
4569 Ok(Statement {
4570 label: None,
4571 kind: StmtKind::StructDecl {
4572 def: StructDef {
4573 name,
4574 fields,
4575 methods,
4576 },
4577 },
4578 line,
4579 })
4580 }
4581
4582 fn parse_enum_decl(&mut self) -> PerlResult<Statement> {
4584 let line = self.peek_line();
4585 self.advance(); let name = self.parse_package_qualified_identifier().map_err(|_| {
4587 self.syntax_err(
4588 format!("Expected enum name, got {:?}", self.peek()),
4589 self.peek_line(),
4590 )
4591 })?;
4592 self.expect(&Token::LBrace)?;
4593 let mut variants = Vec::new();
4594 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
4595 let variant_name = match self.advance() {
4596 (Token::Ident(n), _) => n,
4597 (tok, err_line) => {
4598 return Err(
4599 self.syntax_err(format!("Expected variant name, got {:?}", tok), err_line)
4600 )
4601 }
4602 };
4603 let ty = if self.eat(&Token::FatArrow) {
4604 Some(self.parse_type_name()?)
4605 } else {
4606 None
4607 };
4608 variants.push(EnumVariant {
4609 name: variant_name,
4610 ty,
4611 });
4612 if !self.eat(&Token::Comma) {
4613 self.eat(&Token::Semicolon);
4614 }
4615 }
4616 self.expect(&Token::RBrace)?;
4617 self.eat(&Token::Semicolon);
4618 Ok(Statement {
4619 label: None,
4620 kind: StmtKind::EnumDecl {
4621 def: EnumDef { name, variants },
4622 },
4623 line,
4624 })
4625 }
4626
4627 fn parse_class_decl(&mut self, is_abstract: bool, is_final: bool) -> PerlResult<Statement> {
4629 use crate::ast::{ClassDef, ClassField, ClassMethod, ClassStaticField, Visibility};
4630 let line = self.peek_line();
4631 self.advance(); let name = self.parse_package_qualified_identifier().map_err(|_| {
4633 self.syntax_err(
4634 format!("Expected class name, got {:?}", self.peek()),
4635 self.peek_line(),
4636 )
4637 })?;
4638
4639 let mut extends = Vec::new();
4641 if matches!(self.peek(), Token::Ident(ref s) if s == "extends") {
4642 self.advance(); loop {
4644 let parent = self.parse_package_qualified_identifier().map_err(|_| {
4645 self.syntax_err(
4646 format!(
4647 "Expected parent class name after `extends`, got {:?}",
4648 self.peek()
4649 ),
4650 self.peek_line(),
4651 )
4652 })?;
4653 extends.push(parent);
4654 if !self.eat(&Token::Comma) {
4655 break;
4656 }
4657 }
4658 }
4659
4660 let mut implements = Vec::new();
4662 if matches!(self.peek(), Token::Ident(ref s) if s == "impl") {
4663 self.advance(); loop {
4665 let trait_name = self.parse_package_qualified_identifier().map_err(|_| {
4666 self.syntax_err(
4667 format!("Expected trait name after `impl`, got {:?}", self.peek()),
4668 self.peek_line(),
4669 )
4670 })?;
4671 implements.push(trait_name);
4672 if !self.eat(&Token::Comma) {
4673 break;
4674 }
4675 }
4676 }
4677
4678 self.expect(&Token::LBrace)?;
4679 let mut fields = Vec::new();
4680 let mut methods = Vec::new();
4681 let mut static_fields = Vec::new();
4682
4683 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
4684 let visibility = match self.peek() {
4686 Token::Ident(ref s) if s == "pub" => {
4687 self.advance();
4688 Visibility::Public
4689 }
4690 Token::Ident(ref s) if s == "priv" => {
4691 self.advance();
4692 Visibility::Private
4693 }
4694 Token::Ident(ref s) if s == "prot" => {
4695 self.advance();
4696 Visibility::Protected
4697 }
4698 _ => Visibility::Public, };
4700
4701 if matches!(self.peek(), Token::Ident(ref s) if s == "static") {
4703 self.advance(); if matches!(self.peek(), Token::Ident(ref s) if s == "fn" || s == "sub") {
4707 return Err(self.syntax_err(
4709 "use `fn Self.name` for static methods, not `static fn`",
4710 self.peek_line(),
4711 ));
4712 }
4713
4714 let field_name = match self.advance() {
4715 (Token::Ident(n), _) => n,
4716 (tok, err_line) => {
4717 return Err(self.syntax_err(
4718 format!("Expected static field name, got {:?}", tok),
4719 err_line,
4720 ))
4721 }
4722 };
4723
4724 let ty = if self.eat(&Token::Colon) {
4725 self.parse_type_name()?
4726 } else {
4727 crate::ast::PerlTypeName::Any
4728 };
4729
4730 let default = if self.eat(&Token::Assign) {
4731 Some(self.parse_ternary()?)
4732 } else {
4733 None
4734 };
4735
4736 static_fields.push(ClassStaticField {
4737 name: field_name,
4738 ty,
4739 visibility,
4740 default,
4741 });
4742
4743 if !self.eat(&Token::Comma) {
4744 self.eat(&Token::Semicolon);
4745 }
4746 continue;
4747 }
4748
4749 let method_is_final = matches!(self.peek(), Token::Ident(ref s) if s == "final");
4751 if method_is_final {
4752 self.advance(); }
4754
4755 let is_method = matches!(self.peek(), Token::Ident(ref s) if s == "fn" || s == "sub");
4757 if is_method {
4758 self.advance(); let is_static = matches!(self.peek(), Token::Ident(ref s) if s == "Self");
4762 if is_static {
4763 self.advance(); self.expect(&Token::Dot)?;
4765 }
4766
4767 let method_name = match self.advance() {
4768 (Token::Ident(n), _) => n,
4769 (tok, err_line) => {
4770 return Err(self
4771 .syntax_err(format!("Expected method name, got {:?}", tok), err_line))
4772 }
4773 };
4774
4775 let params = if self.eat(&Token::LParen) {
4777 let p = self.parse_sub_signature_param_list()?;
4778 self.expect(&Token::RParen)?;
4779 p
4780 } else {
4781 Vec::new()
4782 };
4783
4784 let body = if matches!(self.peek(), Token::LBrace) {
4786 Some(self.parse_block()?)
4787 } else {
4788 None
4789 };
4790
4791 methods.push(ClassMethod {
4792 name: method_name,
4793 params,
4794 body,
4795 visibility,
4796 is_static,
4797 is_final: method_is_final,
4798 });
4799 self.eat(&Token::Comma);
4800 self.eat(&Token::Semicolon);
4801 continue;
4802 } else if method_is_final {
4803 return Err(self.syntax_err("`final` must be followed by `fn`", self.peek_line()));
4804 }
4805
4806 let field_name = match self.advance() {
4808 (Token::Ident(n), _) => n,
4809 (tok, err_line) => {
4810 return Err(
4811 self.syntax_err(format!("Expected field name, got {:?}", tok), err_line)
4812 )
4813 }
4814 };
4815
4816 let ty = if self.eat(&Token::Colon) {
4818 self.parse_type_name()?
4819 } else {
4820 crate::ast::PerlTypeName::Any
4821 };
4822
4823 let default = if self.eat(&Token::Assign) {
4825 Some(self.parse_ternary()?)
4826 } else {
4827 None
4828 };
4829
4830 fields.push(ClassField {
4831 name: field_name,
4832 ty,
4833 visibility,
4834 default,
4835 });
4836
4837 if !self.eat(&Token::Comma) {
4838 self.eat(&Token::Semicolon);
4839 }
4840 }
4841
4842 self.expect(&Token::RBrace)?;
4843 self.eat(&Token::Semicolon);
4844
4845 Ok(Statement {
4846 label: None,
4847 kind: StmtKind::ClassDecl {
4848 def: ClassDef {
4849 name,
4850 is_abstract,
4851 is_final,
4852 extends,
4853 implements,
4854 fields,
4855 methods,
4856 static_fields,
4857 },
4858 },
4859 line,
4860 })
4861 }
4862
4863 fn parse_trait_decl(&mut self) -> PerlResult<Statement> {
4865 use crate::ast::{ClassMethod, TraitDef, Visibility};
4866 let line = self.peek_line();
4867 self.advance(); let name = self.parse_package_qualified_identifier().map_err(|_| {
4869 self.syntax_err(
4870 format!("Expected trait name, got {:?}", self.peek()),
4871 self.peek_line(),
4872 )
4873 })?;
4874
4875 self.expect(&Token::LBrace)?;
4876 let mut methods = Vec::new();
4877
4878 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
4879 let visibility = match self.peek() {
4881 Token::Ident(ref s) if s == "pub" => {
4882 self.advance();
4883 Visibility::Public
4884 }
4885 Token::Ident(ref s) if s == "priv" => {
4886 self.advance();
4887 Visibility::Private
4888 }
4889 Token::Ident(ref s) if s == "prot" => {
4890 self.advance();
4891 Visibility::Protected
4892 }
4893 _ => Visibility::Public,
4894 };
4895
4896 if !matches!(self.peek(), Token::Ident(ref s) if s == "fn" || s == "sub") {
4898 return Err(self.syntax_err("Expected `fn` in trait definition", self.peek_line()));
4899 }
4900 self.advance(); let method_name = match self.advance() {
4903 (Token::Ident(n), _) => n,
4904 (tok, err_line) => {
4905 return Err(
4906 self.syntax_err(format!("Expected method name, got {:?}", tok), err_line)
4907 )
4908 }
4909 };
4910
4911 let params = if self.eat(&Token::LParen) {
4913 let p = self.parse_sub_signature_param_list()?;
4914 self.expect(&Token::RParen)?;
4915 p
4916 } else {
4917 Vec::new()
4918 };
4919
4920 let body = if matches!(self.peek(), Token::LBrace) {
4922 Some(self.parse_block()?)
4923 } else {
4924 None
4925 };
4926
4927 methods.push(ClassMethod {
4928 name: method_name,
4929 params,
4930 body,
4931 visibility,
4932 is_static: false,
4933 is_final: false,
4934 });
4935
4936 self.eat(&Token::Comma);
4937 self.eat(&Token::Semicolon);
4938 }
4939
4940 self.expect(&Token::RBrace)?;
4941 self.eat(&Token::Semicolon);
4942
4943 Ok(Statement {
4944 label: None,
4945 kind: StmtKind::TraitDecl {
4946 def: TraitDef { name, methods },
4947 },
4948 line,
4949 })
4950 }
4951
4952 fn local_simple_target_to_var_decl(target: &Expr) -> Option<VarDecl> {
4953 match &target.kind {
4954 ExprKind::ScalarVar(name) => Some(VarDecl {
4955 sigil: Sigil::Scalar,
4956 name: name.clone(),
4957 initializer: None,
4958 frozen: false,
4959 type_annotation: None,
4960 }),
4961 ExprKind::ArrayVar(name) => Some(VarDecl {
4962 sigil: Sigil::Array,
4963 name: name.clone(),
4964 initializer: None,
4965 frozen: false,
4966 type_annotation: None,
4967 }),
4968 ExprKind::HashVar(name) => Some(VarDecl {
4969 sigil: Sigil::Hash,
4970 name: name.clone(),
4971 initializer: None,
4972 frozen: false,
4973 type_annotation: None,
4974 }),
4975 ExprKind::Typeglob(name) => Some(VarDecl {
4976 sigil: Sigil::Typeglob,
4977 name: name.clone(),
4978 initializer: None,
4979 frozen: false,
4980 type_annotation: None,
4981 }),
4982 _ => None,
4983 }
4984 }
4985
4986 fn parse_decl_array_destructure(
4987 &mut self,
4988 keyword: &str,
4989 line: usize,
4990 ) -> PerlResult<Statement> {
4991 self.expect(&Token::LBracket)?;
4992 let elems = self.parse_match_array_elems_until_rbracket()?;
4993 self.expect(&Token::Assign)?;
4994 self.suppress_scalar_hash_brace += 1;
4995 let rhs = self.parse_expression()?;
4996 self.suppress_scalar_hash_brace -= 1;
4997 let stmt = self.desugar_array_destructure(keyword, line, elems, rhs)?;
4998 self.parse_stmt_postfix_modifier(stmt)
4999 }
5000
5001 fn parse_decl_hash_destructure(&mut self, keyword: &str, line: usize) -> PerlResult<Statement> {
5002 let MatchPattern::Hash(pairs) = self.parse_match_hash_pattern()? else {
5003 unreachable!("parse_match_hash_pattern returns Hash");
5004 };
5005 self.expect(&Token::Assign)?;
5006 self.suppress_scalar_hash_brace += 1;
5007 let rhs = self.parse_expression()?;
5008 self.suppress_scalar_hash_brace -= 1;
5009 let stmt = self.desugar_hash_destructure(keyword, line, pairs, rhs)?;
5010 self.parse_stmt_postfix_modifier(stmt)
5011 }
5012
5013 fn desugar_array_destructure(
5014 &mut self,
5015 keyword: &str,
5016 line: usize,
5017 elems: Vec<MatchArrayElem>,
5018 rhs: Expr,
5019 ) -> PerlResult<Statement> {
5020 let tmp = format!("__stryke_ds_{}", self.alloc_desugar_tmp());
5021 let mut stmts: Vec<Statement> = Vec::new();
5022 stmts.push(destructure_stmt_from_var_decls(
5023 keyword,
5024 vec![VarDecl {
5025 sigil: Sigil::Scalar,
5026 name: tmp.clone(),
5027 initializer: Some(rhs),
5028 frozen: false,
5029 type_annotation: None,
5030 }],
5031 line,
5032 ));
5033
5034 let has_rest = elems
5035 .iter()
5036 .any(|e| matches!(e, MatchArrayElem::Rest | MatchArrayElem::RestBind(_)));
5037 let fixed_slots = elems
5038 .iter()
5039 .filter(|e| {
5040 matches!(
5041 e,
5042 MatchArrayElem::CaptureScalar(_) | MatchArrayElem::Expr(_)
5043 )
5044 })
5045 .count();
5046 if !has_rest {
5047 let cond = Expr {
5048 kind: ExprKind::BinOp {
5049 left: Box::new(destructure_expr_array_len(&tmp, line)),
5050 op: BinOp::NumEq,
5051 right: Box::new(Expr {
5052 kind: ExprKind::Integer(fixed_slots as i64),
5053 line,
5054 }),
5055 },
5056 line,
5057 };
5058 stmts.push(destructure_stmt_unless_die(
5059 line,
5060 cond,
5061 "array destructure: length mismatch",
5062 ));
5063 }
5064
5065 let mut idx: i64 = 0;
5066 for elem in elems {
5067 match elem {
5068 MatchArrayElem::Rest => break,
5069 MatchArrayElem::RestBind(name) => {
5070 let list_source = Expr {
5071 kind: ExprKind::Deref {
5072 expr: Box::new(destructure_expr_scalar_tmp(&tmp, line)),
5073 kind: Sigil::Array,
5074 },
5075 line,
5076 };
5077 let last_ix = Expr {
5078 kind: ExprKind::BinOp {
5079 left: Box::new(destructure_expr_array_len(&tmp, line)),
5080 op: BinOp::Sub,
5081 right: Box::new(Expr {
5082 kind: ExprKind::Integer(1),
5083 line,
5084 }),
5085 },
5086 line,
5087 };
5088 let range = Expr {
5089 kind: ExprKind::Range {
5090 from: Box::new(Expr {
5091 kind: ExprKind::Integer(idx),
5092 line,
5093 }),
5094 to: Box::new(last_ix),
5095 exclusive: false,
5096 step: None,
5097 },
5098 line,
5099 };
5100 let slice = Expr {
5101 kind: ExprKind::AnonymousListSlice {
5102 source: Box::new(list_source),
5103 indices: vec![range],
5104 },
5105 line,
5106 };
5107 stmts.push(destructure_stmt_from_var_decls(
5108 keyword,
5109 vec![VarDecl {
5110 sigil: Sigil::Array,
5111 name,
5112 initializer: Some(slice),
5113 frozen: false,
5114 type_annotation: None,
5115 }],
5116 line,
5117 ));
5118 break;
5119 }
5120 MatchArrayElem::CaptureScalar(name) => {
5121 let arrow = Expr {
5122 kind: ExprKind::ArrowDeref {
5123 expr: Box::new(destructure_expr_scalar_tmp(&tmp, line)),
5124 index: Box::new(Expr {
5125 kind: ExprKind::Integer(idx),
5126 line,
5127 }),
5128 kind: DerefKind::Array,
5129 },
5130 line,
5131 };
5132 stmts.push(destructure_stmt_from_var_decls(
5133 keyword,
5134 vec![VarDecl {
5135 sigil: Sigil::Scalar,
5136 name,
5137 initializer: Some(arrow),
5138 frozen: false,
5139 type_annotation: None,
5140 }],
5141 line,
5142 ));
5143 idx += 1;
5144 }
5145 MatchArrayElem::Expr(e) => {
5146 let elem_subj = Expr {
5147 kind: ExprKind::ArrowDeref {
5148 expr: Box::new(destructure_expr_scalar_tmp(&tmp, line)),
5149 index: Box::new(Expr {
5150 kind: ExprKind::Integer(idx),
5151 line,
5152 }),
5153 kind: DerefKind::Array,
5154 },
5155 line,
5156 };
5157 let match_expr = Expr {
5158 kind: ExprKind::AlgebraicMatch {
5159 subject: Box::new(elem_subj),
5160 arms: vec![
5161 MatchArm {
5162 pattern: MatchPattern::Value(Box::new(e.clone())),
5163 guard: None,
5164 body: Expr {
5165 kind: ExprKind::Integer(0),
5166 line,
5167 },
5168 },
5169 MatchArm {
5170 pattern: MatchPattern::Any,
5171 guard: None,
5172 body: Expr {
5173 kind: ExprKind::Die(vec![Expr {
5174 kind: ExprKind::String(
5175 "array destructure: element pattern mismatch"
5176 .to_string(),
5177 ),
5178 line,
5179 }]),
5180 line,
5181 },
5182 },
5183 ],
5184 },
5185 line,
5186 };
5187 stmts.push(Statement {
5188 label: None,
5189 kind: StmtKind::Expression(match_expr),
5190 line,
5191 });
5192 idx += 1;
5193 }
5194 }
5195 }
5196
5197 Ok(Statement {
5198 label: None,
5199 kind: StmtKind::StmtGroup(stmts),
5200 line,
5201 })
5202 }
5203
5204 fn desugar_hash_destructure(
5205 &mut self,
5206 keyword: &str,
5207 line: usize,
5208 pairs: Vec<MatchHashPair>,
5209 rhs: Expr,
5210 ) -> PerlResult<Statement> {
5211 let tmp = format!("__stryke_ds_{}", self.alloc_desugar_tmp());
5212 let mut stmts: Vec<Statement> = Vec::new();
5213 stmts.push(destructure_stmt_from_var_decls(
5214 keyword,
5215 vec![VarDecl {
5216 sigil: Sigil::Scalar,
5217 name: tmp.clone(),
5218 initializer: Some(rhs),
5219 frozen: false,
5220 type_annotation: None,
5221 }],
5222 line,
5223 ));
5224
5225 for pair in pairs {
5226 match pair {
5227 MatchHashPair::KeyOnly { key } => {
5228 let exists_op = Expr {
5229 kind: ExprKind::Exists(Box::new(Expr {
5230 kind: ExprKind::ArrowDeref {
5231 expr: Box::new(destructure_expr_scalar_tmp(&tmp, line)),
5232 index: Box::new(key),
5233 kind: DerefKind::Hash,
5234 },
5235 line,
5236 })),
5237 line,
5238 };
5239 stmts.push(destructure_stmt_unless_die(
5240 line,
5241 exists_op,
5242 "hash destructure: missing required key",
5243 ));
5244 }
5245 MatchHashPair::Capture { key, name } => {
5246 let init = Expr {
5247 kind: ExprKind::ArrowDeref {
5248 expr: Box::new(destructure_expr_scalar_tmp(&tmp, line)),
5249 index: Box::new(key),
5250 kind: DerefKind::Hash,
5251 },
5252 line,
5253 };
5254 stmts.push(destructure_stmt_from_var_decls(
5255 keyword,
5256 vec![VarDecl {
5257 sigil: Sigil::Scalar,
5258 name,
5259 initializer: Some(init),
5260 frozen: false,
5261 type_annotation: None,
5262 }],
5263 line,
5264 ));
5265 }
5266 }
5267 }
5268
5269 Ok(Statement {
5270 label: None,
5271 kind: StmtKind::StmtGroup(stmts),
5272 line,
5273 })
5274 }
5275
5276 fn parse_my_our_local(
5277 &mut self,
5278 keyword: &str,
5279 allow_type_annotation: bool,
5280 ) -> PerlResult<Statement> {
5281 let line = self.peek_line();
5282 self.advance(); if keyword == "local"
5285 && !matches!(self.peek(), Token::LParen | Token::LBracket | Token::LBrace)
5286 {
5287 let target = self.parse_postfix()?;
5288 let mut initializer: Option<Expr> = None;
5289 if self.eat(&Token::Assign) {
5290 initializer = Some(self.parse_expression()?);
5291 } else if matches!(
5292 self.peek(),
5293 Token::OrAssign | Token::DefinedOrAssign | Token::AndAssign
5294 ) {
5295 if matches!(&target.kind, ExprKind::Typeglob(_)) {
5296 return Err(self.syntax_err(
5297 "compound assignment on typeglob declaration is not supported",
5298 self.peek_line(),
5299 ));
5300 }
5301 let op = match self.peek().clone() {
5302 Token::OrAssign => BinOp::LogOr,
5303 Token::DefinedOrAssign => BinOp::DefinedOr,
5304 Token::AndAssign => BinOp::LogAnd,
5305 _ => unreachable!(),
5306 };
5307 self.advance();
5308 let rhs = self.parse_assign_expr()?;
5309 let tgt_line = target.line;
5310 initializer = Some(Expr {
5311 kind: ExprKind::CompoundAssign {
5312 target: Box::new(target.clone()),
5313 op,
5314 value: Box::new(rhs),
5315 },
5316 line: tgt_line,
5317 });
5318 }
5319
5320 let kind = if let Some(mut decl) = Self::local_simple_target_to_var_decl(&target) {
5321 decl.initializer = initializer;
5322 StmtKind::Local(vec![decl])
5323 } else {
5324 StmtKind::LocalExpr {
5325 target,
5326 initializer,
5327 }
5328 };
5329 let stmt = Statement {
5330 label: None,
5331 kind,
5332 line,
5333 };
5334 return self.parse_stmt_postfix_modifier(stmt);
5335 }
5336
5337 if matches!(self.peek(), Token::LBracket) {
5338 return self.parse_decl_array_destructure(keyword, line);
5339 }
5340 if matches!(self.peek(), Token::LBrace) {
5341 return self.parse_decl_hash_destructure(keyword, line);
5342 }
5343
5344 let mut decls = Vec::new();
5345
5346 if self.eat(&Token::LParen) {
5347 while !matches!(self.peek(), Token::RParen | Token::Eof) {
5349 let decl = self.parse_var_decl(allow_type_annotation)?;
5350 decls.push(decl);
5351 if !self.eat(&Token::Comma) {
5352 break;
5353 }
5354 }
5355 self.expect(&Token::RParen)?;
5356 } else {
5357 decls.push(self.parse_var_decl(allow_type_annotation)?);
5358 }
5359
5360 if self.eat(&Token::Assign) {
5362 if keyword == "our" && decls.len() == 1 {
5363 while matches!(self.peek(), Token::Ident(ref i) if i == "our") {
5364 self.advance();
5365 decls.push(self.parse_var_decl(allow_type_annotation)?);
5366 if !self.eat(&Token::Assign) {
5367 return Err(self.syntax_err(
5368 "expected `=` after `our` in chained our-declaration",
5369 self.peek_line(),
5370 ));
5371 }
5372 }
5373 }
5374 let val = self.parse_expression()?;
5375 if !crate::compat_mode() && decls.len() == 1 {
5378 let decl = &decls[0];
5379 let target_kind = match decl.sigil {
5380 Sigil::Scalar => ExprKind::ScalarVar(decl.name.clone()),
5381 Sigil::Array => ExprKind::ArrayVar(decl.name.clone()),
5382 Sigil::Hash => ExprKind::HashVar(decl.name.clone()),
5383 Sigil::Typeglob => {
5384 if decls.len() == 1 {
5386 decls[0].initializer = Some(val);
5387 } else {
5388 for d in &mut decls {
5389 d.initializer = Some(val.clone());
5390 }
5391 }
5392 return Ok(Statement {
5393 label: None,
5394 kind: match keyword {
5395 "my" => StmtKind::My(decls),
5396 "mysync" => StmtKind::MySync(decls),
5397 "our" => StmtKind::Our(decls),
5398 "local" => StmtKind::Local(decls),
5399 "state" => StmtKind::State(decls),
5400 _ => unreachable!(),
5401 },
5402 line,
5403 });
5404 }
5405 };
5406 let target = Expr {
5407 kind: target_kind,
5408 line,
5409 };
5410 self.validate_assignment(&target, &val, line)?;
5411 }
5412 if decls.len() == 1 {
5413 decls[0].initializer = Some(val);
5414 } else {
5415 for decl in &mut decls {
5416 decl.initializer = Some(val.clone());
5417 }
5418 }
5419 } else if decls.len() == 1 {
5420 let op = match self.peek().clone() {
5422 Token::OrAssign => Some(BinOp::LogOr),
5423 Token::DefinedOrAssign => Some(BinOp::DefinedOr),
5424 Token::AndAssign => Some(BinOp::LogAnd),
5425 _ => None,
5426 };
5427 if let Some(op) = op {
5428 let d = &decls[0];
5429 if matches!(d.sigil, Sigil::Typeglob) {
5430 return Err(self.syntax_err(
5431 "compound assignment on typeglob declaration is not supported",
5432 self.peek_line(),
5433 ));
5434 }
5435 self.advance();
5436 let rhs = self.parse_assign_expr()?;
5437 let target = Expr {
5438 kind: match d.sigil {
5439 Sigil::Scalar => ExprKind::ScalarVar(d.name.clone()),
5440 Sigil::Array => ExprKind::ArrayVar(d.name.clone()),
5441 Sigil::Hash => ExprKind::HashVar(d.name.clone()),
5442 Sigil::Typeglob => unreachable!(),
5443 },
5444 line,
5445 };
5446 decls[0].initializer = Some(Expr {
5447 kind: ExprKind::CompoundAssign {
5448 target: Box::new(target),
5449 op,
5450 value: Box::new(rhs),
5451 },
5452 line,
5453 });
5454 }
5455 }
5456
5457 let kind = match keyword {
5458 "my" => StmtKind::My(decls),
5459 "mysync" => StmtKind::MySync(decls),
5460 "our" => StmtKind::Our(decls),
5461 "local" => StmtKind::Local(decls),
5462 "state" => StmtKind::State(decls),
5463 _ => unreachable!(),
5464 };
5465 let stmt = Statement {
5466 label: None,
5467 kind,
5468 line,
5469 };
5470 self.parse_stmt_postfix_modifier(stmt)
5472 }
5473
5474 fn parse_var_decl(&mut self, allow_type_annotation: bool) -> PerlResult<VarDecl> {
5475 let mut decl = match self.advance() {
5476 (Token::ScalarVar(name), _) => VarDecl {
5477 sigil: Sigil::Scalar,
5478 name,
5479 initializer: None,
5480 frozen: false,
5481 type_annotation: None,
5482 },
5483 (Token::ArrayVar(name), _) => VarDecl {
5484 sigil: Sigil::Array,
5485 name,
5486 initializer: None,
5487 frozen: false,
5488 type_annotation: None,
5489 },
5490 (Token::HashVar(name), line) => {
5491 if !crate::compat_mode() {
5492 self.check_hash_shadows_reserved(&name, line)?;
5493 }
5494 VarDecl {
5495 sigil: Sigil::Hash,
5496 name,
5497 initializer: None,
5498 frozen: false,
5499 type_annotation: None,
5500 }
5501 }
5502 (Token::Star, _line) => {
5503 let name = match self.advance() {
5504 (Token::Ident(n), _) => n,
5505 (tok, l) => {
5506 return Err(self
5507 .syntax_err(format!("Expected identifier after *, got {:?}", tok), l));
5508 }
5509 };
5510 VarDecl {
5511 sigil: Sigil::Typeglob,
5512 name,
5513 initializer: None,
5514 frozen: false,
5515 type_annotation: None,
5516 }
5517 }
5518 (Token::Ident(ref kw), _) if kw == "undef" => VarDecl {
5523 sigil: Sigil::Scalar,
5524 name: format!("__undef_sink_{}", self.pos),
5528 initializer: None,
5529 frozen: false,
5530 type_annotation: None,
5531 },
5532 (tok, line) => {
5533 return Err(self.syntax_err(
5534 format!("Expected variable in declaration, got {:?}", tok),
5535 line,
5536 ));
5537 }
5538 };
5539 if allow_type_annotation && self.eat(&Token::Colon) {
5540 let ty = self.parse_type_name()?;
5541 if decl.sigil != Sigil::Scalar {
5542 return Err(self.syntax_err(
5543 "`: Type` is only valid for scalar declarations (typed my $name : Int)",
5544 self.peek_line(),
5545 ));
5546 }
5547 decl.type_annotation = Some(ty);
5548 }
5549 Ok(decl)
5550 }
5551
5552 fn parse_type_name(&mut self) -> PerlResult<PerlTypeName> {
5553 match self.advance() {
5554 (Token::Ident(name), _) => match name.as_str() {
5555 "Int" => Ok(PerlTypeName::Int),
5556 "Str" => Ok(PerlTypeName::Str),
5557 "Float" => Ok(PerlTypeName::Float),
5558 "Bool" => Ok(PerlTypeName::Bool),
5559 "Array" => Ok(PerlTypeName::Array),
5560 "Hash" => Ok(PerlTypeName::Hash),
5561 "Ref" => Ok(PerlTypeName::Ref),
5562 "Any" => Ok(PerlTypeName::Any),
5563 _ => Ok(PerlTypeName::Struct(name)),
5564 },
5565 (tok, err_line) => Err(self.syntax_err(
5566 format!("Expected type name after `:`, got {:?}", tok),
5567 err_line,
5568 )),
5569 }
5570 }
5571
5572 fn parse_package(&mut self) -> PerlResult<Statement> {
5573 let line = self.peek_line();
5574 self.advance(); let name = match self.advance() {
5576 (Token::Ident(n), _) => n,
5577 (tok, line) => {
5578 return Err(self.syntax_err(format!("Expected package name, got {:?}", tok), line))
5579 }
5580 };
5581 let mut full_name = name;
5583 while self.eat(&Token::PackageSep) {
5584 if let (Token::Ident(part), _) = self.advance() {
5585 full_name = format!("{}::{}", full_name, part);
5586 }
5587 }
5588 self.eat(&Token::Semicolon);
5589 Ok(Statement {
5590 label: None,
5591 kind: StmtKind::Package { name: full_name },
5592 line,
5593 })
5594 }
5595
5596 fn parse_use(&mut self) -> PerlResult<Statement> {
5597 let line = self.peek_line();
5598 self.advance(); let (tok, tok_line) = self.advance();
5600 match tok {
5601 Token::Float(v) => {
5602 self.eat(&Token::Semicolon);
5603 Ok(Statement {
5604 label: None,
5605 kind: StmtKind::UsePerlVersion { version: v },
5606 line,
5607 })
5608 }
5609 Token::Integer(n) => {
5610 if matches!(self.peek(), Token::Semicolon | Token::Eof) {
5611 self.eat(&Token::Semicolon);
5612 Ok(Statement {
5613 label: None,
5614 kind: StmtKind::UsePerlVersion { version: n as f64 },
5615 line,
5616 })
5617 } else {
5618 Err(self.syntax_err(
5619 format!("Expected ';' after use VERSION (got {:?})", self.peek()),
5620 line,
5621 ))
5622 }
5623 }
5624 Token::Ident(n) => {
5625 let mut full_name = n;
5626 while self.eat(&Token::PackageSep) {
5627 if let (Token::Ident(part), _) = self.advance() {
5628 full_name = format!("{}::{}", full_name, part);
5629 }
5630 }
5631 if full_name == "overload" {
5632 let mut pairs = Vec::new();
5633 let mut parse_overload_pairs = |this: &mut Self| -> PerlResult<()> {
5634 loop {
5635 if matches!(this.peek(), Token::RParen | Token::Semicolon | Token::Eof)
5636 {
5637 break;
5638 }
5639 let key_e = this.parse_assign_expr()?;
5640 this.expect(&Token::FatArrow)?;
5641 let val_e = this.parse_assign_expr()?;
5642 let key = this.expr_to_overload_key(&key_e)?;
5643 let val = this.expr_to_overload_sub(&val_e)?;
5644 pairs.push((key, val));
5645 if !this.eat(&Token::Comma) {
5646 break;
5647 }
5648 }
5649 Ok(())
5650 };
5651 if self.eat(&Token::LParen) {
5652 parse_overload_pairs(self)?;
5654 self.expect(&Token::RParen)?;
5655 } else if !matches!(self.peek(), Token::Semicolon | Token::Eof) {
5656 parse_overload_pairs(self)?;
5657 }
5658 self.eat(&Token::Semicolon);
5659 return Ok(Statement {
5660 label: None,
5661 kind: StmtKind::UseOverload { pairs },
5662 line,
5663 });
5664 }
5665 let mut imports = Vec::new();
5666 if !matches!(self.peek(), Token::Semicolon | Token::Eof)
5667 && !self.next_is_new_statement_start(tok_line)
5668 {
5669 loop {
5670 if matches!(self.peek(), Token::Semicolon | Token::Eof) {
5671 break;
5672 }
5673 imports.push(self.parse_expression()?);
5674 if !self.eat(&Token::Comma) {
5675 break;
5676 }
5677 }
5678 }
5679 self.eat(&Token::Semicolon);
5680 Ok(Statement {
5681 label: None,
5682 kind: StmtKind::Use {
5683 module: full_name,
5684 imports,
5685 },
5686 line,
5687 })
5688 }
5689 other => Err(self.syntax_err(
5690 format!("Expected module name or version after use, got {:?}", other),
5691 tok_line,
5692 )),
5693 }
5694 }
5695
5696 fn parse_no(&mut self) -> PerlResult<Statement> {
5697 let line = self.peek_line();
5698 self.advance(); let module = match self.advance() {
5700 (Token::Ident(n), tok_line) => (n, tok_line),
5701 (tok, line) => {
5702 return Err(self.syntax_err(
5703 format!("Expected module name after no, got {:?}", tok),
5704 line,
5705 ))
5706 }
5707 };
5708 let (module_name, tok_line) = module;
5709 let mut full_name = module_name;
5710 while self.eat(&Token::PackageSep) {
5711 if let (Token::Ident(part), _) = self.advance() {
5712 full_name = format!("{}::{}", full_name, part);
5713 }
5714 }
5715 let mut imports = Vec::new();
5716 if !matches!(self.peek(), Token::Semicolon | Token::Eof)
5717 && !self.next_is_new_statement_start(tok_line)
5718 {
5719 loop {
5720 if matches!(self.peek(), Token::Semicolon | Token::Eof) {
5721 break;
5722 }
5723 imports.push(self.parse_expression()?);
5724 if !self.eat(&Token::Comma) {
5725 break;
5726 }
5727 }
5728 }
5729 self.eat(&Token::Semicolon);
5730 Ok(Statement {
5731 label: None,
5732 kind: StmtKind::No {
5733 module: full_name,
5734 imports,
5735 },
5736 line,
5737 })
5738 }
5739
5740 fn parse_return(&mut self) -> PerlResult<Statement> {
5741 let line = self.peek_line();
5742 self.advance(); let val = if matches!(self.peek(), Token::Semicolon | Token::RBrace | Token::Eof)
5749 || self.peek_is_postfix_stmt_modifier_keyword()
5750 {
5751 None
5752 } else {
5753 let first = self.parse_assign_expr()?;
5758 if matches!(self.peek(), Token::Comma | Token::FatArrow) {
5759 let mut items = vec![first];
5760 while self.eat(&Token::Comma) || self.eat(&Token::FatArrow) {
5761 if matches!(
5762 self.peek(),
5763 Token::Semicolon | Token::RBrace | Token::Eof
5764 ) || self.peek_is_postfix_stmt_modifier_keyword()
5765 {
5766 break;
5767 }
5768 items.push(self.parse_assign_expr()?);
5769 }
5770 let line = items.first().map(|e| e.line).unwrap_or(line);
5771 Some(Expr {
5772 kind: ExprKind::List(items),
5773 line,
5774 })
5775 } else {
5776 Some(first)
5777 }
5778 };
5779 let stmt = Statement {
5781 label: None,
5782 kind: StmtKind::Return(val),
5783 line,
5784 };
5785 if let Token::Ident(ref kw) = self.peek().clone() {
5786 match kw.as_str() {
5787 "if" => {
5788 self.advance();
5789 let cond = self.parse_expression()?;
5790 self.eat(&Token::Semicolon);
5791 return Ok(Statement {
5792 label: None,
5793 kind: StmtKind::If {
5794 condition: cond,
5795 body: vec![stmt],
5796 elsifs: vec![],
5797 else_block: None,
5798 },
5799 line,
5800 });
5801 }
5802 "unless" => {
5803 self.advance();
5804 let cond = self.parse_expression()?;
5805 self.eat(&Token::Semicolon);
5806 return Ok(Statement {
5807 label: None,
5808 kind: StmtKind::Unless {
5809 condition: cond,
5810 body: vec![stmt],
5811 else_block: None,
5812 },
5813 line,
5814 });
5815 }
5816 _ => {}
5817 }
5818 }
5819 self.eat(&Token::Semicolon);
5820 Ok(stmt)
5821 }
5822
5823 fn parse_expression(&mut self) -> PerlResult<Expr> {
5826 self.parse_comma_expr()
5827 }
5828
5829 fn parse_comma_expr(&mut self) -> PerlResult<Expr> {
5830 let expr = self.parse_assign_expr()?;
5831 let mut exprs = vec![expr];
5832 while self.eat(&Token::Comma) || self.eat(&Token::FatArrow) {
5833 if matches!(
5834 self.peek(),
5835 Token::RParen | Token::RBracket | Token::RBrace | Token::Semicolon | Token::Eof
5836 ) {
5837 break; }
5839 exprs.push(self.parse_assign_expr()?);
5840 }
5841 if exprs.len() == 1 {
5842 return Ok(exprs.pop().unwrap());
5843 }
5844 let line = exprs[0].line;
5845 Ok(Expr {
5846 kind: ExprKind::List(exprs),
5847 line,
5848 })
5849 }
5850
5851 fn parse_assign_expr(&mut self) -> PerlResult<Expr> {
5852 let expr = self.parse_ternary()?;
5853 let line = expr.line;
5854
5855 match self.peek().clone() {
5856 Token::Assign => {
5857 self.advance();
5858 let right = self.parse_assign_expr()?;
5859 if let ExprKind::MethodCall { ref args, .. } = expr.kind {
5861 if args.is_empty() {
5862 let ExprKind::MethodCall {
5864 object,
5865 method,
5866 super_call,
5867 ..
5868 } = expr.kind
5869 else {
5870 unreachable!()
5871 };
5872 return Ok(Expr {
5873 kind: ExprKind::MethodCall {
5874 object,
5875 method,
5876 args: vec![right],
5877 super_call,
5878 },
5879 line,
5880 });
5881 }
5882 }
5883 self.validate_assignment(&expr, &right, line)?;
5884 Ok(Expr {
5885 kind: ExprKind::Assign {
5886 target: Box::new(expr),
5887 value: Box::new(right),
5888 },
5889 line,
5890 })
5891 }
5892 Token::PlusAssign => {
5893 self.advance();
5894 let r = self.parse_assign_expr()?;
5895 Ok(Expr {
5896 kind: ExprKind::CompoundAssign {
5897 target: Box::new(expr),
5898 op: BinOp::Add,
5899 value: Box::new(r),
5900 },
5901 line,
5902 })
5903 }
5904 Token::MinusAssign => {
5905 self.advance();
5906 let r = self.parse_assign_expr()?;
5907 Ok(Expr {
5908 kind: ExprKind::CompoundAssign {
5909 target: Box::new(expr),
5910 op: BinOp::Sub,
5911 value: Box::new(r),
5912 },
5913 line,
5914 })
5915 }
5916 Token::MulAssign => {
5917 self.advance();
5918 let r = self.parse_assign_expr()?;
5919 Ok(Expr {
5920 kind: ExprKind::CompoundAssign {
5921 target: Box::new(expr),
5922 op: BinOp::Mul,
5923 value: Box::new(r),
5924 },
5925 line,
5926 })
5927 }
5928 Token::DivAssign => {
5929 self.advance();
5930 let r = self.parse_assign_expr()?;
5931 Ok(Expr {
5932 kind: ExprKind::CompoundAssign {
5933 target: Box::new(expr),
5934 op: BinOp::Div,
5935 value: Box::new(r),
5936 },
5937 line,
5938 })
5939 }
5940 Token::ModAssign => {
5941 self.advance();
5942 let r = self.parse_assign_expr()?;
5943 Ok(Expr {
5944 kind: ExprKind::CompoundAssign {
5945 target: Box::new(expr),
5946 op: BinOp::Mod,
5947 value: Box::new(r),
5948 },
5949 line,
5950 })
5951 }
5952 Token::PowAssign => {
5953 self.advance();
5954 let r = self.parse_assign_expr()?;
5955 Ok(Expr {
5956 kind: ExprKind::CompoundAssign {
5957 target: Box::new(expr),
5958 op: BinOp::Pow,
5959 value: Box::new(r),
5960 },
5961 line,
5962 })
5963 }
5964 Token::DotAssign => {
5965 self.advance();
5966 let r = self.parse_assign_expr()?;
5967 Ok(Expr {
5968 kind: ExprKind::CompoundAssign {
5969 target: Box::new(expr),
5970 op: BinOp::Concat,
5971 value: Box::new(r),
5972 },
5973 line,
5974 })
5975 }
5976 Token::BitAndAssign => {
5977 self.advance();
5978 let r = self.parse_assign_expr()?;
5979 Ok(Expr {
5980 kind: ExprKind::CompoundAssign {
5981 target: Box::new(expr),
5982 op: BinOp::BitAnd,
5983 value: Box::new(r),
5984 },
5985 line,
5986 })
5987 }
5988 Token::BitOrAssign => {
5989 self.advance();
5990 let r = self.parse_assign_expr()?;
5991 Ok(Expr {
5992 kind: ExprKind::CompoundAssign {
5993 target: Box::new(expr),
5994 op: BinOp::BitOr,
5995 value: Box::new(r),
5996 },
5997 line,
5998 })
5999 }
6000 Token::XorAssign => {
6001 self.advance();
6002 let r = self.parse_assign_expr()?;
6003 Ok(Expr {
6004 kind: ExprKind::CompoundAssign {
6005 target: Box::new(expr),
6006 op: BinOp::BitXor,
6007 value: Box::new(r),
6008 },
6009 line,
6010 })
6011 }
6012 Token::ShiftLeftAssign => {
6013 self.advance();
6014 let r = self.parse_assign_expr()?;
6015 Ok(Expr {
6016 kind: ExprKind::CompoundAssign {
6017 target: Box::new(expr),
6018 op: BinOp::ShiftLeft,
6019 value: Box::new(r),
6020 },
6021 line,
6022 })
6023 }
6024 Token::ShiftRightAssign => {
6025 self.advance();
6026 let r = self.parse_assign_expr()?;
6027 Ok(Expr {
6028 kind: ExprKind::CompoundAssign {
6029 target: Box::new(expr),
6030 op: BinOp::ShiftRight,
6031 value: Box::new(r),
6032 },
6033 line,
6034 })
6035 }
6036 Token::OrAssign => {
6037 self.advance();
6038 let r = self.parse_assign_expr()?;
6039 Ok(Expr {
6040 kind: ExprKind::CompoundAssign {
6041 target: Box::new(expr),
6042 op: BinOp::LogOr,
6043 value: Box::new(r),
6044 },
6045 line,
6046 })
6047 }
6048 Token::DefinedOrAssign => {
6049 self.advance();
6050 let r = self.parse_assign_expr()?;
6051 Ok(Expr {
6052 kind: ExprKind::CompoundAssign {
6053 target: Box::new(expr),
6054 op: BinOp::DefinedOr,
6055 value: Box::new(r),
6056 },
6057 line,
6058 })
6059 }
6060 Token::AndAssign => {
6061 self.advance();
6062 let r = self.parse_assign_expr()?;
6063 Ok(Expr {
6064 kind: ExprKind::CompoundAssign {
6065 target: Box::new(expr),
6066 op: BinOp::LogAnd,
6067 value: Box::new(r),
6068 },
6069 line,
6070 })
6071 }
6072 _ => Ok(expr),
6073 }
6074 }
6075
6076 fn parse_ternary(&mut self) -> PerlResult<Expr> {
6077 let expr = self.parse_pipe_forward()?;
6078 if self.eat(&Token::Question) {
6079 let line = expr.line;
6080 self.suppress_colon_range = self.suppress_colon_range.saturating_add(1);
6081 let then_expr = self.parse_assign_expr();
6082 self.suppress_colon_range = self.suppress_colon_range.saturating_sub(1);
6083 let then_expr = then_expr?;
6084 self.expect(&Token::Colon)?;
6085 let else_expr = self.parse_assign_expr()?;
6086 return Ok(Expr {
6087 kind: ExprKind::Ternary {
6088 condition: Box::new(expr),
6089 then_expr: Box::new(then_expr),
6090 else_expr: Box::new(else_expr),
6091 },
6092 line,
6093 });
6094 }
6095 Ok(expr)
6096 }
6097
6098 fn parse_pipe_forward(&mut self) -> PerlResult<Expr> {
6104 let mut left = self.parse_or_word()?;
6105 if self.no_pipe_forward_depth > 0 {
6111 return Ok(left);
6112 }
6113 while matches!(self.peek(), Token::PipeForward) {
6114 if crate::compat_mode() {
6115 return Err(self.syntax_err(
6116 "pipe-forward operator `|>` is a stryke extension (disabled by --compat)",
6117 left.line,
6118 ));
6119 }
6120 let line = left.line;
6121 self.advance();
6122 self.pipe_rhs_depth = self.pipe_rhs_depth.saturating_add(1);
6125 let right_result = self.parse_or_word();
6126 self.pipe_rhs_depth = self.pipe_rhs_depth.saturating_sub(1);
6127 let right = right_result?;
6128 left = self.pipe_forward_apply(left, right, line)?;
6129 }
6130 Ok(left)
6131 }
6132
6133 fn pipe_forward_apply(&self, lhs: Expr, rhs: Expr, line: usize) -> PerlResult<Expr> {
6155 let Expr { kind, line: rline } = rhs;
6156 let new_kind = match kind {
6157 ExprKind::FuncCall { name, mut args } => {
6159 let dispatch_name: &str = name.strip_prefix("CORE::").unwrap_or(name.as_str());
6162 match dispatch_name {
6163 "puniq" | "uniq" | "distinct" | "flatten" | "set" | "list_count"
6164 | "list_size" | "count" | "size" | "cnt" | "len" | "with_index" | "shuffle"
6165 | "shuffled" | "frequencies" | "freq" | "interleave" | "ddump"
6166 | "stringify" | "str" | "lines" | "words" | "chars" | "digits" | "letters"
6167 | "letters_uc" | "letters_lc" | "punctuation" | "numbers" | "graphemes"
6168 | "columns" | "sentences" | "paragraphs" | "sections" | "trim" | "avg"
6169 | "to_json" | "to_csv" | "to_toml" | "to_yaml" | "to_xml" | "to_html"
6170 | "from_json" | "from_csv" | "from_toml" | "from_yaml" | "from_xml"
6171 | "to_markdown" | "to_table" | "xopen" | "clip" | "sparkline" | "bar_chart"
6172 | "flame" | "stddev" | "squared" | "sq" | "square" | "cubed" | "cb"
6173 | "cube" | "normalize" | "snake_case" | "camel_case" | "kebab_case" => {
6174 if args.is_empty() {
6175 args.push(lhs);
6176 } else {
6177 args[0] = lhs;
6178 }
6179 }
6180 "chunked" | "windowed" => {
6181 if args.is_empty() {
6182 return Err(self.syntax_err(
6183 "|>: chunked(N) / windowed(N) needs size — e.g. `@a |> windowed(2)`",
6184 line,
6185 ));
6186 }
6187 args.insert(0, lhs);
6188 }
6189 "reduce" | "fold" => {
6190 args.push(lhs);
6191 }
6192 "grep_v" | "pluck" | "tee" | "nth" | "chunk" => {
6193 args.push(lhs);
6199 }
6200 "enumerate" | "dedup" => {
6201 args.insert(0, lhs);
6204 }
6205 "clamp" => {
6206 args.push(lhs);
6208 }
6209 n if Self::is_block_then_list_pipe_builtin(n) => {
6210 if args.len() < 2 {
6211 return Err(self.syntax_err(
6212 format!(
6213 "|>: `{name}` needs {{ BLOCK }}, LIST so the list can receive the pipe"
6214 ),
6215 line,
6216 ));
6217 }
6218 args[1] = lhs;
6219 }
6220 "take" | "head" | "tail" | "drop" => {
6221 if args.is_empty() {
6222 return Err(self.syntax_err(
6223 "|>: `{name}` needs N last — e.g. `@a |> take(3)` for `take(@a, 3)`",
6224 line,
6225 ));
6226 }
6227 args.insert(0, lhs);
6229 }
6230 _ => {
6231 if self.thread_last_mode {
6232 args.push(lhs);
6233 } else {
6234 args.insert(0, lhs);
6235 }
6236 }
6237 }
6238 ExprKind::FuncCall { name, args }
6239 }
6240 ExprKind::MethodCall {
6241 object,
6242 method,
6243 mut args,
6244 super_call,
6245 } => {
6246 if self.thread_last_mode {
6247 args.push(lhs);
6248 } else {
6249 args.insert(0, lhs);
6250 }
6251 ExprKind::MethodCall {
6252 object,
6253 method,
6254 args,
6255 super_call,
6256 }
6257 }
6258 ExprKind::IndirectCall {
6259 target,
6260 mut args,
6261 ampersand,
6262 pass_caller_arglist: _,
6263 } => {
6264 if self.thread_last_mode {
6265 args.push(lhs);
6266 } else {
6267 args.insert(0, lhs);
6268 }
6269 ExprKind::IndirectCall {
6270 target,
6271 args,
6272 ampersand,
6273 pass_caller_arglist: false,
6276 }
6277 }
6278
6279 ExprKind::Print { handle, mut args } => {
6281 if self.thread_last_mode {
6282 args.push(lhs);
6283 } else {
6284 args.insert(0, lhs);
6285 }
6286 ExprKind::Print { handle, args }
6287 }
6288 ExprKind::Say { handle, mut args } => {
6289 if self.thread_last_mode {
6290 args.push(lhs);
6291 } else {
6292 args.insert(0, lhs);
6293 }
6294 ExprKind::Say { handle, args }
6295 }
6296 ExprKind::Printf { handle, mut args } => {
6297 if self.thread_last_mode {
6298 args.push(lhs);
6299 } else {
6300 args.insert(0, lhs);
6301 }
6302 ExprKind::Printf { handle, args }
6303 }
6304 ExprKind::Die(mut args) => {
6305 if self.thread_last_mode {
6306 args.push(lhs);
6307 } else {
6308 args.insert(0, lhs);
6309 }
6310 ExprKind::Die(args)
6311 }
6312 ExprKind::Warn(mut args) => {
6313 if self.thread_last_mode {
6314 args.push(lhs);
6315 } else {
6316 args.insert(0, lhs);
6317 }
6318 ExprKind::Warn(args)
6319 }
6320
6321 ExprKind::Sprintf { format, mut args } => {
6327 if self.thread_last_mode {
6328 args.push(lhs);
6329 } else {
6330 args.insert(0, lhs);
6331 }
6332 ExprKind::Sprintf { format, args }
6333 }
6334
6335 ExprKind::System(mut args) => {
6337 if self.thread_last_mode {
6338 args.push(lhs);
6339 } else {
6340 args.insert(0, lhs);
6341 }
6342 ExprKind::System(args)
6343 }
6344 ExprKind::Exec(mut args) => {
6345 if self.thread_last_mode {
6346 args.push(lhs);
6347 } else {
6348 args.insert(0, lhs);
6349 }
6350 ExprKind::Exec(args)
6351 }
6352 ExprKind::Unlink(mut args) => {
6353 if self.thread_last_mode {
6354 args.push(lhs);
6355 } else {
6356 args.insert(0, lhs);
6357 }
6358 ExprKind::Unlink(args)
6359 }
6360 ExprKind::Chmod(mut args) => {
6361 if self.thread_last_mode {
6362 args.push(lhs);
6363 } else {
6364 args.insert(0, lhs);
6365 }
6366 ExprKind::Chmod(args)
6367 }
6368 ExprKind::Chown(mut args) => {
6369 if self.thread_last_mode {
6370 args.push(lhs);
6371 } else {
6372 args.insert(0, lhs);
6373 }
6374 ExprKind::Chown(args)
6375 }
6376 ExprKind::Glob(mut args) => {
6377 if self.thread_last_mode {
6378 args.push(lhs);
6379 } else {
6380 args.insert(0, lhs);
6381 }
6382 ExprKind::Glob(args)
6383 }
6384 ExprKind::Files(mut args) => {
6385 if self.thread_last_mode {
6386 args.push(lhs);
6387 } else {
6388 args.insert(0, lhs);
6389 }
6390 ExprKind::Files(args)
6391 }
6392 ExprKind::Filesf(mut args) => {
6393 if self.thread_last_mode {
6394 args.push(lhs);
6395 } else {
6396 args.insert(0, lhs);
6397 }
6398 ExprKind::Filesf(args)
6399 }
6400 ExprKind::FilesfRecursive(mut args) => {
6401 if self.thread_last_mode {
6402 args.push(lhs);
6403 } else {
6404 args.insert(0, lhs);
6405 }
6406 ExprKind::FilesfRecursive(args)
6407 }
6408 ExprKind::Dirs(mut args) => {
6409 if self.thread_last_mode {
6410 args.push(lhs);
6411 } else {
6412 args.insert(0, lhs);
6413 }
6414 ExprKind::Dirs(args)
6415 }
6416 ExprKind::DirsRecursive(mut args) => {
6417 if self.thread_last_mode {
6418 args.push(lhs);
6419 } else {
6420 args.insert(0, lhs);
6421 }
6422 ExprKind::DirsRecursive(args)
6423 }
6424 ExprKind::SymLinks(mut args) => {
6425 if self.thread_last_mode {
6426 args.push(lhs);
6427 } else {
6428 args.insert(0, lhs);
6429 }
6430 ExprKind::SymLinks(args)
6431 }
6432 ExprKind::Sockets(mut args) => {
6433 if self.thread_last_mode {
6434 args.push(lhs);
6435 } else {
6436 args.insert(0, lhs);
6437 }
6438 ExprKind::Sockets(args)
6439 }
6440 ExprKind::Pipes(mut args) => {
6441 if self.thread_last_mode {
6442 args.push(lhs);
6443 } else {
6444 args.insert(0, lhs);
6445 }
6446 ExprKind::Pipes(args)
6447 }
6448 ExprKind::BlockDevices(mut args) => {
6449 if self.thread_last_mode {
6450 args.push(lhs);
6451 } else {
6452 args.insert(0, lhs);
6453 }
6454 ExprKind::BlockDevices(args)
6455 }
6456 ExprKind::CharDevices(mut args) => {
6457 if self.thread_last_mode {
6458 args.push(lhs);
6459 } else {
6460 args.insert(0, lhs);
6461 }
6462 ExprKind::CharDevices(args)
6463 }
6464 ExprKind::GlobPar { mut args, progress } => {
6465 if self.thread_last_mode {
6466 args.push(lhs);
6467 } else {
6468 args.insert(0, lhs);
6469 }
6470 ExprKind::GlobPar { args, progress }
6471 }
6472 ExprKind::ParSed { mut args, progress } => {
6473 if self.thread_last_mode {
6474 args.push(lhs);
6475 } else {
6476 args.insert(0, lhs);
6477 }
6478 ExprKind::ParSed { args, progress }
6479 }
6480
6481 ExprKind::Length(_) => ExprKind::Length(Box::new(lhs)),
6483 ExprKind::Abs(_) => ExprKind::Abs(Box::new(lhs)),
6484 ExprKind::Int(_) => ExprKind::Int(Box::new(lhs)),
6485 ExprKind::Sqrt(_) => ExprKind::Sqrt(Box::new(lhs)),
6486 ExprKind::Sin(_) => ExprKind::Sin(Box::new(lhs)),
6487 ExprKind::Cos(_) => ExprKind::Cos(Box::new(lhs)),
6488 ExprKind::Exp(_) => ExprKind::Exp(Box::new(lhs)),
6489 ExprKind::Log(_) => ExprKind::Log(Box::new(lhs)),
6490 ExprKind::Hex(_) => ExprKind::Hex(Box::new(lhs)),
6491 ExprKind::Oct(_) => ExprKind::Oct(Box::new(lhs)),
6492 ExprKind::Lc(_) => ExprKind::Lc(Box::new(lhs)),
6493 ExprKind::Uc(_) => ExprKind::Uc(Box::new(lhs)),
6494 ExprKind::Lcfirst(_) => ExprKind::Lcfirst(Box::new(lhs)),
6495 ExprKind::Ucfirst(_) => ExprKind::Ucfirst(Box::new(lhs)),
6496 ExprKind::Fc(_) => ExprKind::Fc(Box::new(lhs)),
6497 ExprKind::Chr(_) => ExprKind::Chr(Box::new(lhs)),
6498 ExprKind::Ord(_) => ExprKind::Ord(Box::new(lhs)),
6499 ExprKind::Chomp(_) => ExprKind::Chomp(Box::new(lhs)),
6500 ExprKind::Chop(_) => ExprKind::Chop(Box::new(lhs)),
6501 ExprKind::Defined(_) => ExprKind::Defined(Box::new(lhs)),
6502 ExprKind::Ref(_) => ExprKind::Ref(Box::new(lhs)),
6503 ExprKind::ScalarContext(_) => ExprKind::ScalarContext(Box::new(lhs)),
6504 ExprKind::Keys(_) => ExprKind::Keys(Box::new(lhs)),
6505 ExprKind::Values(_) => ExprKind::Values(Box::new(lhs)),
6506 ExprKind::Each(_) => ExprKind::Each(Box::new(lhs)),
6507 ExprKind::Pop(_) => ExprKind::Pop(Box::new(lhs)),
6508 ExprKind::Shift(_) => ExprKind::Shift(Box::new(lhs)),
6509 ExprKind::Delete(_) => ExprKind::Delete(Box::new(lhs)),
6510 ExprKind::Exists(_) => ExprKind::Exists(Box::new(lhs)),
6511 ExprKind::ReverseExpr(_) => ExprKind::ReverseExpr(Box::new(lhs)),
6512 ExprKind::Rev(_) => ExprKind::Rev(Box::new(lhs)),
6513 ExprKind::Slurp(_) => ExprKind::Slurp(Box::new(lhs)),
6514 ExprKind::Capture(_) => ExprKind::Capture(Box::new(lhs)),
6515 ExprKind::Qx(_) => ExprKind::Qx(Box::new(lhs)),
6516 ExprKind::FetchUrl(_) => ExprKind::FetchUrl(Box::new(lhs)),
6517 ExprKind::Close(_) => ExprKind::Close(Box::new(lhs)),
6518 ExprKind::Chdir(_) => ExprKind::Chdir(Box::new(lhs)),
6519 ExprKind::Readdir(_) => ExprKind::Readdir(Box::new(lhs)),
6520 ExprKind::Closedir(_) => ExprKind::Closedir(Box::new(lhs)),
6521 ExprKind::Rewinddir(_) => ExprKind::Rewinddir(Box::new(lhs)),
6522 ExprKind::Telldir(_) => ExprKind::Telldir(Box::new(lhs)),
6523 ExprKind::Stat(_) => ExprKind::Stat(Box::new(lhs)),
6524 ExprKind::Lstat(_) => ExprKind::Lstat(Box::new(lhs)),
6525 ExprKind::Readlink(_) => ExprKind::Readlink(Box::new(lhs)),
6526 ExprKind::Study(_) => ExprKind::Study(Box::new(lhs)),
6527 ExprKind::Await(_) => ExprKind::Await(Box::new(lhs)),
6528 ExprKind::Eval(_) => ExprKind::Eval(Box::new(lhs)),
6529 ExprKind::Rand(_) => ExprKind::Rand(Some(Box::new(lhs))),
6530 ExprKind::Srand(_) => ExprKind::Srand(Some(Box::new(lhs))),
6531 ExprKind::Pos(_) => ExprKind::Pos(Some(Box::new(lhs))),
6532 ExprKind::Exit(_) => ExprKind::Exit(Some(Box::new(lhs))),
6533
6534 ExprKind::MapExpr {
6536 block,
6537 list: _,
6538 flatten_array_refs,
6539 stream,
6540 } => ExprKind::MapExpr {
6541 block,
6542 list: Box::new(lhs),
6543 flatten_array_refs,
6544 stream,
6545 },
6546 ExprKind::MapExprComma {
6547 expr,
6548 list: _,
6549 flatten_array_refs,
6550 stream,
6551 } => ExprKind::MapExprComma {
6552 expr,
6553 list: Box::new(lhs),
6554 flatten_array_refs,
6555 stream,
6556 },
6557 ExprKind::GrepExpr {
6558 block,
6559 list: _,
6560 keyword,
6561 } => ExprKind::GrepExpr {
6562 block,
6563 list: Box::new(lhs),
6564 keyword,
6565 },
6566 ExprKind::GrepExprComma {
6567 expr,
6568 list: _,
6569 keyword,
6570 } => ExprKind::GrepExprComma {
6571 expr,
6572 list: Box::new(lhs),
6573 keyword,
6574 },
6575 ExprKind::ForEachExpr { block, list: _ } => ExprKind::ForEachExpr {
6576 block,
6577 list: Box::new(lhs),
6578 },
6579 ExprKind::SortExpr { cmp, list: _ } => ExprKind::SortExpr {
6580 cmp,
6581 list: Box::new(lhs),
6582 },
6583 ExprKind::JoinExpr { separator, list: _ } => ExprKind::JoinExpr {
6584 separator,
6585 list: Box::new(lhs),
6586 },
6587 ExprKind::ReduceExpr { block, list: _ } => ExprKind::ReduceExpr {
6588 block,
6589 list: Box::new(lhs),
6590 },
6591 ExprKind::PMapExpr {
6592 block,
6593 list: _,
6594 progress,
6595 flat_outputs,
6596 on_cluster,
6597 stream,
6598 } => ExprKind::PMapExpr {
6599 block,
6600 list: Box::new(lhs),
6601 progress,
6602 flat_outputs,
6603 on_cluster,
6604 stream,
6605 },
6606 ExprKind::PMapChunkedExpr {
6607 chunk_size,
6608 block,
6609 list: _,
6610 progress,
6611 } => ExprKind::PMapChunkedExpr {
6612 chunk_size,
6613 block,
6614 list: Box::new(lhs),
6615 progress,
6616 },
6617 ExprKind::PGrepExpr {
6618 block,
6619 list: _,
6620 progress,
6621 stream,
6622 } => ExprKind::PGrepExpr {
6623 block,
6624 list: Box::new(lhs),
6625 progress,
6626 stream,
6627 },
6628 ExprKind::PForExpr {
6629 block,
6630 list: _,
6631 progress,
6632 } => ExprKind::PForExpr {
6633 block,
6634 list: Box::new(lhs),
6635 progress,
6636 },
6637 ExprKind::PSortExpr {
6638 cmp,
6639 list: _,
6640 progress,
6641 } => ExprKind::PSortExpr {
6642 cmp,
6643 list: Box::new(lhs),
6644 progress,
6645 },
6646 ExprKind::PReduceExpr {
6647 block,
6648 list: _,
6649 progress,
6650 } => ExprKind::PReduceExpr {
6651 block,
6652 list: Box::new(lhs),
6653 progress,
6654 },
6655 ExprKind::PcacheExpr {
6656 block,
6657 list: _,
6658 progress,
6659 } => ExprKind::PcacheExpr {
6660 block,
6661 list: Box::new(lhs),
6662 progress,
6663 },
6664 ExprKind::PReduceInitExpr {
6665 init,
6666 block,
6667 list: _,
6668 progress,
6669 } => ExprKind::PReduceInitExpr {
6670 init,
6671 block,
6672 list: Box::new(lhs),
6673 progress,
6674 },
6675 ExprKind::PMapReduceExpr {
6676 map_block,
6677 reduce_block,
6678 list: _,
6679 progress,
6680 } => ExprKind::PMapReduceExpr {
6681 map_block,
6682 reduce_block,
6683 list: Box::new(lhs),
6684 progress,
6685 },
6686
6687 ExprKind::Push { array, mut values } => {
6692 values.insert(0, lhs);
6693 ExprKind::Push { array, values }
6694 }
6695 ExprKind::Unshift { array, mut values } => {
6696 values.insert(0, lhs);
6697 ExprKind::Unshift { array, values }
6698 }
6699
6700 ExprKind::SplitExpr {
6702 pattern,
6703 string: _,
6704 limit,
6705 } => ExprKind::SplitExpr {
6706 pattern,
6707 string: Box::new(lhs),
6708 limit,
6709 },
6710
6711 ExprKind::Substitution {
6715 pattern,
6716 replacement,
6717 mut flags,
6718 expr: _,
6719 delim,
6720 } => {
6721 if !flags.contains('r') {
6722 flags.push('r');
6723 }
6724 ExprKind::Substitution {
6725 expr: Box::new(lhs),
6726 pattern,
6727 replacement,
6728 flags,
6729 delim,
6730 }
6731 }
6732 ExprKind::Transliterate {
6733 from,
6734 to,
6735 mut flags,
6736 expr: _,
6737 delim,
6738 } => {
6739 if !flags.contains('r') {
6740 flags.push('r');
6741 }
6742 ExprKind::Transliterate {
6743 expr: Box::new(lhs),
6744 from,
6745 to,
6746 flags,
6747 delim,
6748 }
6749 }
6750 ExprKind::Match {
6751 pattern,
6752 flags,
6753 scalar_g,
6754 expr: _,
6755 delim,
6756 } => ExprKind::Match {
6757 expr: Box::new(lhs),
6758 pattern,
6759 flags,
6760 scalar_g,
6761 delim,
6762 },
6763 ExprKind::Regex(pattern, flags) => ExprKind::Match {
6765 expr: Box::new(lhs),
6766 pattern,
6767 flags,
6768 scalar_g: false,
6769 delim: '/',
6770 },
6771
6772 ExprKind::Bareword(name) => match name.as_str() {
6774 "reverse" => {
6775 if crate::no_interop_mode() {
6776 return Err(self.syntax_err(
6777 "stryke uses `rev` instead of `reverse` (--no-interop)",
6778 line,
6779 ));
6780 }
6781 ExprKind::ReverseExpr(Box::new(lhs))
6782 }
6783 "rv" | "reversed" | "rev" => ExprKind::Rev(Box::new(lhs)),
6784 "uq" | "uniq" | "distinct" => ExprKind::FuncCall {
6785 name: "uniq".to_string(),
6786 args: vec![lhs],
6787 },
6788 "fl" | "flatten" => ExprKind::FuncCall {
6789 name: "flatten".to_string(),
6790 args: vec![lhs],
6791 },
6792 _ => ExprKind::FuncCall {
6793 name,
6794 args: vec![lhs],
6795 },
6796 },
6797
6798 kind @ (ExprKind::ScalarVar(_)
6800 | ExprKind::ArrayElement { .. }
6801 | ExprKind::HashElement { .. }
6802 | ExprKind::Deref { .. }
6803 | ExprKind::ArrowDeref { .. }
6804 | ExprKind::CodeRef { .. }
6805 | ExprKind::SubroutineRef(_)
6806 | ExprKind::SubroutineCodeRef(_)
6807 | ExprKind::DynamicSubCodeRef(_)) => ExprKind::IndirectCall {
6808 target: Box::new(Expr { kind, line: rline }),
6809 args: vec![lhs],
6810 ampersand: false,
6811 pass_caller_arglist: false,
6812 },
6813
6814 ExprKind::Do(inner) if matches!(inner.kind, ExprKind::CodeRef { .. }) => {
6818 ExprKind::IndirectCall {
6819 target: inner,
6820 args: vec![lhs],
6821 ampersand: false,
6822 pass_caller_arglist: false,
6823 }
6824 }
6825
6826 other => {
6827 return Err(self.syntax_err(
6828 format!(
6829 "right-hand side of `|>` must be a call, builtin, or coderef \
6830 expression (got {})",
6831 Self::expr_kind_name(&other)
6832 ),
6833 line,
6834 ));
6835 }
6836 };
6837 Ok(Expr {
6838 kind: new_kind,
6839 line,
6840 })
6841 }
6842
6843 fn expr_kind_name(kind: &ExprKind) -> &'static str {
6845 match kind {
6846 ExprKind::Integer(_) | ExprKind::Float(_) => "numeric literal",
6847 ExprKind::String(_) | ExprKind::InterpolatedString(_) => "string literal",
6848 ExprKind::BinOp { .. } => "binary expression",
6849 ExprKind::UnaryOp { .. } => "unary expression",
6850 ExprKind::Ternary { .. } => "ternary expression",
6851 ExprKind::Assign { .. } | ExprKind::CompoundAssign { .. } => "assignment",
6852 ExprKind::List(_) => "list expression",
6853 ExprKind::Range { .. } => "range expression",
6854 _ => "expression",
6855 }
6856 }
6857
6858 fn parse_or_word(&mut self) -> PerlResult<Expr> {
6860 let mut left = self.parse_and_word()?;
6861 while matches!(self.peek(), Token::LogOrWord) {
6862 let line = left.line;
6863 self.advance();
6864 let right = self.parse_and_word()?;
6865 left = Expr {
6866 kind: ExprKind::BinOp {
6867 left: Box::new(left),
6868 op: BinOp::LogOrWord,
6869 right: Box::new(right),
6870 },
6871 line,
6872 };
6873 }
6874 Ok(left)
6875 }
6876
6877 fn parse_and_word(&mut self) -> PerlResult<Expr> {
6878 let mut left = self.parse_not_word()?;
6879 while matches!(self.peek(), Token::LogAndWord) {
6880 let line = left.line;
6881 self.advance();
6882 let right = self.parse_not_word()?;
6883 left = Expr {
6884 kind: ExprKind::BinOp {
6885 left: Box::new(left),
6886 op: BinOp::LogAndWord,
6887 right: Box::new(right),
6888 },
6889 line,
6890 };
6891 }
6892 Ok(left)
6893 }
6894
6895 fn parse_not_word(&mut self) -> PerlResult<Expr> {
6896 if matches!(self.peek(), Token::LogNotWord) {
6897 let line = self.peek_line();
6898 self.advance();
6899 let expr = self.parse_not_word()?;
6900 return Ok(Expr {
6901 kind: ExprKind::UnaryOp {
6902 op: UnaryOp::LogNotWord,
6903 expr: Box::new(expr),
6904 },
6905 line,
6906 });
6907 }
6908 self.parse_range()
6909 }
6910
6911 fn parse_log_or(&mut self) -> PerlResult<Expr> {
6912 let mut left = self.parse_log_and()?;
6913 loop {
6914 let op = match self.peek() {
6915 Token::LogOr => BinOp::LogOr,
6916 Token::DefinedOr => BinOp::DefinedOr,
6917 _ => break,
6918 };
6919 let line = left.line;
6920 self.advance();
6921 let right = self.parse_log_and()?;
6922 left = Expr {
6923 kind: ExprKind::BinOp {
6924 left: Box::new(left),
6925 op,
6926 right: Box::new(right),
6927 },
6928 line,
6929 };
6930 }
6931 Ok(left)
6932 }
6933
6934 fn parse_log_and(&mut self) -> PerlResult<Expr> {
6935 let mut left = self.parse_bit_or()?;
6936 while matches!(self.peek(), Token::LogAnd) {
6937 let line = left.line;
6938 self.advance();
6939 let right = self.parse_bit_or()?;
6940 left = Expr {
6941 kind: ExprKind::BinOp {
6942 left: Box::new(left),
6943 op: BinOp::LogAnd,
6944 right: Box::new(right),
6945 },
6946 line,
6947 };
6948 }
6949 Ok(left)
6950 }
6951
6952 fn parse_bit_or(&mut self) -> PerlResult<Expr> {
6953 let mut left = self.parse_bit_xor()?;
6954 while matches!(self.peek(), Token::BitOr) {
6955 let line = left.line;
6956 self.advance();
6957 let right = self.parse_bit_xor()?;
6958 left = Expr {
6959 kind: ExprKind::BinOp {
6960 left: Box::new(left),
6961 op: BinOp::BitOr,
6962 right: Box::new(right),
6963 },
6964 line,
6965 };
6966 }
6967 Ok(left)
6968 }
6969
6970 fn parse_bit_xor(&mut self) -> PerlResult<Expr> {
6971 let mut left = self.parse_bit_and()?;
6972 while matches!(self.peek(), Token::BitXor) {
6973 let line = left.line;
6974 self.advance();
6975 let right = self.parse_bit_and()?;
6976 left = Expr {
6977 kind: ExprKind::BinOp {
6978 left: Box::new(left),
6979 op: BinOp::BitXor,
6980 right: Box::new(right),
6981 },
6982 line,
6983 };
6984 }
6985 Ok(left)
6986 }
6987
6988 fn parse_bit_and(&mut self) -> PerlResult<Expr> {
6989 let mut left = self.parse_equality()?;
6990 while matches!(self.peek(), Token::BitAnd) {
6991 let line = left.line;
6992 self.advance();
6993 let right = self.parse_equality()?;
6994 left = Expr {
6995 kind: ExprKind::BinOp {
6996 left: Box::new(left),
6997 op: BinOp::BitAnd,
6998 right: Box::new(right),
6999 },
7000 line,
7001 };
7002 }
7003 Ok(left)
7004 }
7005
7006 fn parse_equality(&mut self) -> PerlResult<Expr> {
7007 let mut left = self.parse_comparison()?;
7008 loop {
7009 let op = match self.peek() {
7010 Token::NumEq => BinOp::NumEq,
7011 Token::NumNe => BinOp::NumNe,
7012 Token::StrEq => BinOp::StrEq,
7013 Token::StrNe => BinOp::StrNe,
7014 Token::Spaceship => BinOp::Spaceship,
7015 Token::StrCmp => BinOp::StrCmp,
7016 _ => break,
7017 };
7018 let line = left.line;
7019 self.advance();
7020 let right = self.parse_comparison()?;
7021 left = Expr {
7022 kind: ExprKind::BinOp {
7023 left: Box::new(left),
7024 op,
7025 right: Box::new(right),
7026 },
7027 line,
7028 };
7029 }
7030 Ok(left)
7031 }
7032
7033 fn parse_comparison(&mut self) -> PerlResult<Expr> {
7034 let left = self.parse_shift()?;
7035 let first_op = match self.peek() {
7036 Token::NumLt => BinOp::NumLt,
7037 Token::NumGt => BinOp::NumGt,
7038 Token::NumLe => BinOp::NumLe,
7039 Token::NumGe => BinOp::NumGe,
7040 Token::StrLt => BinOp::StrLt,
7041 Token::StrGt => BinOp::StrGt,
7042 Token::StrLe => BinOp::StrLe,
7043 Token::StrGe => BinOp::StrGe,
7044 _ => return Ok(left),
7045 };
7046 let line = left.line;
7047 self.advance();
7048 let middle = self.parse_shift()?;
7049
7050 let second_op = match self.peek() {
7051 Token::NumLt => Some(BinOp::NumLt),
7052 Token::NumGt => Some(BinOp::NumGt),
7053 Token::NumLe => Some(BinOp::NumLe),
7054 Token::NumGe => Some(BinOp::NumGe),
7055 Token::StrLt => Some(BinOp::StrLt),
7056 Token::StrGt => Some(BinOp::StrGt),
7057 Token::StrLe => Some(BinOp::StrLe),
7058 Token::StrGe => Some(BinOp::StrGe),
7059 _ => None,
7060 };
7061
7062 if second_op.is_none() {
7063 return Ok(Expr {
7064 kind: ExprKind::BinOp {
7065 left: Box::new(left),
7066 op: first_op,
7067 right: Box::new(middle),
7068 },
7069 line,
7070 });
7071 }
7072
7073 let mut operands = vec![left, middle];
7076 let mut ops = vec![first_op];
7077
7078 loop {
7079 let op = match self.peek() {
7080 Token::NumLt => BinOp::NumLt,
7081 Token::NumGt => BinOp::NumGt,
7082 Token::NumLe => BinOp::NumLe,
7083 Token::NumGe => BinOp::NumGe,
7084 Token::StrLt => BinOp::StrLt,
7085 Token::StrGt => BinOp::StrGt,
7086 Token::StrLe => BinOp::StrLe,
7087 Token::StrGe => BinOp::StrGe,
7088 _ => break,
7089 };
7090 self.advance();
7091 ops.push(op);
7092 operands.push(self.parse_shift()?);
7093 }
7094
7095 let mut result = Expr {
7097 kind: ExprKind::BinOp {
7098 left: Box::new(operands[0].clone()),
7099 op: ops[0],
7100 right: Box::new(operands[1].clone()),
7101 },
7102 line,
7103 };
7104
7105 for i in 1..ops.len() {
7106 let cmp = Expr {
7107 kind: ExprKind::BinOp {
7108 left: Box::new(operands[i].clone()),
7109 op: ops[i],
7110 right: Box::new(operands[i + 1].clone()),
7111 },
7112 line,
7113 };
7114 result = Expr {
7115 kind: ExprKind::BinOp {
7116 left: Box::new(result),
7117 op: BinOp::LogAnd,
7118 right: Box::new(cmp),
7119 },
7120 line,
7121 };
7122 }
7123
7124 Ok(result)
7125 }
7126
7127 fn parse_shift(&mut self) -> PerlResult<Expr> {
7128 let mut left = self.parse_addition()?;
7129 loop {
7130 let op = match self.peek() {
7131 Token::ShiftLeft => BinOp::ShiftLeft,
7132 Token::ShiftRight => BinOp::ShiftRight,
7133 _ => break,
7134 };
7135 let line = left.line;
7136 self.advance();
7137 let right = self.parse_addition()?;
7138 left = Expr {
7139 kind: ExprKind::BinOp {
7140 left: Box::new(left),
7141 op,
7142 right: Box::new(right),
7143 },
7144 line,
7145 };
7146 }
7147 Ok(left)
7148 }
7149
7150 fn parse_addition(&mut self) -> PerlResult<Expr> {
7151 let mut left = self.parse_multiplication()?;
7152 loop {
7153 let op = match self.peek() {
7156 Token::Plus if self.peek_line() == self.prev_line() => BinOp::Add,
7157 Token::Minus if self.peek_line() == self.prev_line() => BinOp::Sub,
7158 Token::Dot => BinOp::Concat,
7159 _ => break,
7160 };
7161 let line = left.line;
7162 self.advance();
7163 let right = self.parse_multiplication()?;
7164 left = Expr {
7165 kind: ExprKind::BinOp {
7166 left: Box::new(left),
7167 op,
7168 right: Box::new(right),
7169 },
7170 line,
7171 };
7172 }
7173 Ok(left)
7174 }
7175
7176 fn parse_multiplication(&mut self) -> PerlResult<Expr> {
7177 let mut left = self.parse_regex_bind()?;
7178 loop {
7179 let op = match self.peek() {
7180 Token::Star => BinOp::Mul,
7181 Token::Slash if self.suppress_slash_as_div == 0 => BinOp::Div,
7182 Token::Percent if self.peek_line() == self.prev_line() => BinOp::Mod,
7185 Token::X => {
7186 let line = left.line;
7187 let list_repeat = self.list_construct_close_pos == Some(self.pos);
7194 self.advance();
7195 let right = self.parse_regex_bind()?;
7196 left = Expr {
7197 kind: ExprKind::Repeat {
7198 expr: Box::new(left),
7199 count: Box::new(right),
7200 list_repeat,
7201 },
7202 line,
7203 };
7204 continue;
7205 }
7206 _ => break,
7207 };
7208 let line = left.line;
7209 self.advance();
7210 let right = self.parse_regex_bind()?;
7211 left = Expr {
7212 kind: ExprKind::BinOp {
7213 left: Box::new(left),
7214 op,
7215 right: Box::new(right),
7216 },
7217 line,
7218 };
7219 }
7220 Ok(left)
7221 }
7222
7223 fn parse_regex_bind(&mut self) -> PerlResult<Expr> {
7224 let left = self.parse_unary()?;
7225 match self.peek() {
7226 Token::BindMatch => {
7227 let line = left.line;
7228 self.advance();
7229 match self.peek().clone() {
7230 Token::Regex(pattern, flags, delim) => {
7231 self.advance();
7232 Ok(Expr {
7233 kind: ExprKind::Match {
7234 expr: Box::new(left),
7235 pattern,
7236 flags,
7237 scalar_g: false,
7238 delim,
7239 },
7240 line,
7241 })
7242 }
7243 Token::Ident(ref s) if s.starts_with('\x00') => {
7244 let (Token::Ident(encoded), _) = self.advance() else {
7245 unreachable!()
7246 };
7247 let parts: Vec<&str> = encoded.split('\x00').collect();
7248 if parts.len() >= 4 && parts[1] == "s" {
7249 let delim = parts.get(5).and_then(|s| s.chars().next()).unwrap_or('/');
7250 Ok(Expr {
7251 kind: ExprKind::Substitution {
7252 expr: Box::new(left),
7253 pattern: parts[2].to_string(),
7254 replacement: parts[3].to_string(),
7255 flags: parts.get(4).unwrap_or(&"").to_string(),
7256 delim,
7257 },
7258 line,
7259 })
7260 } else if parts.len() >= 4 && parts[1] == "tr" {
7261 let delim = parts.get(5).and_then(|s| s.chars().next()).unwrap_or('/');
7262 Ok(Expr {
7263 kind: ExprKind::Transliterate {
7264 expr: Box::new(left),
7265 from: parts[2].to_string(),
7266 to: parts[3].to_string(),
7267 flags: parts.get(4).unwrap_or(&"").to_string(),
7268 delim,
7269 },
7270 line,
7271 })
7272 } else {
7273 Err(self.syntax_err("Invalid regex binding", line))
7274 }
7275 }
7276 _ => {
7277 let rhs = self.parse_unary()?;
7278 Ok(Expr {
7279 kind: ExprKind::BinOp {
7280 left: Box::new(left),
7281 op: BinOp::BindMatch,
7282 right: Box::new(rhs),
7283 },
7284 line,
7285 })
7286 }
7287 }
7288 }
7289 Token::BindNotMatch => {
7290 let line = left.line;
7291 self.advance();
7292 match self.peek().clone() {
7293 Token::Regex(pattern, flags, delim) => {
7294 self.advance();
7295 Ok(Expr {
7296 kind: ExprKind::UnaryOp {
7297 op: UnaryOp::LogNot,
7298 expr: Box::new(Expr {
7299 kind: ExprKind::Match {
7300 expr: Box::new(left),
7301 pattern,
7302 flags,
7303 scalar_g: false,
7304 delim,
7305 },
7306 line,
7307 }),
7308 },
7309 line,
7310 })
7311 }
7312 Token::Ident(ref s) if s.starts_with('\x00') => {
7313 let (Token::Ident(encoded), _) = self.advance() else {
7314 unreachable!()
7315 };
7316 let parts: Vec<&str> = encoded.split('\x00').collect();
7317 if parts.len() >= 4 && parts[1] == "s" {
7318 let delim = parts.get(5).and_then(|s| s.chars().next()).unwrap_or('/');
7319 Ok(Expr {
7320 kind: ExprKind::UnaryOp {
7321 op: UnaryOp::LogNot,
7322 expr: Box::new(Expr {
7323 kind: ExprKind::Substitution {
7324 expr: Box::new(left),
7325 pattern: parts[2].to_string(),
7326 replacement: parts[3].to_string(),
7327 flags: parts.get(4).unwrap_or(&"").to_string(),
7328 delim,
7329 },
7330 line,
7331 }),
7332 },
7333 line,
7334 })
7335 } else if parts.len() >= 4 && parts[1] == "tr" {
7336 let delim = parts.get(5).and_then(|s| s.chars().next()).unwrap_or('/');
7337 Ok(Expr {
7338 kind: ExprKind::UnaryOp {
7339 op: UnaryOp::LogNot,
7340 expr: Box::new(Expr {
7341 kind: ExprKind::Transliterate {
7342 expr: Box::new(left),
7343 from: parts[2].to_string(),
7344 to: parts[3].to_string(),
7345 flags: parts.get(4).unwrap_or(&"").to_string(),
7346 delim,
7347 },
7348 line,
7349 }),
7350 },
7351 line,
7352 })
7353 } else {
7354 Err(self.syntax_err("Invalid regex binding after !~", line))
7355 }
7356 }
7357 _ => {
7358 let rhs = self.parse_unary()?;
7359 Ok(Expr {
7360 kind: ExprKind::BinOp {
7361 left: Box::new(left),
7362 op: BinOp::BindNotMatch,
7363 right: Box::new(rhs),
7364 },
7365 line,
7366 })
7367 }
7368 }
7369 }
7370 _ => Ok(left),
7371 }
7372 }
7373
7374 fn parse_thread_input(&mut self) -> PerlResult<Expr> {
7377 self.suppress_slash_as_div = self.suppress_slash_as_div.saturating_add(1);
7378 let result = self.parse_range();
7379 self.suppress_slash_as_div = self.suppress_slash_as_div.saturating_sub(1);
7380 result
7381 }
7382
7383 fn parse_range(&mut self) -> PerlResult<Expr> {
7390 let left = self.parse_log_or()?;
7391 let line = left.line;
7392 let (exclusive, _colon_style) = if self.eat(&Token::RangeExclusive) {
7399 (true, false)
7400 } else if self.eat(&Token::Range) {
7401 (false, false)
7402 } else if self.suppress_colon_range == 0 && self.eat(&Token::Colon) {
7403 (false, true)
7406 } else if self.suppress_tilde_range == 0 && self.eat(&Token::BitNot) {
7407 (false, true)
7408 } else {
7409 return Ok(left);
7410 };
7411 let right = self.parse_log_or()?;
7412 let step = if self.eat(&Token::Colon)
7416 || (self.suppress_tilde_range == 0 && self.eat(&Token::BitNot))
7417 {
7418 Some(Box::new(self.parse_unary()?))
7419 } else {
7420 None
7421 };
7422 Ok(Expr {
7423 kind: ExprKind::Range {
7424 from: Box::new(left),
7425 to: Box::new(right),
7426 exclusive,
7427 step,
7428 },
7429 line,
7430 })
7431 }
7432
7433 fn parse_package_qualified_identifier(&mut self) -> PerlResult<String> {
7435 let mut name = match self.advance() {
7436 (Token::Ident(n), _) => n,
7437 (tok, l) => {
7438 return Err(self.syntax_err(format!("Expected identifier, got {:?}", tok), l));
7439 }
7440 };
7441 while self.eat(&Token::PackageSep) {
7442 match self.advance() {
7443 (Token::Ident(part), _) => {
7444 name.push_str("::");
7445 name.push_str(&part);
7446 }
7447 (tok, l) => {
7448 return Err(self
7449 .syntax_err(format!("Expected identifier after `::`, got {:?}", tok), l));
7450 }
7451 }
7452 }
7453 Ok(name)
7454 }
7455
7456 fn parse_qualified_subroutine_name(&mut self) -> PerlResult<String> {
7458 self.parse_package_qualified_identifier()
7459 }
7460
7461 fn parse_unary(&mut self) -> PerlResult<Expr> {
7462 let line = self.peek_line();
7463 match self.peek().clone() {
7464 Token::Minus => {
7465 self.advance();
7466 let expr = self.parse_power()?;
7467 Ok(Expr {
7468 kind: ExprKind::UnaryOp {
7469 op: UnaryOp::Negate,
7470 expr: Box::new(expr),
7471 },
7472 line,
7473 })
7474 }
7475 Token::Plus => {
7482 self.advance();
7483 if matches!(self.peek(), Token::LBrace) {
7484 let line = self.peek_line();
7485 self.advance(); return self.parse_forced_hashref_body(line);
7487 }
7488 self.parse_unary()
7489 }
7490 Token::LogNot => {
7491 self.advance();
7492 let expr = self.parse_unary()?;
7493 Ok(Expr {
7494 kind: ExprKind::UnaryOp {
7495 op: UnaryOp::LogNot,
7496 expr: Box::new(expr),
7497 },
7498 line,
7499 })
7500 }
7501 Token::BitNot => {
7502 self.advance();
7503 let expr = self.parse_unary()?;
7504 Ok(Expr {
7505 kind: ExprKind::UnaryOp {
7506 op: UnaryOp::BitNot,
7507 expr: Box::new(expr),
7508 },
7509 line,
7510 })
7511 }
7512 Token::Increment => {
7513 self.advance();
7514 let expr = self.parse_postfix()?;
7515 Ok(Expr {
7516 kind: ExprKind::UnaryOp {
7517 op: UnaryOp::PreIncrement,
7518 expr: Box::new(expr),
7519 },
7520 line,
7521 })
7522 }
7523 Token::Decrement => {
7524 self.advance();
7525 let expr = self.parse_postfix()?;
7526 Ok(Expr {
7527 kind: ExprKind::UnaryOp {
7528 op: UnaryOp::PreDecrement,
7529 expr: Box::new(expr),
7530 },
7531 line,
7532 })
7533 }
7534 Token::BitAnd => {
7535 self.advance();
7538 if matches!(self.peek(), Token::LBrace) {
7539 self.advance();
7540 let inner = self.parse_expression()?;
7541 self.expect(&Token::RBrace)?;
7542 return Ok(Expr {
7543 kind: ExprKind::DynamicSubCodeRef(Box::new(inner)),
7544 line,
7545 });
7546 }
7547 if matches!(self.peek(), Token::Ident(_)) {
7548 let name = self.parse_qualified_subroutine_name()?;
7549 return Ok(Expr {
7550 kind: ExprKind::SubroutineRef(name),
7551 line,
7552 });
7553 }
7554 let target = self.parse_primary()?;
7555 if matches!(self.peek(), Token::LParen) {
7556 self.advance();
7557 let args = self.parse_arg_list()?;
7558 self.expect(&Token::RParen)?;
7559 return Ok(Expr {
7560 kind: ExprKind::IndirectCall {
7561 target: Box::new(target),
7562 args,
7563 ampersand: true,
7564 pass_caller_arglist: false,
7565 },
7566 line,
7567 });
7568 }
7569 Ok(Expr {
7571 kind: ExprKind::IndirectCall {
7572 target: Box::new(target),
7573 args: vec![],
7574 ampersand: true,
7575 pass_caller_arglist: true,
7576 },
7577 line,
7578 })
7579 }
7580 Token::Backslash => {
7581 self.advance();
7582 let expr = self.parse_unary()?;
7583 if let ExprKind::SubroutineRef(name) = expr.kind {
7584 return Ok(Expr {
7585 kind: ExprKind::SubroutineCodeRef(name),
7586 line,
7587 });
7588 }
7589 if matches!(expr.kind, ExprKind::DynamicSubCodeRef(_)) {
7590 return Ok(expr);
7591 }
7592 Ok(Expr {
7594 kind: ExprKind::ScalarRef(Box::new(expr)),
7595 line,
7596 })
7597 }
7598 Token::FileTest(op) => {
7599 self.advance();
7600 let expr = if Self::filetest_allows_implicit_topic(self.peek()) {
7602 Expr {
7603 kind: ExprKind::ScalarVar("_".into()),
7604 line: self.peek_line(),
7605 }
7606 } else {
7607 self.parse_unary()?
7608 };
7609 Ok(Expr {
7610 kind: ExprKind::FileTest {
7611 op,
7612 expr: Box::new(expr),
7613 },
7614 line,
7615 })
7616 }
7617 _ => self.parse_power(),
7618 }
7619 }
7620
7621 fn parse_power(&mut self) -> PerlResult<Expr> {
7622 let left = self.parse_postfix()?;
7623 if matches!(self.peek(), Token::Power) {
7624 let line = left.line;
7625 self.advance();
7626 let right = self.parse_unary()?; return Ok(Expr {
7628 kind: ExprKind::BinOp {
7629 left: Box::new(left),
7630 op: BinOp::Pow,
7631 right: Box::new(right),
7632 },
7633 line,
7634 });
7635 }
7636 Ok(left)
7637 }
7638
7639 fn parse_postfix(&mut self) -> PerlResult<Expr> {
7640 let mut expr = self.parse_primary()?;
7641 loop {
7642 match self.peek().clone() {
7643 Token::Increment => {
7644 if self.peek_line() > self.prev_line() {
7647 break;
7648 }
7649 let line = expr.line;
7650 self.advance();
7651 expr = Expr {
7652 kind: ExprKind::PostfixOp {
7653 expr: Box::new(expr),
7654 op: PostfixOp::Increment,
7655 },
7656 line,
7657 };
7658 }
7659 Token::Decrement => {
7660 if self.peek_line() > self.prev_line() {
7663 break;
7664 }
7665 let line = expr.line;
7666 self.advance();
7667 expr = Expr {
7668 kind: ExprKind::PostfixOp {
7669 expr: Box::new(expr),
7670 op: PostfixOp::Decrement,
7671 },
7672 line,
7673 };
7674 }
7675 Token::LParen => {
7676 if self.suppress_indirect_paren_call > 0 {
7677 break;
7678 }
7679 if self.peek_line() > self.prev_line() {
7683 break;
7684 }
7685 let line = expr.line;
7686 self.advance();
7687 let args = self.parse_arg_list()?;
7688 self.expect(&Token::RParen)?;
7689 expr = Expr {
7690 kind: ExprKind::IndirectCall {
7691 target: Box::new(expr),
7692 args,
7693 ampersand: false,
7694 pass_caller_arglist: false,
7695 },
7696 line,
7697 };
7698 }
7699 Token::Arrow => {
7700 let line = expr.line;
7701 self.advance();
7702 match self.peek().clone() {
7703 Token::LBracket => {
7704 self.advance();
7705 let index = self.parse_expression()?;
7706 self.expect(&Token::RBracket)?;
7707 expr = Expr {
7708 kind: ExprKind::ArrowDeref {
7709 expr: Box::new(expr),
7710 index: Box::new(index),
7711 kind: DerefKind::Array,
7712 },
7713 line,
7714 };
7715 }
7716 Token::LBrace => {
7717 self.advance();
7718 let key = self.parse_hash_subscript_key()?;
7719 self.expect(&Token::RBrace)?;
7720 expr = Expr {
7721 kind: ExprKind::ArrowDeref {
7722 expr: Box::new(expr),
7723 index: Box::new(key),
7724 kind: DerefKind::Hash,
7725 },
7726 line,
7727 };
7728 }
7729 Token::LParen => {
7730 self.advance();
7731 let args = self.parse_arg_list()?;
7732 self.expect(&Token::RParen)?;
7733 expr = Expr {
7734 kind: ExprKind::ArrowDeref {
7735 expr: Box::new(expr),
7736 index: Box::new(Expr {
7737 kind: ExprKind::List(args),
7738 line,
7739 }),
7740 kind: DerefKind::Call,
7741 },
7742 line,
7743 };
7744 }
7745 Token::Ident(method) => {
7746 self.advance();
7747 if method == "SUPER" {
7748 self.expect(&Token::PackageSep)?;
7749 let real_method = match self.advance() {
7750 (Token::Ident(n), _) => n,
7751 (tok, l) => {
7752 return Err(self.syntax_err(
7753 format!(
7754 "Expected method name after SUPER::, got {:?}",
7755 tok
7756 ),
7757 l,
7758 ));
7759 }
7760 };
7761 let args = if self.eat(&Token::LParen) {
7762 let a = self.parse_arg_list()?;
7763 self.expect(&Token::RParen)?;
7764 a
7765 } else {
7766 self.parse_method_arg_list_no_paren()?
7767 };
7768 expr = Expr {
7769 kind: ExprKind::MethodCall {
7770 object: Box::new(expr),
7771 method: real_method,
7772 args,
7773 super_call: true,
7774 },
7775 line,
7776 };
7777 } else {
7778 let mut method_name = method;
7779 while self.eat(&Token::PackageSep) {
7780 match self.advance() {
7781 (Token::Ident(part), _) => {
7782 method_name.push_str("::");
7783 method_name.push_str(&part);
7784 }
7785 (tok, l) => {
7786 return Err(self.syntax_err(
7787 format!(
7788 "Expected identifier after :: in method name, got {:?}",
7789 tok
7790 ),
7791 l,
7792 ));
7793 }
7794 }
7795 }
7796 let args = if self.eat(&Token::LParen) {
7797 let a = self.parse_arg_list()?;
7798 self.expect(&Token::RParen)?;
7799 a
7800 } else {
7801 self.parse_method_arg_list_no_paren()?
7802 };
7803 expr = Expr {
7804 kind: ExprKind::MethodCall {
7805 object: Box::new(expr),
7806 method: method_name,
7807 args,
7808 super_call: false,
7809 },
7810 line,
7811 };
7812 }
7813 }
7814 Token::ArrayAt => {
7820 self.advance(); match self.peek().clone() {
7822 Token::Star => {
7823 self.advance();
7824 expr = Expr {
7825 kind: ExprKind::Deref {
7826 expr: Box::new(expr),
7827 kind: Sigil::Array,
7828 },
7829 line,
7830 };
7831 }
7832 Token::LBracket => {
7833 self.advance();
7834 let indices = self.parse_slice_arg_list(false)?;
7835 self.expect(&Token::RBracket)?;
7836 let source = Expr {
7837 kind: ExprKind::Deref {
7838 expr: Box::new(expr),
7839 kind: Sigil::Array,
7840 },
7841 line,
7842 };
7843 expr = Expr {
7844 kind: ExprKind::AnonymousListSlice {
7845 source: Box::new(source),
7846 indices,
7847 },
7848 line,
7849 };
7850 }
7851 Token::LBrace => {
7852 self.advance();
7853 let keys = self.parse_slice_arg_list(true)?;
7854 self.expect(&Token::RBrace)?;
7855 expr = Expr {
7856 kind: ExprKind::HashSliceDeref {
7857 container: Box::new(expr),
7858 keys,
7859 },
7860 line,
7861 };
7862 }
7863 tok => {
7864 return Err(self.syntax_err(
7865 format!(
7866 "Expected `*`, `[…]`, or `{{…}}` after `->@`, got {:?}",
7867 tok
7868 ),
7869 line,
7870 ));
7871 }
7872 }
7873 }
7874 Token::HashPercent => {
7875 self.advance(); match self.peek().clone() {
7877 Token::Star => {
7878 self.advance();
7879 expr = Expr {
7880 kind: ExprKind::Deref {
7881 expr: Box::new(expr),
7882 kind: Sigil::Hash,
7883 },
7884 line,
7885 };
7886 }
7887 tok => {
7888 return Err(self.syntax_err(
7889 format!("Expected `*` after `->%`, got {:?}", tok),
7890 line,
7891 ));
7892 }
7893 }
7894 }
7895 Token::X => {
7897 self.advance();
7898 let args = if self.eat(&Token::LParen) {
7899 let a = self.parse_arg_list()?;
7900 self.expect(&Token::RParen)?;
7901 a
7902 } else {
7903 self.parse_method_arg_list_no_paren()?
7904 };
7905 expr = Expr {
7906 kind: ExprKind::MethodCall {
7907 object: Box::new(expr),
7908 method: "x".to_string(),
7909 args,
7910 super_call: false,
7911 },
7912 line,
7913 };
7914 }
7915 _ => break,
7916 }
7917 }
7918 Token::LBracket => {
7919 if self.peek_line() > self.prev_line() {
7922 break;
7923 }
7924 let line = expr.line;
7926 if matches!(expr.kind, ExprKind::ScalarVar(_)) {
7927 if let ExprKind::ScalarVar(ref name) = expr.kind {
7928 let name = name.clone();
7929 self.advance();
7930 let index = self.parse_expression()?;
7931 self.expect(&Token::RBracket)?;
7932 expr = Expr {
7933 kind: ExprKind::ArrayElement {
7934 array: name,
7935 index: Box::new(index),
7936 },
7937 line,
7938 };
7939 }
7940 } else if postfix_lbracket_is_arrow_container(&expr) {
7941 self.advance();
7942 let indices = self.parse_arg_list()?;
7943 self.expect(&Token::RBracket)?;
7944 expr = Expr {
7945 kind: ExprKind::ArrowDeref {
7946 expr: Box::new(expr),
7947 index: Box::new(Expr {
7948 kind: ExprKind::List(indices),
7949 line,
7950 }),
7951 kind: DerefKind::Array,
7952 },
7953 line,
7954 };
7955 } else {
7956 self.advance();
7957 let indices = self.parse_arg_list()?;
7958 self.expect(&Token::RBracket)?;
7959 expr = Expr {
7960 kind: ExprKind::AnonymousListSlice {
7961 source: Box::new(expr),
7962 indices,
7963 },
7964 line,
7965 };
7966 }
7967 }
7968 Token::LBrace => {
7969 if self.suppress_scalar_hash_brace > 0 {
7970 break;
7971 }
7972 if self.peek_line() > self.prev_line() {
7975 break;
7976 }
7977 let line = expr.line;
7980 let is_scalar_named_hash = matches!(expr.kind, ExprKind::ScalarVar(_));
7981 let is_chainable_hash_subscript = is_scalar_named_hash
7982 || matches!(
7983 expr.kind,
7984 ExprKind::HashElement { .. }
7985 | ExprKind::ArrayElement { .. }
7986 | ExprKind::ArrowDeref { .. }
7987 | ExprKind::Deref {
7988 kind: Sigil::Scalar,
7989 ..
7990 }
7991 );
7992 if !is_chainable_hash_subscript {
7993 break;
7994 }
7995 self.advance();
7996 let key = self.parse_hash_subscript_key()?;
7997 self.expect(&Token::RBrace)?;
7998 expr = if is_scalar_named_hash {
7999 if let ExprKind::ScalarVar(ref name) = expr.kind {
8000 let name = name.clone();
8001 if name == "_" {
8003 Expr {
8004 kind: ExprKind::ArrowDeref {
8005 expr: Box::new(Expr {
8006 kind: ExprKind::ScalarVar("_".into()),
8007 line,
8008 }),
8009 index: Box::new(key),
8010 kind: DerefKind::Hash,
8011 },
8012 line,
8013 }
8014 } else {
8015 Expr {
8016 kind: ExprKind::HashElement {
8017 hash: name,
8018 key: Box::new(key),
8019 },
8020 line,
8021 }
8022 }
8023 } else {
8024 unreachable!("is_scalar_named_hash implies ScalarVar");
8025 }
8026 } else {
8027 Expr {
8028 kind: ExprKind::ArrowDeref {
8029 expr: Box::new(expr),
8030 index: Box::new(key),
8031 kind: DerefKind::Hash,
8032 },
8033 line,
8034 }
8035 };
8036 }
8037 Token::LogNot | Token::BitNot => {
8038 if !matches!(expr.kind, ExprKind::ScalarVar(_)) {
8053 break;
8054 }
8055 if self.peek_line() > self.prev_line() {
8056 break;
8057 }
8058 let opener = self.peek().clone();
8059 let line = expr.line;
8060 let name = if let ExprKind::ScalarVar(ref n) = expr.kind {
8061 n.clone()
8062 } else {
8063 unreachable!()
8064 };
8065 self.advance(); self.suppress_tilde_range = self.suppress_tilde_range.saturating_add(1);
8071 let index_result = self.parse_expression();
8072 self.suppress_tilde_range = self.suppress_tilde_range.saturating_sub(1);
8073 let index = index_result?;
8074 let close_match = matches!(
8075 (&opener, self.peek()),
8076 (Token::LogNot, Token::LogNot) | (Token::BitNot, Token::BitNot)
8077 );
8078 if !close_match {
8079 let want = if matches!(opener, Token::LogNot) {
8080 "!"
8081 } else {
8082 "~"
8083 };
8084 return Err(self.syntax_err(
8085 format!("expected closing `{}` for string subscript", want),
8086 self.peek_line(),
8087 ));
8088 }
8089 self.advance(); expr = Expr {
8091 kind: ExprKind::ArrayElement {
8092 array: format!("__topicstr__{}", name),
8093 index: Box::new(index),
8094 },
8095 line,
8096 };
8097 }
8098 _ => break,
8099 }
8100 }
8101 Ok(expr)
8102 }
8103
8104 fn parse_primary(&mut self) -> PerlResult<Expr> {
8105 let line = self.peek_line();
8106 if let Token::Ident(ref kw) = self.peek().clone() {
8111 if matches!(kw.as_str(), "my" | "our" | "state" | "local") {
8112 let kw_owned = kw.clone();
8113 let saved_pos = self.pos;
8118 let stmt = self.parse_my_our_local(&kw_owned, false)?;
8119 let decls = match stmt.kind {
8120 StmtKind::My(d)
8121 | StmtKind::Our(d)
8122 | StmtKind::State(d)
8123 | StmtKind::Local(d) => d,
8124 _ => {
8125 self.pos = saved_pos;
8130 return Err(self.syntax_err(
8131 "`my`/`our`/`local` in expression must declare variables",
8132 line,
8133 ));
8134 }
8135 };
8136 return Ok(Expr {
8137 kind: ExprKind::MyExpr {
8138 keyword: kw_owned,
8139 decls,
8140 },
8141 line,
8142 });
8143 }
8144 }
8145 match self.peek().clone() {
8146 Token::Integer(n) => {
8147 self.advance();
8148 Ok(Expr {
8149 kind: ExprKind::Integer(n),
8150 line,
8151 })
8152 }
8153 Token::Float(f) => {
8154 self.advance();
8155 Ok(Expr {
8156 kind: ExprKind::Float(f),
8157 line,
8158 })
8159 }
8160 Token::ArrowBrace => {
8166 self.advance();
8167 let mut stmts = Vec::new();
8168 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
8169 if self.eat(&Token::Semicolon) {
8170 continue;
8171 }
8172 stmts.push(self.parse_statement()?);
8173 }
8174 self.expect(&Token::RBrace)?;
8175 let inner_line = stmts.first().map(|s| s.line).unwrap_or(line);
8176 let inner = Expr {
8177 kind: ExprKind::CodeRef {
8178 params: vec![],
8179 body: stmts,
8180 },
8181 line: inner_line,
8182 };
8183 Ok(Expr {
8184 kind: ExprKind::Do(Box::new(inner)),
8185 line,
8186 })
8187 }
8188 Token::Star => {
8189 self.advance();
8190 if matches!(self.peek(), Token::LBrace) {
8191 self.advance();
8192 let inner = self.parse_expression()?;
8193 self.expect(&Token::RBrace)?;
8194 return Ok(Expr {
8195 kind: ExprKind::Deref {
8196 expr: Box::new(inner),
8197 kind: Sigil::Typeglob,
8198 },
8199 line,
8200 });
8201 }
8202 if matches!(
8204 self.peek(),
8205 Token::ScalarVar(_)
8206 | Token::ArrayVar(_)
8207 | Token::HashVar(_)
8208 | Token::DerefScalarVar(_)
8209 | Token::HashPercent
8210 ) {
8211 let inner = self.parse_postfix()?;
8212 return Ok(Expr {
8213 kind: ExprKind::TypeglobExpr(Box::new(inner)),
8214 line,
8215 });
8216 }
8217 let mut full_name = match self.advance() {
8219 (Token::Ident(n), _) => n,
8220 (Token::X, _) => "x".to_string(),
8221 (tok, l) => {
8222 return Err(self
8223 .syntax_err(format!("Expected identifier after *, got {:?}", tok), l));
8224 }
8225 };
8226 while self.eat(&Token::PackageSep) {
8227 match self.advance() {
8228 (Token::Ident(part), _) => {
8229 full_name = format!("{}::{}", full_name, part);
8230 }
8231 (Token::X, _) => {
8232 full_name = format!("{}::x", full_name);
8233 }
8234 (tok, l) => {
8235 return Err(self.syntax_err(
8236 format!("Expected identifier after :: in typeglob, got {:?}", tok),
8237 l,
8238 ));
8239 }
8240 }
8241 }
8242 Ok(Expr {
8243 kind: ExprKind::Typeglob(full_name),
8244 line,
8245 })
8246 }
8247 Token::SingleString(s) => {
8248 self.advance();
8249 Ok(Expr {
8250 kind: ExprKind::String(s),
8251 line,
8252 })
8253 }
8254 Token::DoubleString(s) => {
8255 self.advance();
8256 self.parse_interpolated_string(&s, line)
8257 }
8258 Token::BacktickString(s) => {
8259 self.advance();
8260 let inner = self.parse_interpolated_string(&s, line)?;
8261 Ok(Expr {
8262 kind: ExprKind::Qx(Box::new(inner)),
8263 line,
8264 })
8265 }
8266 Token::HereDoc(_, body, interpolate) => {
8267 self.advance();
8268 if interpolate {
8269 self.parse_interpolated_string(&body, line)
8270 } else {
8271 Ok(Expr {
8272 kind: ExprKind::String(body),
8273 line,
8274 })
8275 }
8276 }
8277 Token::Regex(pattern, flags, _delim) => {
8278 self.advance();
8279 Ok(Expr {
8280 kind: ExprKind::Regex(pattern, flags),
8281 line,
8282 })
8283 }
8284 Token::QW(words) => {
8285 self.advance();
8286 self.list_construct_close_pos = Some(self.pos);
8289 Ok(Expr {
8290 kind: ExprKind::QW(words),
8291 line,
8292 })
8293 }
8294 Token::DerefScalarVar(name) => {
8295 self.advance();
8296 Ok(Expr {
8297 kind: ExprKind::Deref {
8298 expr: Box::new(Expr {
8299 kind: ExprKind::ScalarVar(name),
8300 line,
8301 }),
8302 kind: Sigil::Scalar,
8303 },
8304 line,
8305 })
8306 }
8307 Token::ScalarVar(name) => {
8308 self.advance();
8309 Ok(Expr {
8310 kind: ExprKind::ScalarVar(name),
8311 line,
8312 })
8313 }
8314 Token::ArrayVar(name) => {
8315 self.advance();
8316 match self.peek() {
8318 Token::LBracket => {
8319 self.advance();
8320 let indices = self.parse_slice_arg_list(false)?;
8321 self.expect(&Token::RBracket)?;
8322 Ok(Expr {
8323 kind: ExprKind::ArraySlice {
8324 array: name,
8325 indices,
8326 },
8327 line,
8328 })
8329 }
8330 Token::LBrace if self.suppress_scalar_hash_brace == 0 => {
8331 self.advance();
8332 let keys = self.parse_slice_arg_list(true)?;
8333 self.expect(&Token::RBrace)?;
8334 Ok(Expr {
8335 kind: ExprKind::HashSlice { hash: name, keys },
8336 line,
8337 })
8338 }
8339 _ => Ok(Expr {
8340 kind: ExprKind::ArrayVar(name),
8341 line,
8342 }),
8343 }
8344 }
8345 Token::HashVar(name) => {
8346 self.advance();
8347 if matches!(self.peek(), Token::LBrace)
8352 && self.suppress_scalar_hash_brace == 0
8353 {
8354 self.advance(); let keys = self.parse_slice_arg_list(true)?;
8356 self.expect(&Token::RBrace)?;
8357 return Ok(Expr {
8358 kind: ExprKind::HashKvSlice { hash: name, keys },
8359 line,
8360 });
8361 }
8362 Ok(Expr {
8363 kind: ExprKind::HashVar(name),
8364 line,
8365 })
8366 }
8367 Token::HashPercent => {
8368 self.advance();
8370 if matches!(self.peek(), Token::ScalarVar(_)) {
8371 let n = match self.advance() {
8372 (Token::ScalarVar(n), _) => n,
8373 (tok, l) => {
8374 return Err(self.syntax_err(
8375 format!("Expected scalar variable after %%, got {:?}", tok),
8376 l,
8377 ));
8378 }
8379 };
8380 return Ok(Expr {
8381 kind: ExprKind::Deref {
8382 expr: Box::new(Expr {
8383 kind: ExprKind::ScalarVar(n),
8384 line,
8385 }),
8386 kind: Sigil::Hash,
8387 },
8388 line,
8389 });
8390 }
8391 if matches!(self.peek(), Token::LBracket) {
8396 self.advance();
8397 let pairs = self.parse_hashref_pairs_until(&Token::RBracket)?;
8398 self.expect(&Token::RBracket)?;
8399 let href = Expr {
8400 kind: ExprKind::HashRef(pairs),
8401 line,
8402 };
8403 return Ok(Expr {
8404 kind: ExprKind::Deref {
8405 expr: Box::new(href),
8406 kind: Sigil::Hash,
8407 },
8408 line,
8409 });
8410 }
8411 self.expect(&Token::LBrace)?;
8412 let looks_like_pair = matches!(
8418 self.peek(),
8419 Token::Ident(_) | Token::SingleString(_) | Token::DoubleString(_)
8420 ) && matches!(self.peek_at(1), Token::FatArrow);
8421 let inner = if looks_like_pair {
8422 let pairs = self.parse_hashref_pairs_until(&Token::RBrace)?;
8423 Expr {
8424 kind: ExprKind::HashRef(pairs),
8425 line,
8426 }
8427 } else {
8428 self.parse_expression()?
8429 };
8430 self.expect(&Token::RBrace)?;
8431 Ok(Expr {
8432 kind: ExprKind::Deref {
8433 expr: Box::new(inner),
8434 kind: Sigil::Hash,
8435 },
8436 line,
8437 })
8438 }
8439 Token::ArrayAt => {
8440 self.advance();
8441 if matches!(self.peek(), Token::LBrace) {
8443 self.advance();
8444 let inner = self.parse_expression()?;
8445 self.expect(&Token::RBrace)?;
8446 return Ok(Expr {
8447 kind: ExprKind::Deref {
8448 expr: Box::new(inner),
8449 kind: Sigil::Array,
8450 },
8451 line,
8452 });
8453 }
8454 if matches!(self.peek(), Token::LBracket) {
8458 self.advance();
8459 let mut elems = Vec::new();
8460 if !matches!(self.peek(), Token::RBracket) {
8461 elems.push(self.parse_assign_expr()?);
8462 while self.eat(&Token::Comma) {
8463 if matches!(self.peek(), Token::RBracket) {
8464 break;
8465 }
8466 elems.push(self.parse_assign_expr()?);
8467 }
8468 }
8469 self.expect(&Token::RBracket)?;
8470 let aref = Expr {
8471 kind: ExprKind::ArrayRef(elems),
8472 line,
8473 };
8474 return Ok(Expr {
8475 kind: ExprKind::Deref {
8476 expr: Box::new(aref),
8477 kind: Sigil::Array,
8478 },
8479 line,
8480 });
8481 }
8482 let container = match self.peek().clone() {
8484 Token::ScalarVar(n) => {
8485 self.advance();
8486 Expr {
8487 kind: ExprKind::ScalarVar(n),
8488 line,
8489 }
8490 }
8491 _ => {
8492 return Err(self.syntax_err(
8493 "Expected `$name`, `{`, or `[` after `@` (e.g. `@$aref`, `@{expr}`, `@[1,2,3]`, or `@$href{keys}`)",
8494 line,
8495 ));
8496 }
8497 };
8498 if matches!(self.peek(), Token::LBrace) {
8499 self.advance();
8500 let keys = self.parse_slice_arg_list(true)?;
8501 self.expect(&Token::RBrace)?;
8502 return Ok(Expr {
8503 kind: ExprKind::HashSliceDeref {
8504 container: Box::new(container),
8505 keys,
8506 },
8507 line,
8508 });
8509 }
8510 Ok(Expr {
8511 kind: ExprKind::Deref {
8512 expr: Box::new(container),
8513 kind: Sigil::Array,
8514 },
8515 line,
8516 })
8517 }
8518 Token::LParen => {
8519 self.advance();
8520 if matches!(self.peek(), Token::RParen) {
8521 self.advance();
8522 self.list_construct_close_pos = Some(self.pos);
8525 return Ok(Expr {
8526 kind: ExprKind::List(vec![]),
8527 line,
8528 });
8529 }
8530 let saved_no_pipe = self.no_pipe_forward_depth;
8533 self.no_pipe_forward_depth = 0;
8534 let expr = self.parse_expression();
8535 self.no_pipe_forward_depth = saved_no_pipe;
8536 let expr = expr?;
8537 self.expect(&Token::RParen)?;
8538 self.list_construct_close_pos = Some(self.pos);
8543 Ok(expr)
8544 }
8545 Token::LBracket => {
8546 self.advance();
8547 let elems = self.parse_arg_list()?;
8548 self.expect(&Token::RBracket)?;
8549 Ok(Expr {
8550 kind: ExprKind::ArrayRef(elems),
8551 line,
8552 })
8553 }
8554 Token::LBrace => {
8555 self.advance();
8557 let saved = self.pos;
8559 match self.try_parse_hash_ref() {
8560 Ok(pairs) => Ok(Expr {
8561 kind: ExprKind::HashRef(pairs),
8562 line,
8563 }),
8564 Err(_) => {
8565 self.pos = saved;
8566 let mut stmts = Vec::new();
8568 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
8569 if self.eat(&Token::Semicolon) {
8570 continue;
8571 }
8572 stmts.push(self.parse_statement()?);
8573 }
8574 self.expect(&Token::RBrace)?;
8575 Ok(Expr {
8576 kind: ExprKind::CodeRef {
8577 params: vec![],
8578 body: stmts,
8579 },
8580 line,
8581 })
8582 }
8583 }
8584 }
8585 Token::Diamond => {
8586 self.advance();
8587 Ok(Expr {
8588 kind: ExprKind::ReadLine(None),
8589 line,
8590 })
8591 }
8592 Token::ReadLine(handle) => {
8593 self.advance();
8594 Ok(Expr {
8595 kind: ExprKind::ReadLine(Some(handle)),
8596 line,
8597 })
8598 }
8599
8600 Token::ThreadArrow => {
8602 self.advance();
8603 self.parse_thread_macro(line, false)
8604 }
8605 Token::ThreadArrowLast => {
8606 self.advance();
8607 self.parse_thread_macro(line, true)
8608 }
8609 Token::Ident(ref name) => {
8610 let name = name.clone();
8611 if name.starts_with('\x00') {
8613 self.advance();
8614 let parts: Vec<&str> = name.split('\x00').collect();
8615 if parts.len() >= 4 && parts[1] == "s" {
8616 let delim = parts.get(5).and_then(|s| s.chars().next()).unwrap_or('/');
8617 return Ok(Expr {
8618 kind: ExprKind::Substitution {
8619 expr: Box::new(Expr {
8620 kind: ExprKind::ScalarVar("_".into()),
8621 line,
8622 }),
8623 pattern: parts[2].to_string(),
8624 replacement: parts[3].to_string(),
8625 flags: parts.get(4).unwrap_or(&"").to_string(),
8626 delim,
8627 },
8628 line,
8629 });
8630 }
8631 if parts.len() >= 4 && parts[1] == "tr" {
8632 let delim = parts.get(5).and_then(|s| s.chars().next()).unwrap_or('/');
8633 return Ok(Expr {
8634 kind: ExprKind::Transliterate {
8635 expr: Box::new(Expr {
8636 kind: ExprKind::ScalarVar("_".into()),
8637 line,
8638 }),
8639 from: parts[2].to_string(),
8640 to: parts[3].to_string(),
8641 flags: parts.get(4).unwrap_or(&"").to_string(),
8642 delim,
8643 },
8644 line,
8645 });
8646 }
8647 return Err(self.syntax_err("Unexpected encoded token", line));
8648 }
8649 self.parse_named_expr(name)
8650 }
8651
8652 Token::Percent => {
8655 self.advance();
8656 match self.peek().clone() {
8657 Token::Ident(name) => {
8658 self.advance();
8659 Ok(Expr {
8660 kind: ExprKind::HashVar(name),
8661 line,
8662 })
8663 }
8664 Token::ScalarVar(n) => {
8665 self.advance();
8666 Ok(Expr {
8667 kind: ExprKind::Deref {
8668 expr: Box::new(Expr {
8669 kind: ExprKind::ScalarVar(n),
8670 line,
8671 }),
8672 kind: Sigil::Hash,
8673 },
8674 line,
8675 })
8676 }
8677 Token::LBrace => {
8678 self.advance();
8679 let looks_like_pair = matches!(
8680 self.peek(),
8681 Token::Ident(_) | Token::SingleString(_) | Token::DoubleString(_)
8682 ) && matches!(self.peek_at(1), Token::FatArrow);
8683 let inner = if looks_like_pair {
8684 let pairs = self.parse_hashref_pairs_until(&Token::RBrace)?;
8685 Expr {
8686 kind: ExprKind::HashRef(pairs),
8687 line,
8688 }
8689 } else {
8690 self.parse_expression()?
8691 };
8692 self.expect(&Token::RBrace)?;
8693 Ok(Expr {
8694 kind: ExprKind::Deref {
8695 expr: Box::new(inner),
8696 kind: Sigil::Hash,
8697 },
8698 line,
8699 })
8700 }
8701 Token::LBracket => {
8702 self.advance();
8703 let pairs = self.parse_hashref_pairs_until(&Token::RBracket)?;
8704 self.expect(&Token::RBracket)?;
8705 let href = Expr {
8706 kind: ExprKind::HashRef(pairs),
8707 line,
8708 };
8709 Ok(Expr {
8710 kind: ExprKind::Deref {
8711 expr: Box::new(href),
8712 kind: Sigil::Hash,
8713 },
8714 line,
8715 })
8716 }
8717 tok => Err(self.syntax_err(
8718 format!(
8719 "Expected identifier, `$`, `{{`, or `[` after `%`, got {:?}",
8720 tok
8721 ),
8722 line,
8723 )),
8724 }
8725 }
8726
8727 tok => Err(self.syntax_err(format!("Unexpected token {:?}", tok), line)),
8728 }
8729 }
8730
8731 fn parse_named_expr(&mut self, mut name: String) -> PerlResult<Expr> {
8732 let line = self.peek_line();
8733 self.advance(); while self.eat(&Token::PackageSep) {
8735 match self.advance() {
8736 (Token::Ident(part), _) => {
8737 name = format!("{}::{}", name, part);
8738 }
8739 (tok, err_line) => {
8740 return Err(self.syntax_err(
8741 format!("Expected identifier after `::`, got {:?}", tok),
8742 err_line,
8743 ));
8744 }
8745 }
8746 }
8747
8748 if matches!(self.peek(), Token::FatArrow) && !Self::is_underscore_topic_slot(&name) {
8755 return Ok(Expr {
8756 kind: ExprKind::String(name),
8757 line,
8758 });
8759 }
8760
8761 if crate::compat_mode() {
8762 if let Some(ext) = Self::stryke_extension_name(&name) {
8763 if !self.declared_subs.contains(&name) {
8764 return Err(self.syntax_err(
8765 format!("`{ext}` is a stryke extension (disabled by --compat)"),
8766 line,
8767 ));
8768 }
8769 }
8770 }
8771
8772 if let Some(rest) = name.strip_prefix("CORE::") {
8779 name = rest.to_string();
8780 }
8781
8782 match name.as_str() {
8783 "__FILE__" => Ok(Expr {
8784 kind: ExprKind::MagicConst(MagicConstKind::File),
8785 line,
8786 }),
8787 "__LINE__" => Ok(Expr {
8788 kind: ExprKind::MagicConst(MagicConstKind::Line),
8789 line,
8790 }),
8791 "__SUB__" => Ok(Expr {
8792 kind: ExprKind::MagicConst(MagicConstKind::Sub),
8793 line,
8794 }),
8795 "stdin" => Ok(Expr {
8796 kind: ExprKind::FuncCall {
8797 name: "stdin".into(),
8798 args: vec![],
8799 },
8800 line,
8801 }),
8802 "range" => {
8803 let args = self.parse_builtin_args()?;
8804 Ok(Expr {
8805 kind: ExprKind::FuncCall {
8806 name: "range".into(),
8807 args,
8808 },
8809 line,
8810 })
8811 }
8812 "print" | "pr" => self.parse_print_like(|h, a| ExprKind::Print { handle: h, args: a }),
8813 "say" => {
8814 if crate::no_interop_mode() {
8815 return Err(
8816 self.syntax_err("stryke uses `p` instead of `say` (--no-interop)", line)
8817 );
8818 }
8819 self.parse_print_like(|h, a| ExprKind::Say { handle: h, args: a })
8820 }
8821 "p" => self.parse_print_like(|h, a| ExprKind::Say { handle: h, args: a }),
8822 "printf" => self.parse_print_like(|h, a| ExprKind::Printf { handle: h, args: a }),
8823 "die" => {
8824 let args = self.parse_list_until_terminator()?;
8825 Ok(Expr {
8826 kind: ExprKind::Die(args),
8827 line,
8828 })
8829 }
8830 "warn" => {
8831 let args = self.parse_list_until_terminator()?;
8832 Ok(Expr {
8833 kind: ExprKind::Warn(args),
8834 line,
8835 })
8836 }
8837 "croak" | "confess" => {
8842 let args = self.parse_list_until_terminator()?;
8843 Ok(Expr {
8844 kind: ExprKind::Die(args),
8845 line,
8846 })
8847 }
8848 "carp" | "cluck" => {
8850 let args = self.parse_list_until_terminator()?;
8851 Ok(Expr {
8852 kind: ExprKind::Warn(args),
8853 line,
8854 })
8855 }
8856 "chomp" => {
8857 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8858 return Ok(e);
8859 }
8860 let a = self.parse_one_arg_or_default()?;
8861 Ok(Expr {
8862 kind: ExprKind::Chomp(Box::new(a)),
8863 line,
8864 })
8865 }
8866 "chop" => {
8867 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8868 return Ok(e);
8869 }
8870 let a = self.parse_one_arg_or_default()?;
8871 Ok(Expr {
8872 kind: ExprKind::Chop(Box::new(a)),
8873 line,
8874 })
8875 }
8876 "length" => {
8877 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8878 return Ok(e);
8879 }
8880 let a = self.parse_one_arg_or_default()?;
8881 Ok(Expr {
8882 kind: ExprKind::Length(Box::new(a)),
8883 line,
8884 })
8885 }
8886 "defined" => {
8887 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8888 return Ok(e);
8889 }
8890 let a = if matches!(
8898 self.peek(),
8899 Token::Semicolon
8900 | Token::RBrace
8901 | Token::RParen
8902 | Token::RBracket
8903 | Token::Eof
8904 | Token::Comma
8905 | Token::FatArrow
8906 | Token::PipeForward
8907 | Token::Question
8908 | Token::Colon
8909 | Token::NumEq
8910 | Token::NumNe
8911 | Token::NumLt
8912 | Token::NumGt
8913 | Token::NumLe
8914 | Token::NumGe
8915 | Token::Spaceship
8916 | Token::StrEq
8917 | Token::StrNe
8918 | Token::StrLt
8919 | Token::StrGt
8920 | Token::StrLe
8921 | Token::StrGe
8922 | Token::StrCmp
8923 | Token::LogAnd
8924 | Token::LogOr
8925 | Token::LogNot
8926 | Token::LogAndWord
8927 | Token::LogOrWord
8928 | Token::LogNotWord
8929 | Token::DefinedOr
8930 | Token::Range
8931 | Token::RangeExclusive
8932 | Token::Assign
8933 | Token::PlusAssign
8934 | Token::MinusAssign
8935 | Token::MulAssign
8936 | Token::DivAssign
8937 | Token::ModAssign
8938 | Token::PowAssign
8939 | Token::DotAssign
8940 | Token::AndAssign
8941 | Token::OrAssign
8942 | Token::XorAssign
8943 | Token::DefinedOrAssign
8944 | Token::ShiftLeftAssign
8945 | Token::ShiftRightAssign
8946 | Token::BitAndAssign
8947 | Token::BitOrAssign
8948 ) {
8949 Expr {
8950 kind: ExprKind::ScalarVar("_".into()),
8951 line: self.peek_line(),
8952 }
8953 } else if matches!(self.peek(), Token::LParen)
8954 && matches!(self.peek_at(1), Token::RParen)
8955 {
8956 let pl = self.peek_line();
8957 self.advance();
8958 self.advance();
8959 Expr {
8960 kind: ExprKind::ScalarVar("_".into()),
8961 line: pl,
8962 }
8963 } else {
8964 self.parse_named_unary_arg()?
8965 };
8966 Ok(Expr {
8967 kind: ExprKind::Defined(Box::new(a)),
8968 line,
8969 })
8970 }
8971 "ref" => {
8972 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8973 return Ok(e);
8974 }
8975 let a = self.parse_one_arg_or_default()?;
8976 Ok(Expr {
8977 kind: ExprKind::Ref(Box::new(a)),
8978 line,
8979 })
8980 }
8981 "undef" => {
8982 if self.peek_line() == self.prev_line()
8985 && matches!(
8986 self.peek(),
8987 Token::ScalarVar(_) | Token::ArrayVar(_) | Token::HashVar(_)
8988 )
8989 {
8990 let target = self.parse_primary()?;
8991 return Ok(Expr {
8992 kind: ExprKind::Assign {
8993 target: Box::new(target),
8994 value: Box::new(Expr {
8995 kind: ExprKind::Undef,
8996 line,
8997 }),
8998 },
8999 line,
9000 });
9001 }
9002 Ok(Expr {
9003 kind: ExprKind::Undef,
9004 line,
9005 })
9006 }
9007 "scalar" => {
9008 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9009 return Ok(e);
9010 }
9011 if crate::no_interop_mode() {
9012 return Err(self.syntax_err(
9013 "stryke uses `len` (also `cnt` / `count`) instead of `scalar` (--no-interop)",
9014 line,
9015 ));
9016 }
9017 let a = self.parse_one_arg_or_default()?;
9018 Ok(Expr {
9019 kind: ExprKind::ScalarContext(Box::new(a)),
9020 line,
9021 })
9022 }
9023 "abs" => {
9024 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9025 return Ok(e);
9026 }
9027 let a = self.parse_one_arg_or_default()?;
9028 Ok(Expr {
9029 kind: ExprKind::Abs(Box::new(a)),
9030 line,
9031 })
9032 }
9033 "inc" | "dec" => {
9038 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9039 return Ok(e);
9040 }
9041 let a = self.parse_one_arg_or_default()?;
9042 Ok(Expr {
9043 kind: ExprKind::FuncCall {
9044 name,
9045 args: vec![a],
9046 },
9047 line,
9048 })
9049 }
9050 "int" => {
9051 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9052 return Ok(e);
9053 }
9054 let a = self.parse_one_arg_or_default()?;
9055 Ok(Expr {
9056 kind: ExprKind::Int(Box::new(a)),
9057 line,
9058 })
9059 }
9060 "sqrt" => {
9061 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9062 return Ok(e);
9063 }
9064 let a = self.parse_one_arg_or_default()?;
9065 Ok(Expr {
9066 kind: ExprKind::Sqrt(Box::new(a)),
9067 line,
9068 })
9069 }
9070 "sin" => {
9071 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9072 return Ok(e);
9073 }
9074 let a = self.parse_one_arg_or_default()?;
9075 Ok(Expr {
9076 kind: ExprKind::Sin(Box::new(a)),
9077 line,
9078 })
9079 }
9080 "cos" => {
9081 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9082 return Ok(e);
9083 }
9084 let a = self.parse_one_arg_or_default()?;
9085 Ok(Expr {
9086 kind: ExprKind::Cos(Box::new(a)),
9087 line,
9088 })
9089 }
9090 "atan2" => {
9091 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9092 return Ok(e);
9093 }
9094 let args = self.parse_builtin_args()?;
9095 if args.len() != 2 {
9096 return Err(self.syntax_err("atan2 requires two arguments", line));
9097 }
9098 Ok(Expr {
9099 kind: ExprKind::Atan2 {
9100 y: Box::new(args[0].clone()),
9101 x: Box::new(args[1].clone()),
9102 },
9103 line,
9104 })
9105 }
9106 "exp" => {
9107 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9108 return Ok(e);
9109 }
9110 let a = self.parse_one_arg_or_default()?;
9111 Ok(Expr {
9112 kind: ExprKind::Exp(Box::new(a)),
9113 line,
9114 })
9115 }
9116 "log" => {
9117 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9118 return Ok(e);
9119 }
9120 let a = self.parse_one_arg_or_default()?;
9121 Ok(Expr {
9122 kind: ExprKind::Log(Box::new(a)),
9123 line,
9124 })
9125 }
9126 "input" => {
9127 let args = if matches!(
9128 self.peek(),
9129 Token::Semicolon
9130 | Token::RBrace
9131 | Token::RParen
9132 | Token::Eof
9133 | Token::Comma
9134 | Token::PipeForward
9135 ) {
9136 vec![]
9137 } else if matches!(self.peek(), Token::LParen) {
9138 self.advance();
9139 if matches!(self.peek(), Token::RParen) {
9140 self.advance();
9141 vec![]
9142 } else {
9143 let a = self.parse_expression()?;
9144 self.expect(&Token::RParen)?;
9145 vec![a]
9146 }
9147 } else {
9148 let a = self.parse_one_arg()?;
9149 vec![a]
9150 };
9151 Ok(Expr {
9152 kind: ExprKind::FuncCall {
9153 name: "input".to_string(),
9154 args,
9155 },
9156 line,
9157 })
9158 }
9159 "rand" => {
9160 if matches!(
9161 self.peek(),
9162 Token::Semicolon
9163 | Token::RBrace
9164 | Token::RParen
9165 | Token::Eof
9166 | Token::Comma
9167 | Token::PipeForward
9168 ) {
9169 Ok(Expr {
9170 kind: ExprKind::Rand(None),
9171 line,
9172 })
9173 } else if matches!(self.peek(), Token::LParen) {
9174 self.advance();
9175 if matches!(self.peek(), Token::RParen) {
9176 self.advance();
9177 Ok(Expr {
9178 kind: ExprKind::Rand(None),
9179 line,
9180 })
9181 } else {
9182 let a = self.parse_expression()?;
9183 self.expect(&Token::RParen)?;
9184 Ok(Expr {
9185 kind: ExprKind::Rand(Some(Box::new(a))),
9186 line,
9187 })
9188 }
9189 } else {
9190 let a = self.parse_one_arg()?;
9191 Ok(Expr {
9192 kind: ExprKind::Rand(Some(Box::new(a))),
9193 line,
9194 })
9195 }
9196 }
9197 "srand" => {
9198 if matches!(
9199 self.peek(),
9200 Token::Semicolon
9201 | Token::RBrace
9202 | Token::RParen
9203 | Token::Eof
9204 | Token::Comma
9205 | Token::PipeForward
9206 ) {
9207 Ok(Expr {
9208 kind: ExprKind::Srand(None),
9209 line,
9210 })
9211 } else if matches!(self.peek(), Token::LParen) {
9212 self.advance();
9213 if matches!(self.peek(), Token::RParen) {
9214 self.advance();
9215 Ok(Expr {
9216 kind: ExprKind::Srand(None),
9217 line,
9218 })
9219 } else {
9220 let a = self.parse_expression()?;
9221 self.expect(&Token::RParen)?;
9222 Ok(Expr {
9223 kind: ExprKind::Srand(Some(Box::new(a))),
9224 line,
9225 })
9226 }
9227 } else {
9228 let a = self.parse_one_arg()?;
9229 Ok(Expr {
9230 kind: ExprKind::Srand(Some(Box::new(a))),
9231 line,
9232 })
9233 }
9234 }
9235 "hex" => {
9236 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9237 return Ok(e);
9238 }
9239 let a = self.parse_one_arg_or_default()?;
9240 Ok(Expr {
9241 kind: ExprKind::Hex(Box::new(a)),
9242 line,
9243 })
9244 }
9245 "oct" => {
9246 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9247 return Ok(e);
9248 }
9249 let a = self.parse_one_arg_or_default()?;
9250 Ok(Expr {
9251 kind: ExprKind::Oct(Box::new(a)),
9252 line,
9253 })
9254 }
9255 "chr" => {
9256 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9257 return Ok(e);
9258 }
9259 let a = self.parse_one_arg_or_default()?;
9260 Ok(Expr {
9261 kind: ExprKind::Chr(Box::new(a)),
9262 line,
9263 })
9264 }
9265 "ord" => {
9266 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9267 return Ok(e);
9268 }
9269 let a = self.parse_one_arg_or_default()?;
9270 Ok(Expr {
9271 kind: ExprKind::Ord(Box::new(a)),
9272 line,
9273 })
9274 }
9275 "lc" => {
9276 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9277 return Ok(e);
9278 }
9279 let a = self.parse_one_arg_or_default()?;
9280 Ok(Expr {
9281 kind: ExprKind::Lc(Box::new(a)),
9282 line,
9283 })
9284 }
9285 "uc" => {
9286 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9287 return Ok(e);
9288 }
9289 let a = self.parse_one_arg_or_default()?;
9290 Ok(Expr {
9291 kind: ExprKind::Uc(Box::new(a)),
9292 line,
9293 })
9294 }
9295 "lcfirst" => {
9296 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9297 return Ok(e);
9298 }
9299 let a = self.parse_one_arg_or_default()?;
9300 Ok(Expr {
9301 kind: ExprKind::Lcfirst(Box::new(a)),
9302 line,
9303 })
9304 }
9305 "ucfirst" => {
9306 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9307 return Ok(e);
9308 }
9309 let a = self.parse_one_arg_or_default()?;
9310 Ok(Expr {
9311 kind: ExprKind::Ucfirst(Box::new(a)),
9312 line,
9313 })
9314 }
9315 "fc" => {
9316 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9317 return Ok(e);
9318 }
9319 let a = self.parse_one_arg_or_default()?;
9320 Ok(Expr {
9321 kind: ExprKind::Fc(Box::new(a)),
9322 line,
9323 })
9324 }
9325 "crypt" => {
9326 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9327 return Ok(e);
9328 }
9329 let args = self.parse_builtin_args()?;
9330 if args.len() != 2 {
9331 return Err(self.syntax_err("crypt requires two arguments", line));
9332 }
9333 Ok(Expr {
9334 kind: ExprKind::Crypt {
9335 plaintext: Box::new(args[0].clone()),
9336 salt: Box::new(args[1].clone()),
9337 },
9338 line,
9339 })
9340 }
9341 "pos" => {
9342 if matches!(
9343 self.peek(),
9344 Token::Semicolon
9345 | Token::RBrace
9346 | Token::RParen
9347 | Token::Eof
9348 | Token::Comma
9349 | Token::PipeForward
9350 ) {
9351 Ok(Expr {
9352 kind: ExprKind::Pos(None),
9353 line,
9354 })
9355 } else if matches!(self.peek(), Token::Assign) {
9356 self.advance();
9358 let rhs = self.parse_assign_expr()?;
9359 Ok(Expr {
9360 kind: ExprKind::Assign {
9361 target: Box::new(Expr {
9362 kind: ExprKind::Pos(Some(Box::new(Expr {
9363 kind: ExprKind::ScalarVar("_".into()),
9364 line,
9365 }))),
9366 line,
9367 }),
9368 value: Box::new(rhs),
9369 },
9370 line,
9371 })
9372 } else if matches!(self.peek(), Token::LParen) {
9373 self.advance();
9374 if matches!(self.peek(), Token::RParen) {
9375 self.advance();
9376 Ok(Expr {
9377 kind: ExprKind::Pos(None),
9378 line,
9379 })
9380 } else {
9381 let a = self.parse_expression()?;
9382 self.expect(&Token::RParen)?;
9383 Ok(Expr {
9384 kind: ExprKind::Pos(Some(Box::new(a))),
9385 line,
9386 })
9387 }
9388 } else {
9389 let saved = self.pos;
9390 let subj = self.parse_unary()?;
9391 if matches!(self.peek(), Token::Assign) {
9392 self.advance();
9393 let rhs = self.parse_assign_expr()?;
9394 Ok(Expr {
9395 kind: ExprKind::Assign {
9396 target: Box::new(Expr {
9397 kind: ExprKind::Pos(Some(Box::new(subj))),
9398 line,
9399 }),
9400 value: Box::new(rhs),
9401 },
9402 line,
9403 })
9404 } else {
9405 self.pos = saved;
9406 let a = self.parse_one_arg()?;
9407 Ok(Expr {
9408 kind: ExprKind::Pos(Some(Box::new(a))),
9409 line,
9410 })
9411 }
9412 }
9413 }
9414 "study" => {
9415 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9416 return Ok(e);
9417 }
9418 let a = self.parse_one_arg_or_default()?;
9419 Ok(Expr {
9420 kind: ExprKind::Study(Box::new(a)),
9421 line,
9422 })
9423 }
9424 "push" => {
9425 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9426 return Ok(e);
9427 }
9428 let args = self.parse_builtin_args()?;
9429 let (first, rest) = args
9430 .split_first()
9431 .ok_or_else(|| self.syntax_err("push requires arguments", line))?;
9432 Ok(Expr {
9433 kind: ExprKind::Push {
9434 array: Box::new(first.clone()),
9435 values: rest.to_vec(),
9436 },
9437 line,
9438 })
9439 }
9440 "pop" => {
9441 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9442 return Ok(e);
9443 }
9444 let a = self.parse_one_arg_or_argv()?;
9445 Ok(Expr {
9446 kind: ExprKind::Pop(Box::new(a)),
9447 line,
9448 })
9449 }
9450 "shift" => {
9451 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9452 return Ok(e);
9453 }
9454 let a = self.parse_one_arg_or_argv()?;
9455 Ok(Expr {
9456 kind: ExprKind::Shift(Box::new(a)),
9457 line,
9458 })
9459 }
9460 "unshift" => {
9461 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9462 return Ok(e);
9463 }
9464 let args = self.parse_builtin_args()?;
9465 let (first, rest) = args
9466 .split_first()
9467 .ok_or_else(|| self.syntax_err("unshift requires arguments", line))?;
9468 Ok(Expr {
9469 kind: ExprKind::Unshift {
9470 array: Box::new(first.clone()),
9471 values: rest.to_vec(),
9472 },
9473 line,
9474 })
9475 }
9476 "splice" => {
9477 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9478 return Ok(e);
9479 }
9480 let args = self.parse_builtin_args()?;
9481 let mut iter = args.into_iter();
9482 let array = Box::new(
9483 iter.next()
9484 .ok_or_else(|| self.syntax_err("splice requires arguments", line))?,
9485 );
9486 let offset = iter.next().map(Box::new);
9487 let length = iter.next().map(Box::new);
9488 let replacement: Vec<Expr> = iter.collect();
9489 Ok(Expr {
9490 kind: ExprKind::Splice {
9491 array,
9492 offset,
9493 length,
9494 replacement,
9495 },
9496 line,
9497 })
9498 }
9499 "splice_last" | "splice1" | "spl_last" => {
9504 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9505 return Ok(e);
9506 }
9507 let args = self.parse_builtin_args()?;
9508 let mut iter = args.into_iter();
9509 let array = Box::new(
9510 iter.next()
9511 .ok_or_else(|| self.syntax_err("splice_last requires arguments", line))?,
9512 );
9513 let offset = iter.next().map(Box::new);
9514 let length = iter.next().map(Box::new);
9515 let replacement: Vec<Expr> = iter.collect();
9516 let splice_expr = Expr {
9517 kind: ExprKind::Splice {
9518 array,
9519 offset,
9520 length,
9521 replacement,
9522 },
9523 line,
9524 };
9525 Ok(Expr {
9526 kind: ExprKind::FuncCall {
9527 name: "tail".to_string(),
9528 args: vec![splice_expr],
9529 },
9530 line,
9531 })
9532 }
9533 "delete" => {
9534 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9535 return Ok(e);
9536 }
9537 let a = self.parse_postfix()?;
9538 Ok(Expr {
9539 kind: ExprKind::Delete(Box::new(a)),
9540 line,
9541 })
9542 }
9543 "exists" => {
9544 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9545 return Ok(e);
9546 }
9547 let a = self.parse_postfix()?;
9548 Ok(Expr {
9549 kind: ExprKind::Exists(Box::new(a)),
9550 line,
9551 })
9552 }
9553 "keys" => {
9554 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9555 return Ok(e);
9556 }
9557 let a = self.parse_one_arg_or_default()?;
9558 Ok(Expr {
9559 kind: ExprKind::Keys(Box::new(a)),
9560 line,
9561 })
9562 }
9563 "values" => {
9564 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9565 return Ok(e);
9566 }
9567 let a = self.parse_one_arg_or_default()?;
9568 Ok(Expr {
9569 kind: ExprKind::Values(Box::new(a)),
9570 line,
9571 })
9572 }
9573 "each" => {
9574 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9575 return Ok(e);
9576 }
9577 let a = self.parse_one_arg_or_default()?;
9578 Ok(Expr {
9579 kind: ExprKind::Each(Box::new(a)),
9580 line,
9581 })
9582 }
9583 "fore" | "e" | "ep" => {
9584 if matches!(self.peek(), Token::LBrace) {
9586 let (block, list) = self.parse_block_list()?;
9587 Ok(Expr {
9588 kind: ExprKind::ForEachExpr {
9589 block,
9590 list: Box::new(list),
9591 },
9592 line,
9593 })
9594 } else if self.in_pipe_rhs() {
9595 let is_terminal = matches!(
9598 self.peek(),
9599 Token::Semicolon
9600 | Token::RParen
9601 | Token::Eof
9602 | Token::PipeForward
9603 | Token::RBrace
9604 );
9605 let block = if name == "ep" && is_terminal {
9606 vec![Statement {
9607 label: None,
9608 kind: StmtKind::Expression(Expr {
9609 kind: ExprKind::Say {
9610 handle: None,
9611 args: vec![Expr {
9612 kind: ExprKind::ScalarVar("_".into()),
9613 line,
9614 }],
9615 },
9616 line,
9617 }),
9618 line,
9619 }]
9620 } else {
9621 let expr = self.parse_assign_expr_stop_at_pipe()?;
9622 let expr = Self::lift_bareword_to_topic_call(expr);
9623 vec![Statement {
9624 label: None,
9625 kind: StmtKind::Expression(expr),
9626 line,
9627 }]
9628 };
9629 let list = self.pipe_placeholder_list(line);
9630 Ok(Expr {
9631 kind: ExprKind::ForEachExpr {
9632 block,
9633 list: Box::new(list),
9634 },
9635 line,
9636 })
9637 } else {
9638 let expr = self.parse_assign_expr()?;
9647 let expr = Self::lift_bareword_to_topic_call(expr);
9648 if !matches!(self.peek(), Token::Comma) && name == "ep" {
9649 let block = vec![Statement {
9650 label: None,
9651 kind: StmtKind::Expression(Expr {
9652 kind: ExprKind::Say {
9653 handle: None,
9654 args: vec![Expr {
9655 kind: ExprKind::ScalarVar("_".into()),
9656 line,
9657 }],
9658 },
9659 line,
9660 }),
9661 line,
9662 }];
9663 return Ok(Expr {
9664 kind: ExprKind::ForEachExpr {
9665 block,
9666 list: Box::new(expr),
9667 },
9668 line,
9669 });
9670 }
9671 self.expect(&Token::Comma)?;
9672 let list_parts = self.parse_list_until_terminator()?;
9673 let list_expr = if list_parts.len() == 1 {
9674 list_parts.into_iter().next().unwrap()
9675 } else {
9676 Expr {
9677 kind: ExprKind::List(list_parts),
9678 line,
9679 }
9680 };
9681 let block = vec![Statement {
9682 label: None,
9683 kind: StmtKind::Expression(expr),
9684 line,
9685 }];
9686 Ok(Expr {
9687 kind: ExprKind::ForEachExpr {
9688 block,
9689 list: Box::new(list_expr),
9690 },
9691 line,
9692 })
9693 }
9694 }
9695 "rev" => {
9696 let a = if self.in_pipe_rhs()
9702 && matches!(
9703 self.peek(),
9704 Token::Semicolon | Token::RParen | Token::Eof | Token::PipeForward
9705 ) {
9706 self.pipe_placeholder_list(line)
9707 } else if matches!(
9708 self.peek(),
9709 Token::Semicolon
9710 | Token::RBrace
9711 | Token::RParen
9712 | Token::RBracket
9713 | Token::Eof
9714 | Token::Comma
9715 | Token::FatArrow
9716 | Token::PipeForward
9717 ) {
9718 Expr {
9719 kind: ExprKind::ScalarVar("_".into()),
9720 line: self.peek_line(),
9721 }
9722 } else if matches!(self.peek(), Token::LParen)
9723 && matches!(self.peek_at(1), Token::RParen)
9724 {
9725 let pl = self.peek_line();
9728 self.advance(); self.advance(); Expr {
9731 kind: ExprKind::ScalarVar("_".into()),
9732 line: pl,
9733 }
9734 } else {
9735 self.parse_one_arg()?
9736 };
9737 Ok(Expr {
9738 kind: ExprKind::Rev(Box::new(a)),
9739 line,
9740 })
9741 }
9742 "reverse" => {
9743 if crate::no_interop_mode() {
9744 return Err(self.syntax_err(
9745 "stryke uses `rev` instead of `reverse` (--no-interop)",
9746 line,
9747 ));
9748 }
9749 let a = if self.in_pipe_rhs()
9751 && matches!(
9752 self.peek(),
9753 Token::Semicolon
9754 | Token::RBrace
9755 | Token::RParen
9756 | Token::Eof
9757 | Token::PipeForward
9758 ) {
9759 self.pipe_placeholder_list(line)
9760 } else {
9761 self.parse_one_arg()?
9762 };
9763 Ok(Expr {
9764 kind: ExprKind::ReverseExpr(Box::new(a)),
9765 line,
9766 })
9767 }
9768 "reversed" | "rv" => {
9769 let a = if self.in_pipe_rhs()
9771 && matches!(
9772 self.peek(),
9773 Token::Semicolon
9774 | Token::RBrace
9775 | Token::RParen
9776 | Token::Eof
9777 | Token::PipeForward
9778 ) {
9779 self.pipe_placeholder_list(line)
9780 } else {
9781 self.parse_one_arg()?
9782 };
9783 Ok(Expr {
9784 kind: ExprKind::Rev(Box::new(a)),
9785 line,
9786 })
9787 }
9788 "join" => {
9789 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9790 return Ok(e);
9791 }
9792 let args = self.parse_builtin_args()?;
9793 if args.is_empty() {
9794 return Err(self.syntax_err("join requires separator and list", line));
9795 }
9796 if args.len() < 2 && !self.in_pipe_rhs() {
9798 return Err(self.syntax_err("join requires separator and list", line));
9799 }
9800 Ok(Expr {
9801 kind: ExprKind::JoinExpr {
9802 separator: Box::new(args[0].clone()),
9803 list: Box::new(Expr {
9804 kind: ExprKind::List(args[1..].to_vec()),
9805 line,
9806 }),
9807 },
9808 line,
9809 })
9810 }
9811 "split" => {
9812 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9813 return Ok(e);
9814 }
9815 let args = self.parse_builtin_args()?;
9816 let pattern = args.first().cloned().unwrap_or(Expr {
9817 kind: ExprKind::String(" ".into()),
9818 line,
9819 });
9820 let string = args.get(1).cloned().unwrap_or(Expr {
9821 kind: ExprKind::ScalarVar("_".into()),
9822 line,
9823 });
9824 let limit = args.get(2).cloned().map(Box::new);
9825 Ok(Expr {
9826 kind: ExprKind::SplitExpr {
9827 pattern: Box::new(pattern),
9828 string: Box::new(string),
9829 limit,
9830 },
9831 line,
9832 })
9833 }
9834 "substr" => {
9835 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9836 return Ok(e);
9837 }
9838 let args = self.parse_builtin_args()?;
9839 Ok(Expr {
9840 kind: ExprKind::Substr {
9841 string: Box::new(args[0].clone()),
9842 offset: Box::new(args[1].clone()),
9843 length: args.get(2).cloned().map(Box::new),
9844 replacement: args.get(3).cloned().map(Box::new),
9845 },
9846 line,
9847 })
9848 }
9849 "index" => {
9850 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9851 return Ok(e);
9852 }
9853 let args = self.parse_builtin_args()?;
9854 Ok(Expr {
9855 kind: ExprKind::Index {
9856 string: Box::new(args[0].clone()),
9857 substr: Box::new(args[1].clone()),
9858 position: args.get(2).cloned().map(Box::new),
9859 },
9860 line,
9861 })
9862 }
9863 "rindex" => {
9864 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9865 return Ok(e);
9866 }
9867 let args = self.parse_builtin_args()?;
9868 Ok(Expr {
9869 kind: ExprKind::Rindex {
9870 string: Box::new(args[0].clone()),
9871 substr: Box::new(args[1].clone()),
9872 position: args.get(2).cloned().map(Box::new),
9873 },
9874 line,
9875 })
9876 }
9877 "sprintf" => {
9878 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9879 return Ok(e);
9880 }
9881 let args = self.parse_builtin_args()?;
9882 let (first, rest) = args
9883 .split_first()
9884 .ok_or_else(|| self.syntax_err("sprintf requires format", line))?;
9885 Ok(Expr {
9886 kind: ExprKind::Sprintf {
9887 format: Box::new(first.clone()),
9888 args: rest.to_vec(),
9889 },
9890 line,
9891 })
9892 }
9893 "map" | "flat_map" | "maps" | "flat_maps" => {
9894 let flatten_array_refs = matches!(name.as_str(), "flat_map" | "flat_maps");
9895 let stream = matches!(name.as_str(), "maps" | "flat_maps");
9896 if matches!(self.peek(), Token::LBrace) {
9897 let (block, list) = self.parse_block_list()?;
9898 Ok(Expr {
9899 kind: ExprKind::MapExpr {
9900 block,
9901 list: Box::new(list),
9902 flatten_array_refs,
9903 stream,
9904 },
9905 line,
9906 })
9907 } else {
9908 let expr = self.parse_assign_expr_stop_at_pipe()?;
9909 let expr = Self::lift_bareword_to_topic_call(expr);
9912 let list_expr = if self.in_pipe_rhs()
9913 && matches!(
9914 self.peek(),
9915 Token::Semicolon
9916 | Token::RBrace
9917 | Token::RParen
9918 | Token::Eof
9919 | Token::PipeForward
9920 ) {
9921 self.pipe_placeholder_list(line)
9922 } else {
9923 self.expect(&Token::Comma)?;
9924 let list_parts = self.parse_list_until_terminator()?;
9925 if list_parts.len() == 1 {
9926 list_parts.into_iter().next().unwrap()
9927 } else {
9928 Expr {
9929 kind: ExprKind::List(list_parts),
9930 line,
9931 }
9932 }
9933 };
9934 Ok(Expr {
9935 kind: ExprKind::MapExprComma {
9936 expr: Box::new(expr),
9937 list: Box::new(list_expr),
9938 flatten_array_refs,
9939 stream,
9940 },
9941 line,
9942 })
9943 }
9944 }
9945 "cond" => {
9946 if crate::compat_mode() {
9947 return Err(self
9948 .syntax_err("`cond` is a stryke extension (disabled by --compat)", line));
9949 }
9950 self.parse_cond_expr(line)
9951 }
9952 "match" => {
9953 if crate::compat_mode() {
9954 return Err(self.syntax_err(
9955 "algebraic `match` is a stryke extension (disabled by --compat)",
9956 line,
9957 ));
9958 }
9959 self.parse_algebraic_match_expr(line)
9960 }
9961 "grep" | "greps" | "filter" | "fi" | "find_all" => {
9962 let keyword = match name.as_str() {
9963 "grep" => crate::ast::GrepBuiltinKeyword::Grep,
9964 "greps" => crate::ast::GrepBuiltinKeyword::Greps,
9965 "filter" | "fi" => crate::ast::GrepBuiltinKeyword::Filter,
9966 "find_all" => crate::ast::GrepBuiltinKeyword::FindAll,
9967 _ => unreachable!(),
9968 };
9969 if matches!(self.peek(), Token::LBrace) {
9970 let (block, list) = self.parse_block_list()?;
9971 Ok(Expr {
9972 kind: ExprKind::GrepExpr {
9973 block,
9974 list: Box::new(list),
9975 keyword,
9976 },
9977 line,
9978 })
9979 } else {
9980 let expr = self.parse_assign_expr_stop_at_pipe()?;
9981 if self.in_pipe_rhs()
9982 && matches!(
9983 self.peek(),
9984 Token::Semicolon
9985 | Token::RBrace
9986 | Token::RParen
9987 | Token::Eof
9988 | Token::PipeForward
9989 )
9990 {
9991 let list = self.pipe_placeholder_list(line);
9996 let topic = Expr {
9997 kind: ExprKind::ScalarVar("_".into()),
9998 line,
9999 };
10000 let test = match &expr.kind {
10001 ExprKind::Integer(_) | ExprKind::Float(_) => Expr {
10002 kind: ExprKind::BinOp {
10003 op: BinOp::NumEq,
10004 left: Box::new(topic),
10005 right: Box::new(expr),
10006 },
10007 line,
10008 },
10009 ExprKind::String(_) | ExprKind::InterpolatedString(_) => Expr {
10010 kind: ExprKind::BinOp {
10011 op: BinOp::StrEq,
10012 left: Box::new(topic),
10013 right: Box::new(expr),
10014 },
10015 line,
10016 },
10017 ExprKind::Regex { .. } => Expr {
10018 kind: ExprKind::BinOp {
10019 op: BinOp::BindMatch,
10020 left: Box::new(topic),
10021 right: Box::new(expr),
10022 },
10023 line,
10024 },
10025 _ => {
10026 Self::lift_bareword_to_topic_call(expr)
10028 }
10029 };
10030 let block = vec![Statement {
10031 label: None,
10032 kind: StmtKind::Expression(test),
10033 line,
10034 }];
10035 Ok(Expr {
10036 kind: ExprKind::GrepExpr {
10037 block,
10038 list: Box::new(list),
10039 keyword,
10040 },
10041 line,
10042 })
10043 } else {
10044 let expr = Self::lift_bareword_to_topic_call(expr);
10045 self.expect(&Token::Comma)?;
10046 let list_parts = self.parse_list_until_terminator()?;
10047 let list_expr = if list_parts.len() == 1 {
10048 list_parts.into_iter().next().unwrap()
10049 } else {
10050 Expr {
10051 kind: ExprKind::List(list_parts),
10052 line,
10053 }
10054 };
10055 Ok(Expr {
10056 kind: ExprKind::GrepExprComma {
10057 expr: Box::new(expr),
10058 list: Box::new(list_expr),
10059 keyword,
10060 },
10061 line,
10062 })
10063 }
10064 }
10065 }
10066 "sort" => {
10067 use crate::ast::SortComparator;
10068 if matches!(self.peek(), Token::LBrace) {
10069 let block = self.parse_block()?;
10070 let block_end_line = self.prev_line();
10071 let _ = self.eat(&Token::Comma);
10072 let list = if self.in_pipe_rhs()
10073 && (matches!(
10074 self.peek(),
10075 Token::Semicolon
10076 | Token::RBrace
10077 | Token::RParen
10078 | Token::Eof
10079 | Token::PipeForward
10080 ) || self.peek_line() > block_end_line)
10081 {
10082 self.pipe_placeholder_list(line)
10083 } else {
10084 self.parse_expression()?
10085 };
10086 Ok(Expr {
10087 kind: ExprKind::SortExpr {
10088 cmp: Some(SortComparator::Block(block)),
10089 list: Box::new(list),
10090 },
10091 line,
10092 })
10093 } else if matches!(self.peek(), Token::ScalarVar(ref v) if v == "a" || v == "b") {
10094 let block = self.parse_block_or_bareword_cmp_block()?;
10096 let _ = self.eat(&Token::Comma);
10097 let list = if self.in_pipe_rhs()
10098 && matches!(
10099 self.peek(),
10100 Token::Semicolon
10101 | Token::RBrace
10102 | Token::RParen
10103 | Token::Eof
10104 | Token::PipeForward
10105 ) {
10106 self.pipe_placeholder_list(line)
10107 } else {
10108 self.parse_expression()?
10109 };
10110 Ok(Expr {
10111 kind: ExprKind::SortExpr {
10112 cmp: Some(SortComparator::Block(block)),
10113 list: Box::new(list),
10114 },
10115 line,
10116 })
10117 } else if matches!(self.peek(), Token::ScalarVar(_)) {
10118 self.suppress_indirect_paren_call =
10120 self.suppress_indirect_paren_call.saturating_add(1);
10121 let code = self.parse_assign_expr()?;
10122 self.suppress_indirect_paren_call =
10123 self.suppress_indirect_paren_call.saturating_sub(1);
10124 let list = if matches!(self.peek(), Token::LParen) {
10125 self.advance();
10126 let e = self.parse_expression()?;
10127 self.expect(&Token::RParen)?;
10128 e
10129 } else {
10130 self.parse_expression()?
10131 };
10132 Ok(Expr {
10133 kind: ExprKind::SortExpr {
10134 cmp: Some(SortComparator::Code(Box::new(code))),
10135 list: Box::new(list),
10136 },
10137 line,
10138 })
10139 } else if matches!(self.peek(), Token::Ident(ref name) if !Self::is_known_bareword(name))
10140 {
10141 let block = self.parse_block_or_bareword_cmp_block()?;
10143 let _ = self.eat(&Token::Comma);
10144 let list = if self.in_pipe_rhs()
10145 && matches!(
10146 self.peek(),
10147 Token::Semicolon
10148 | Token::RBrace
10149 | Token::RParen
10150 | Token::Eof
10151 | Token::PipeForward
10152 ) {
10153 self.pipe_placeholder_list(line)
10154 } else {
10155 self.parse_expression()?
10156 };
10157 Ok(Expr {
10158 kind: ExprKind::SortExpr {
10159 cmp: Some(SortComparator::Block(block)),
10160 list: Box::new(list),
10161 },
10162 line,
10163 })
10164 } else {
10165 let list = if self.in_pipe_rhs()
10168 && matches!(
10169 self.peek(),
10170 Token::Semicolon
10171 | Token::RBrace
10172 | Token::RParen
10173 | Token::Eof
10174 | Token::PipeForward
10175 ) {
10176 self.pipe_placeholder_list(line)
10177 } else {
10178 self.parse_expression()?
10179 };
10180 Ok(Expr {
10181 kind: ExprKind::SortExpr {
10182 cmp: None,
10183 list: Box::new(list),
10184 },
10185 line,
10186 })
10187 }
10188 }
10189 "reduce" | "fold" | "inject" => {
10190 let (block, list) = self.parse_block_list()?;
10191 Ok(Expr {
10192 kind: ExprKind::ReduceExpr {
10193 block,
10194 list: Box::new(list),
10195 },
10196 line,
10197 })
10198 }
10199 "pmap" => {
10201 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
10202 Ok(Expr {
10203 kind: ExprKind::PMapExpr {
10204 block,
10205 list: Box::new(list),
10206 progress: progress.map(Box::new),
10207 flat_outputs: false,
10208 on_cluster: None,
10209 stream: false,
10210 },
10211 line,
10212 })
10213 }
10214 "pmap_on" => {
10215 let (cluster, block, list, progress) =
10216 self.parse_cluster_block_then_list_optional_progress()?;
10217 Ok(Expr {
10218 kind: ExprKind::PMapExpr {
10219 block,
10220 list: Box::new(list),
10221 progress: progress.map(Box::new),
10222 flat_outputs: false,
10223 on_cluster: Some(Box::new(cluster)),
10224 stream: false,
10225 },
10226 line,
10227 })
10228 }
10229 "pflat_map" => {
10230 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
10231 Ok(Expr {
10232 kind: ExprKind::PMapExpr {
10233 block,
10234 list: Box::new(list),
10235 progress: progress.map(Box::new),
10236 flat_outputs: true,
10237 on_cluster: None,
10238 stream: false,
10239 },
10240 line,
10241 })
10242 }
10243 "pflat_map_on" => {
10244 let (cluster, block, list, progress) =
10245 self.parse_cluster_block_then_list_optional_progress()?;
10246 Ok(Expr {
10247 kind: ExprKind::PMapExpr {
10248 block,
10249 list: Box::new(list),
10250 progress: progress.map(Box::new),
10251 flat_outputs: true,
10252 on_cluster: Some(Box::new(cluster)),
10253 stream: false,
10254 },
10255 line,
10256 })
10257 }
10258 "pmaps" => {
10259 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
10260 Ok(Expr {
10261 kind: ExprKind::PMapExpr {
10262 block,
10263 list: Box::new(list),
10264 progress: progress.map(Box::new),
10265 flat_outputs: false,
10266 on_cluster: None,
10267 stream: true,
10268 },
10269 line,
10270 })
10271 }
10272 "pflat_maps" => {
10273 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
10274 Ok(Expr {
10275 kind: ExprKind::PMapExpr {
10276 block,
10277 list: Box::new(list),
10278 progress: progress.map(Box::new),
10279 flat_outputs: true,
10280 on_cluster: None,
10281 stream: true,
10282 },
10283 line,
10284 })
10285 }
10286 "pgreps" => {
10287 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
10288 Ok(Expr {
10289 kind: ExprKind::PGrepExpr {
10290 block,
10291 list: Box::new(list),
10292 progress: progress.map(Box::new),
10293 stream: true,
10294 },
10295 line,
10296 })
10297 }
10298 "pmap_chunked" => {
10299 let chunk_size = self.parse_assign_expr()?;
10300 let block = self.parse_block_or_bareword_block()?;
10301 self.eat(&Token::Comma);
10302 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
10303 Ok(Expr {
10304 kind: ExprKind::PMapChunkedExpr {
10305 chunk_size: Box::new(chunk_size),
10306 block,
10307 list: Box::new(list),
10308 progress: progress.map(Box::new),
10309 },
10310 line,
10311 })
10312 }
10313 "pgrep" => {
10314 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
10315 Ok(Expr {
10316 kind: ExprKind::PGrepExpr {
10317 block,
10318 list: Box::new(list),
10319 progress: progress.map(Box::new),
10320 stream: false,
10321 },
10322 line,
10323 })
10324 }
10325 "pfor" => {
10326 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
10327 Ok(Expr {
10328 kind: ExprKind::PForExpr {
10329 block,
10330 list: Box::new(list),
10331 progress: progress.map(Box::new),
10332 },
10333 line,
10334 })
10335 }
10336 "par_lines" | "par_walk" => {
10337 let args = self.parse_builtin_args()?;
10338 if args.len() < 2 {
10339 return Err(
10340 self.syntax_err(format!("{} requires at least two arguments", name), line)
10341 );
10342 }
10343
10344 if name == "par_lines" {
10345 Ok(Expr {
10346 kind: ExprKind::ParLinesExpr {
10347 path: Box::new(args[0].clone()),
10348 callback: Box::new(args[1].clone()),
10349 progress: None,
10350 },
10351 line,
10352 })
10353 } else {
10354 Ok(Expr {
10355 kind: ExprKind::ParWalkExpr {
10356 path: Box::new(args[0].clone()),
10357 callback: Box::new(args[1].clone()),
10358 progress: None,
10359 },
10360 line,
10361 })
10362 }
10363 }
10364 "pwatch" | "watch" => {
10365 let args = self.parse_builtin_args()?;
10366 if args.len() < 2 {
10367 return Err(
10368 self.syntax_err(format!("{} requires at least two arguments", name), line)
10369 );
10370 }
10371 Ok(Expr {
10372 kind: ExprKind::PwatchExpr {
10373 path: Box::new(args[0].clone()),
10374 callback: Box::new(args[1].clone()),
10375 },
10376 line,
10377 })
10378 }
10379 "fan" => {
10380 let (count, block) = self.parse_fan_count_and_block(line)?;
10386 let progress = self.parse_fan_optional_progress("fan")?;
10387 Ok(Expr {
10388 kind: ExprKind::FanExpr {
10389 count,
10390 block,
10391 progress,
10392 capture: false,
10393 },
10394 line,
10395 })
10396 }
10397 "fan_cap" => {
10398 let (count, block) = self.parse_fan_count_and_block(line)?;
10399 let progress = self.parse_fan_optional_progress("fan_cap")?;
10400 Ok(Expr {
10401 kind: ExprKind::FanExpr {
10402 count,
10403 block,
10404 progress,
10405 capture: true,
10406 },
10407 line,
10408 })
10409 }
10410 "async" => {
10411 if !matches!(self.peek(), Token::LBrace) {
10412 return Err(self.syntax_err("async must be followed by { BLOCK }", line));
10413 }
10414 let block = self.parse_block()?;
10415 Ok(Expr {
10416 kind: ExprKind::AsyncBlock { body: block },
10417 line,
10418 })
10419 }
10420 "spawn" => {
10421 if !matches!(self.peek(), Token::LBrace) {
10422 return Err(self.syntax_err("spawn must be followed by { BLOCK }", line));
10423 }
10424 let block = self.parse_block()?;
10425 Ok(Expr {
10426 kind: ExprKind::SpawnBlock { body: block },
10427 line,
10428 })
10429 }
10430 "trace" => {
10431 if !matches!(self.peek(), Token::LBrace) {
10432 return Err(self.syntax_err("trace must be followed by { BLOCK }", line));
10433 }
10434 let block = self.parse_block()?;
10435 Ok(Expr {
10436 kind: ExprKind::Trace { body: block },
10437 line,
10438 })
10439 }
10440 "timer" => {
10441 let block = self.parse_block_or_bareword_block_no_args()?;
10442 Ok(Expr {
10443 kind: ExprKind::Timer { body: block },
10444 line,
10445 })
10446 }
10447 "bench" => {
10448 let block = self.parse_block_or_bareword_block_no_args()?;
10449 let times = Box::new(self.parse_expression()?);
10450 Ok(Expr {
10451 kind: ExprKind::Bench { body: block, times },
10452 line,
10453 })
10454 }
10455 "spinner" => {
10456 let (message, body) = if matches!(self.peek(), Token::LBrace) {
10458 let body = self.parse_block()?;
10459 (
10460 Box::new(Expr {
10461 kind: ExprKind::String("working".to_string()),
10462 line,
10463 }),
10464 body,
10465 )
10466 } else {
10467 let msg = self.parse_assign_expr()?;
10468 let body = self.parse_block()?;
10469 (Box::new(msg), body)
10470 };
10471 Ok(Expr {
10472 kind: ExprKind::Spinner { message, body },
10473 line,
10474 })
10475 }
10476 "thread" | "t" => {
10477 self.parse_thread_macro(line, false)
10487 }
10488 "retry" => {
10489 let body = if matches!(self.peek(), Token::LBrace) {
10492 self.parse_block()?
10493 } else {
10494 let bw_line = self.peek_line();
10495 let Token::Ident(ref name) = self.peek().clone() else {
10496 return Err(self
10497 .syntax_err("retry: expected block or bareword function name", line));
10498 };
10499 let name = name.clone();
10500 self.advance();
10501 vec![Statement::new(
10502 StmtKind::Expression(Expr {
10503 kind: ExprKind::FuncCall { name, args: vec![] },
10504 line: bw_line,
10505 }),
10506 bw_line,
10507 )]
10508 };
10509 self.eat(&Token::Comma);
10510 match self.peek() {
10511 Token::Ident(ref s) if s == "times" => {
10512 self.advance();
10513 }
10514 _ => {
10515 return Err(self.syntax_err("retry: expected `times =>` after block", line));
10516 }
10517 }
10518 self.expect(&Token::FatArrow)?;
10519 let times = Box::new(self.parse_assign_expr()?);
10520 let mut backoff = RetryBackoff::None;
10521 if self.eat(&Token::Comma) {
10522 match self.peek() {
10523 Token::Ident(ref s) if s == "backoff" => {
10524 self.advance();
10525 }
10526 _ => {
10527 return Err(
10528 self.syntax_err("retry: expected `backoff =>` after comma", line)
10529 );
10530 }
10531 }
10532 self.expect(&Token::FatArrow)?;
10533 let Token::Ident(mode) = self.peek().clone() else {
10534 return Err(self.syntax_err(
10535 "retry: expected backoff mode (none, linear, exponential)",
10536 line,
10537 ));
10538 };
10539 backoff = match mode.as_str() {
10540 "none" => RetryBackoff::None,
10541 "linear" => RetryBackoff::Linear,
10542 "exponential" => RetryBackoff::Exponential,
10543 _ => {
10544 return Err(
10545 self.syntax_err(format!("retry: invalid backoff `{mode}`"), line)
10546 );
10547 }
10548 };
10549 self.advance();
10550 }
10551 Ok(Expr {
10552 kind: ExprKind::RetryBlock {
10553 body,
10554 times,
10555 backoff,
10556 },
10557 line,
10558 })
10559 }
10560 "rate_limit" => {
10561 self.expect(&Token::LParen)?;
10562 let max = Box::new(self.parse_assign_expr()?);
10563 self.expect(&Token::Comma)?;
10564 let window = Box::new(self.parse_assign_expr()?);
10565 self.expect(&Token::RParen)?;
10566 let body = self.parse_block_or_bareword_block_no_args()?;
10567 let slot = self.alloc_rate_limit_slot();
10568 Ok(Expr {
10569 kind: ExprKind::RateLimitBlock {
10570 slot,
10571 max,
10572 window,
10573 body,
10574 },
10575 line,
10576 })
10577 }
10578 "every" => {
10579 let has_paren = self.eat(&Token::LParen);
10582 let interval = Box::new(self.parse_assign_expr()?);
10583 if has_paren {
10584 self.expect(&Token::RParen)?;
10585 }
10586 let body = if matches!(self.peek(), Token::LBrace) {
10587 self.parse_block()?
10588 } else {
10589 let bline = self.peek_line();
10590 let expr = self.parse_assign_expr()?;
10591 vec![Statement::new(StmtKind::Expression(expr), bline)]
10592 };
10593 Ok(Expr {
10594 kind: ExprKind::EveryBlock { interval, body },
10595 line,
10596 })
10597 }
10598 "gen" => {
10599 if !matches!(self.peek(), Token::LBrace) {
10600 return Err(self.syntax_err("gen must be followed by { BLOCK }", line));
10601 }
10602 let body = self.parse_block()?;
10603 Ok(Expr {
10604 kind: ExprKind::GenBlock { body },
10605 line,
10606 })
10607 }
10608 "yield" => {
10609 let e = self.parse_assign_expr()?;
10610 Ok(Expr {
10611 kind: ExprKind::Yield(Box::new(e)),
10612 line,
10613 })
10614 }
10615 "await" => {
10616 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10617 return Ok(e);
10618 }
10619 let a = self.parse_one_arg_or_default()?;
10622 Ok(Expr {
10623 kind: ExprKind::Await(Box::new(a)),
10624 line,
10625 })
10626 }
10627 "slurp" | "cat" | "c" => {
10628 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10629 return Ok(e);
10630 }
10631 let a = self.parse_one_arg_or_default()?;
10632 Ok(Expr {
10633 kind: ExprKind::Slurp(Box::new(a)),
10634 line,
10635 })
10636 }
10637 "capture" => {
10638 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10639 return Ok(e);
10640 }
10641 let a = self.parse_one_arg()?;
10642 Ok(Expr {
10643 kind: ExprKind::Capture(Box::new(a)),
10644 line,
10645 })
10646 }
10647 "fetch_url" => {
10648 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10649 return Ok(e);
10650 }
10651 let a = self.parse_one_arg()?;
10652 Ok(Expr {
10653 kind: ExprKind::FetchUrl(Box::new(a)),
10654 line,
10655 })
10656 }
10657 "pchannel" => {
10658 let capacity = if self.eat(&Token::LParen) {
10659 if matches!(self.peek(), Token::RParen) {
10660 self.advance();
10661 None
10662 } else {
10663 let e = self.parse_expression()?;
10664 self.expect(&Token::RParen)?;
10665 Some(Box::new(e))
10666 }
10667 } else {
10668 None
10669 };
10670 Ok(Expr {
10671 kind: ExprKind::Pchannel { capacity },
10672 line,
10673 })
10674 }
10675 "psort" => {
10676 if matches!(self.peek(), Token::LBrace)
10677 || matches!(self.peek(), Token::ScalarVar(ref v) if v == "a" || v == "b")
10678 || matches!(self.peek(), Token::Ident(ref name) if !Self::is_known_bareword(name))
10679 {
10680 let block = self.parse_block_or_bareword_cmp_block()?;
10681 self.eat(&Token::Comma);
10682 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
10683 Ok(Expr {
10684 kind: ExprKind::PSortExpr {
10685 cmp: Some(block),
10686 list: Box::new(list),
10687 progress: progress.map(Box::new),
10688 },
10689 line,
10690 })
10691 } else {
10692 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
10693 Ok(Expr {
10694 kind: ExprKind::PSortExpr {
10695 cmp: None,
10696 list: Box::new(list),
10697 progress: progress.map(Box::new),
10698 },
10699 line,
10700 })
10701 }
10702 }
10703 "preduce" => {
10704 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
10705 Ok(Expr {
10706 kind: ExprKind::PReduceExpr {
10707 block,
10708 list: Box::new(list),
10709 progress: progress.map(Box::new),
10710 },
10711 line,
10712 })
10713 }
10714 "preduce_init" => {
10715 let (init, block, list, progress) =
10716 self.parse_init_block_then_list_optional_progress()?;
10717 Ok(Expr {
10718 kind: ExprKind::PReduceInitExpr {
10719 init: Box::new(init),
10720 block,
10721 list: Box::new(list),
10722 progress: progress.map(Box::new),
10723 },
10724 line,
10725 })
10726 }
10727 "pmap_reduce" => {
10728 let map_block = self.parse_block_or_bareword_block()?;
10729 let reduce_block = if matches!(self.peek(), Token::LBrace) {
10732 self.parse_block()?
10733 } else {
10734 self.expect(&Token::Comma)?;
10736 self.parse_block_or_bareword_cmp_block()?
10737 };
10738 self.eat(&Token::Comma);
10739 let line = self.peek_line();
10740 if let Token::Ident(ref kw) = self.peek().clone() {
10741 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
10742 self.advance();
10743 self.expect(&Token::FatArrow)?;
10744 let prog = self.parse_assign_expr()?;
10745 return Ok(Expr {
10746 kind: ExprKind::PMapReduceExpr {
10747 map_block,
10748 reduce_block,
10749 list: Box::new(Expr {
10750 kind: ExprKind::List(vec![]),
10751 line,
10752 }),
10753 progress: Some(Box::new(prog)),
10754 },
10755 line,
10756 });
10757 }
10758 }
10759 if matches!(
10760 self.peek(),
10761 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof
10762 ) {
10763 return Ok(Expr {
10764 kind: ExprKind::PMapReduceExpr {
10765 map_block,
10766 reduce_block,
10767 list: Box::new(Expr {
10768 kind: ExprKind::List(vec![]),
10769 line,
10770 }),
10771 progress: None,
10772 },
10773 line,
10774 });
10775 }
10776 let mut parts = vec![self.parse_assign_expr()?];
10777 loop {
10778 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
10779 break;
10780 }
10781 if matches!(
10782 self.peek(),
10783 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof
10784 ) {
10785 break;
10786 }
10787 if let Token::Ident(ref kw) = self.peek().clone() {
10788 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
10789 self.advance();
10790 self.expect(&Token::FatArrow)?;
10791 let prog = self.parse_assign_expr()?;
10792 return Ok(Expr {
10793 kind: ExprKind::PMapReduceExpr {
10794 map_block,
10795 reduce_block,
10796 list: Box::new(merge_expr_list(parts)),
10797 progress: Some(Box::new(prog)),
10798 },
10799 line,
10800 });
10801 }
10802 }
10803 parts.push(self.parse_assign_expr()?);
10804 }
10805 Ok(Expr {
10806 kind: ExprKind::PMapReduceExpr {
10807 map_block,
10808 reduce_block,
10809 list: Box::new(merge_expr_list(parts)),
10810 progress: None,
10811 },
10812 line,
10813 })
10814 }
10815 "puniq" => {
10816 if self.pipe_supplies_slurped_list_operand() {
10817 return Ok(Expr {
10818 kind: ExprKind::FuncCall {
10819 name: "puniq".to_string(),
10820 args: vec![],
10821 },
10822 line,
10823 });
10824 }
10825 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
10826 let mut args = vec![list];
10827 if let Some(p) = progress {
10828 args.push(p);
10829 }
10830 Ok(Expr {
10831 kind: ExprKind::FuncCall {
10832 name: "puniq".to_string(),
10833 args,
10834 },
10835 line,
10836 })
10837 }
10838 "pfirst" => {
10839 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
10840 let cr = Expr {
10841 kind: ExprKind::CodeRef {
10842 params: vec![],
10843 body: block,
10844 },
10845 line,
10846 };
10847 let mut args = vec![cr, list];
10848 if let Some(p) = progress {
10849 args.push(p);
10850 }
10851 Ok(Expr {
10852 kind: ExprKind::FuncCall {
10853 name: "pfirst".to_string(),
10854 args,
10855 },
10856 line,
10857 })
10858 }
10859 "pany" => {
10860 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
10861 let cr = Expr {
10862 kind: ExprKind::CodeRef {
10863 params: vec![],
10864 body: block,
10865 },
10866 line,
10867 };
10868 let mut args = vec![cr, list];
10869 if let Some(p) = progress {
10870 args.push(p);
10871 }
10872 Ok(Expr {
10873 kind: ExprKind::FuncCall {
10874 name: "pany".to_string(),
10875 args,
10876 },
10877 line,
10878 })
10879 }
10880 "uniq" | "distinct" => {
10881 if self.pipe_supplies_slurped_list_operand() {
10882 return Ok(Expr {
10883 kind: ExprKind::FuncCall {
10884 name: name.clone(),
10885 args: vec![],
10886 },
10887 line,
10888 });
10889 }
10890 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
10891 if progress.is_some() {
10892 return Err(self.syntax_err(
10893 "`progress =>` is not supported for uniq (use puniq for parallel + progress)",
10894 line,
10895 ));
10896 }
10897 Ok(Expr {
10898 kind: ExprKind::FuncCall {
10899 name: name.clone(),
10900 args: vec![list],
10901 },
10902 line,
10903 })
10904 }
10905 "flatten" => {
10906 if self.pipe_supplies_slurped_list_operand() {
10907 return Ok(Expr {
10908 kind: ExprKind::FuncCall {
10909 name: "flatten".to_string(),
10910 args: vec![],
10911 },
10912 line,
10913 });
10914 }
10915 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
10916 if progress.is_some() {
10917 return Err(self.syntax_err("`progress =>` is not supported for flatten", line));
10918 }
10919 Ok(Expr {
10920 kind: ExprKind::FuncCall {
10921 name: "flatten".to_string(),
10922 args: vec![list],
10923 },
10924 line,
10925 })
10926 }
10927 "set" => {
10928 if self.pipe_supplies_slurped_list_operand() {
10929 return Ok(Expr {
10930 kind: ExprKind::FuncCall {
10931 name: "set".to_string(),
10932 args: vec![],
10933 },
10934 line,
10935 });
10936 }
10937 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
10938 if progress.is_some() {
10939 return Err(self.syntax_err("`progress =>` is not supported for set", line));
10940 }
10941 Ok(Expr {
10942 kind: ExprKind::FuncCall {
10943 name: "set".to_string(),
10944 args: vec![list],
10945 },
10946 line,
10947 })
10948 }
10949 "size" => {
10953 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10954 return Ok(e);
10955 }
10956 if self.pipe_supplies_slurped_list_operand() {
10957 return Ok(Expr {
10958 kind: ExprKind::FuncCall {
10959 name: "size".to_string(),
10960 args: vec![],
10961 },
10962 line,
10963 });
10964 }
10965 let a = self.parse_one_arg_or_default()?;
10966 Ok(Expr {
10967 kind: ExprKind::FuncCall {
10968 name: "size".to_string(),
10969 args: vec![a],
10970 },
10971 line,
10972 })
10973 }
10974 "list_count" | "list_size" | "count" | "len" | "cnt" => {
10975 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10976 return Ok(e);
10977 }
10978 if self.pipe_supplies_slurped_list_operand() {
10979 return Ok(Expr {
10980 kind: ExprKind::FuncCall {
10981 name: name.clone(),
10982 args: vec![],
10983 },
10984 line,
10985 });
10986 }
10987 let args = if matches!(self.peek(), Token::LParen) {
11001 self.advance();
11002 if matches!(self.peek(), Token::RParen) {
11003 self.advance();
11004 Vec::new()
11005 } else {
11006 let inner = self.parse_expression()?;
11007 self.expect(&Token::RParen)?;
11008 vec![inner]
11009 }
11010 } else if self.peek_is_named_unary_terminator() {
11011 Vec::new()
11012 } else {
11013 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
11014 if progress.is_some() {
11015 return Err(self.syntax_err(
11016 "`progress =>` is not supported for list_count / list_size / count / cnt",
11017 line,
11018 ));
11019 }
11020 vec![list]
11021 };
11022 Ok(Expr {
11023 kind: ExprKind::FuncCall {
11024 name: name.clone(),
11025 args,
11026 },
11027 line,
11028 })
11029 }
11030 "shuffle" | "shuffled" => {
11031 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11032 return Ok(e);
11033 }
11034 if self.pipe_supplies_slurped_list_operand() {
11035 return Ok(Expr {
11036 kind: ExprKind::FuncCall {
11037 name: "shuffle".to_string(),
11038 args: vec![],
11039 },
11040 line,
11041 });
11042 }
11043 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
11044 if progress.is_some() {
11045 return Err(self.syntax_err("`progress =>` is not supported for shuffle", line));
11046 }
11047 Ok(Expr {
11048 kind: ExprKind::FuncCall {
11049 name: "shuffle".to_string(),
11050 args: vec![list],
11051 },
11052 line,
11053 })
11054 }
11055 "chunked" => {
11056 let mut parts = Vec::new();
11057 if self.eat(&Token::LParen) {
11058 if !matches!(self.peek(), Token::RParen) {
11059 parts.push(self.parse_assign_expr()?);
11060 while self.eat(&Token::Comma) {
11061 if matches!(self.peek(), Token::RParen) {
11062 break;
11063 }
11064 parts.push(self.parse_assign_expr()?);
11065 }
11066 }
11067 self.expect(&Token::RParen)?;
11068 } else {
11069 parts.push(self.parse_assign_expr_stop_at_pipe()?);
11073 loop {
11074 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
11075 break;
11076 }
11077 if matches!(
11078 self.peek(),
11079 Token::Semicolon
11080 | Token::RBrace
11081 | Token::RParen
11082 | Token::Eof
11083 | Token::PipeForward
11084 ) {
11085 break;
11086 }
11087 if self.peek_is_postfix_stmt_modifier_keyword() {
11088 break;
11089 }
11090 parts.push(self.parse_assign_expr_stop_at_pipe()?);
11091 }
11092 }
11093 if parts.len() == 1 {
11094 let n = parts.pop().unwrap();
11095 return Ok(Expr {
11096 kind: ExprKind::FuncCall {
11097 name: "chunked".to_string(),
11098 args: vec![n],
11099 },
11100 line,
11101 });
11102 }
11103 if parts.is_empty() {
11104 return Ok(Expr {
11105 kind: ExprKind::FuncCall {
11106 name: "chunked".to_string(),
11107 args: parts,
11108 },
11109 line,
11110 });
11111 }
11112 if parts.len() == 2 {
11113 let n = parts.pop().unwrap();
11114 let list = parts.pop().unwrap();
11115 return Ok(Expr {
11116 kind: ExprKind::FuncCall {
11117 name: "chunked".to_string(),
11118 args: vec![list, n],
11119 },
11120 line,
11121 });
11122 }
11123 Err(self.syntax_err(
11124 "chunked: use LIST |> chunked(N) or chunked((1,2,3), 2)",
11125 line,
11126 ))
11127 }
11128 "windowed" => {
11129 let mut parts = Vec::new();
11130 if self.eat(&Token::LParen) {
11131 if !matches!(self.peek(), Token::RParen) {
11132 parts.push(self.parse_assign_expr()?);
11133 while self.eat(&Token::Comma) {
11134 if matches!(self.peek(), Token::RParen) {
11135 break;
11136 }
11137 parts.push(self.parse_assign_expr()?);
11138 }
11139 }
11140 self.expect(&Token::RParen)?;
11141 } else {
11142 parts.push(self.parse_assign_expr_stop_at_pipe()?);
11145 loop {
11146 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
11147 break;
11148 }
11149 if matches!(
11150 self.peek(),
11151 Token::Semicolon
11152 | Token::RBrace
11153 | Token::RParen
11154 | Token::Eof
11155 | Token::PipeForward
11156 ) {
11157 break;
11158 }
11159 if self.peek_is_postfix_stmt_modifier_keyword() {
11160 break;
11161 }
11162 parts.push(self.parse_assign_expr_stop_at_pipe()?);
11163 }
11164 }
11165 if parts.len() == 1 {
11166 let n = parts.pop().unwrap();
11167 return Ok(Expr {
11168 kind: ExprKind::FuncCall {
11169 name: "windowed".to_string(),
11170 args: vec![n],
11171 },
11172 line,
11173 });
11174 }
11175 if parts.is_empty() {
11176 return Ok(Expr {
11177 kind: ExprKind::FuncCall {
11178 name: "windowed".to_string(),
11179 args: parts,
11180 },
11181 line,
11182 });
11183 }
11184 if parts.len() == 2 {
11185 let n = parts.pop().unwrap();
11186 let list = parts.pop().unwrap();
11187 return Ok(Expr {
11188 kind: ExprKind::FuncCall {
11189 name: "windowed".to_string(),
11190 args: vec![list, n],
11191 },
11192 line,
11193 });
11194 }
11195 Err(self.syntax_err(
11196 "windowed: use LIST |> windowed(N) or windowed((1,2,3), 2)",
11197 line,
11198 ))
11199 }
11200 "any" | "all" | "none" => {
11201 if matches!(self.peek(), Token::LParen) {
11203 self.advance();
11204 let args = self.parse_arg_list()?;
11205 self.expect(&Token::RParen)?;
11206 return Ok(Expr {
11207 kind: ExprKind::FuncCall {
11208 name: name.clone(),
11209 args,
11210 },
11211 line,
11212 });
11213 }
11214 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
11216 if progress.is_some() {
11217 return Err(self.syntax_err(
11218 "`progress =>` is not supported for any/all/none (use pany for parallel + progress)",
11219 line,
11220 ));
11221 }
11222 let cr = Expr {
11223 kind: ExprKind::CodeRef {
11224 params: vec![],
11225 body: block,
11226 },
11227 line,
11228 };
11229 Ok(Expr {
11230 kind: ExprKind::FuncCall {
11231 name: name.clone(),
11232 args: vec![cr, list],
11233 },
11234 line,
11235 })
11236 }
11237 "first" | "detect" | "find" => {
11239 if matches!(self.peek(), Token::LParen) {
11241 self.advance();
11242 let args = self.parse_arg_list()?;
11243 self.expect(&Token::RParen)?;
11244 return Ok(Expr {
11245 kind: ExprKind::FuncCall {
11246 name: "first".to_string(),
11247 args,
11248 },
11249 line,
11250 });
11251 }
11252 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
11254 if progress.is_some() {
11255 return Err(self.syntax_err(
11256 "`progress =>` is not supported for first/detect/find (use pfirst for parallel + progress)",
11257 line,
11258 ));
11259 }
11260 let cr = Expr {
11261 kind: ExprKind::CodeRef {
11262 params: vec![],
11263 body: block,
11264 },
11265 line,
11266 };
11267 Ok(Expr {
11268 kind: ExprKind::FuncCall {
11269 name: "first".to_string(),
11270 args: vec![cr, list],
11271 },
11272 line,
11273 })
11274 }
11275 "take_while" | "drop_while" | "skip_while" | "reject" | "tap" | "peek"
11276 | "partition" | "min_by" | "max_by" | "zip_with" | "count_by" => {
11277 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
11278 if progress.is_some() {
11279 return Err(
11280 self.syntax_err(format!("`progress =>` is not supported for {name}"), line)
11281 );
11282 }
11283 let cr = Expr {
11284 kind: ExprKind::CodeRef {
11285 params: vec![],
11286 body: block,
11287 },
11288 line,
11289 };
11290 Ok(Expr {
11291 kind: ExprKind::FuncCall {
11292 name: name.to_string(),
11293 args: vec![cr, list],
11294 },
11295 line,
11296 })
11297 }
11298 "group_by" | "chunk_by" => {
11299 if matches!(self.peek(), Token::LBrace) {
11300 let (block, list) = self.parse_block_list()?;
11301 let cr = Expr {
11302 kind: ExprKind::CodeRef {
11303 params: vec![],
11304 body: block,
11305 },
11306 line,
11307 };
11308 Ok(Expr {
11309 kind: ExprKind::FuncCall {
11310 name: name.to_string(),
11311 args: vec![cr, list],
11312 },
11313 line,
11314 })
11315 } else {
11316 let key_expr = self.parse_assign_expr()?;
11317 self.expect(&Token::Comma)?;
11318 let list_parts = self.parse_list_until_terminator()?;
11319 let list_expr = if list_parts.len() == 1 {
11320 list_parts.into_iter().next().unwrap()
11321 } else {
11322 Expr {
11323 kind: ExprKind::List(list_parts),
11324 line,
11325 }
11326 };
11327 Ok(Expr {
11328 kind: ExprKind::FuncCall {
11329 name: name.to_string(),
11330 args: vec![key_expr, list_expr],
11331 },
11332 line,
11333 })
11334 }
11335 }
11336 "with_index" => {
11337 if self.pipe_supplies_slurped_list_operand() {
11338 return Ok(Expr {
11339 kind: ExprKind::FuncCall {
11340 name: "with_index".to_string(),
11341 args: vec![],
11342 },
11343 line,
11344 });
11345 }
11346 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
11347 if progress.is_some() {
11348 return Err(
11349 self.syntax_err("`progress =>` is not supported for with_index", line)
11350 );
11351 }
11352 Ok(Expr {
11353 kind: ExprKind::FuncCall {
11354 name: "with_index".to_string(),
11355 args: vec![list],
11356 },
11357 line,
11358 })
11359 }
11360 "pcache" => {
11361 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
11362 Ok(Expr {
11363 kind: ExprKind::PcacheExpr {
11364 block,
11365 list: Box::new(list),
11366 progress: progress.map(Box::new),
11367 },
11368 line,
11369 })
11370 }
11371 "pselect" => {
11372 let paren = self.eat(&Token::LParen);
11373 let (receivers, timeout) = self.parse_comma_expr_list_with_timeout_tail(paren)?;
11374 if paren {
11375 self.expect(&Token::RParen)?;
11376 }
11377 if receivers.is_empty() {
11378 return Err(self.syntax_err("pselect needs at least one receiver", line));
11379 }
11380 Ok(Expr {
11381 kind: ExprKind::PselectExpr {
11382 receivers,
11383 timeout: timeout.map(Box::new),
11384 },
11385 line,
11386 })
11387 }
11388 "open" => {
11389 let paren = matches!(self.peek(), Token::LParen);
11390 if paren {
11391 self.advance();
11392 }
11393 if matches!(self.peek(), Token::Ident(ref s) if s == "my") {
11394 self.advance();
11395 let name = self.parse_scalar_var_name()?;
11396 self.expect(&Token::Comma)?;
11397 let mode = self.parse_assign_expr()?;
11398 let file = if self.eat(&Token::Comma) {
11399 Some(self.parse_assign_expr()?)
11400 } else {
11401 None
11402 };
11403 if paren {
11404 self.expect(&Token::RParen)?;
11405 }
11406 Ok(Expr {
11407 kind: ExprKind::Open {
11408 handle: Box::new(Expr {
11409 kind: ExprKind::OpenMyHandle { name },
11410 line,
11411 }),
11412 mode: Box::new(mode),
11413 file: file.map(Box::new),
11414 },
11415 line,
11416 })
11417 } else {
11418 let args = if paren {
11419 self.parse_arg_list()?
11420 } else {
11421 self.parse_list_until_terminator()?
11422 };
11423 if paren {
11424 self.expect(&Token::RParen)?;
11425 }
11426 if args.len() < 2 {
11427 return Err(self.syntax_err("open requires at least 2 arguments", line));
11428 }
11429 Ok(Expr {
11430 kind: ExprKind::Open {
11431 handle: Box::new(args[0].clone()),
11432 mode: Box::new(args[1].clone()),
11433 file: args.get(2).cloned().map(Box::new),
11434 },
11435 line,
11436 })
11437 }
11438 }
11439 "close" => {
11440 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11441 return Ok(e);
11442 }
11443 let a = self.parse_one_arg_or_default()?;
11444 Ok(Expr {
11445 kind: ExprKind::Close(Box::new(a)),
11446 line,
11447 })
11448 }
11449 "opendir" => {
11450 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11451 return Ok(e);
11452 }
11453 let args = self.parse_builtin_args()?;
11454 if args.len() != 2 {
11455 return Err(self.syntax_err("opendir requires two arguments", line));
11456 }
11457 Ok(Expr {
11458 kind: ExprKind::Opendir {
11459 handle: Box::new(args[0].clone()),
11460 path: Box::new(args[1].clone()),
11461 },
11462 line,
11463 })
11464 }
11465 "readdir" => {
11466 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11467 return Ok(e);
11468 }
11469 let a = self.parse_one_arg()?;
11470 Ok(Expr {
11471 kind: ExprKind::Readdir(Box::new(a)),
11472 line,
11473 })
11474 }
11475 "closedir" => {
11476 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11477 return Ok(e);
11478 }
11479 let a = self.parse_one_arg()?;
11480 Ok(Expr {
11481 kind: ExprKind::Closedir(Box::new(a)),
11482 line,
11483 })
11484 }
11485 "rewinddir" => {
11486 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11487 return Ok(e);
11488 }
11489 let a = self.parse_one_arg()?;
11490 Ok(Expr {
11491 kind: ExprKind::Rewinddir(Box::new(a)),
11492 line,
11493 })
11494 }
11495 "telldir" => {
11496 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11497 return Ok(e);
11498 }
11499 let a = self.parse_one_arg()?;
11500 Ok(Expr {
11501 kind: ExprKind::Telldir(Box::new(a)),
11502 line,
11503 })
11504 }
11505 "seekdir" => {
11506 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11507 return Ok(e);
11508 }
11509 let args = self.parse_builtin_args()?;
11510 if args.len() != 2 {
11511 return Err(self.syntax_err("seekdir requires two arguments", line));
11512 }
11513 Ok(Expr {
11514 kind: ExprKind::Seekdir {
11515 handle: Box::new(args[0].clone()),
11516 position: Box::new(args[1].clone()),
11517 },
11518 line,
11519 })
11520 }
11521 "eof" => {
11522 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11523 return Ok(e);
11524 }
11525 if matches!(self.peek(), Token::LParen) {
11526 self.advance();
11527 if matches!(self.peek(), Token::RParen) {
11528 self.advance();
11529 Ok(Expr {
11530 kind: ExprKind::Eof(None),
11531 line,
11532 })
11533 } else {
11534 let a = self.parse_expression()?;
11535 self.expect(&Token::RParen)?;
11536 Ok(Expr {
11537 kind: ExprKind::Eof(Some(Box::new(a))),
11538 line,
11539 })
11540 }
11541 } else {
11542 Ok(Expr {
11543 kind: ExprKind::Eof(None),
11544 line,
11545 })
11546 }
11547 }
11548 "system" => {
11549 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11550 return Ok(e);
11551 }
11552 let args = self.parse_builtin_args()?;
11553 Ok(Expr {
11554 kind: ExprKind::System(args),
11555 line,
11556 })
11557 }
11558 "exec" => {
11559 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11560 return Ok(e);
11561 }
11562 let args = self.parse_builtin_args()?;
11563 Ok(Expr {
11564 kind: ExprKind::Exec(args),
11565 line,
11566 })
11567 }
11568 "eval" => {
11569 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11570 return Ok(e);
11571 }
11572 let a = if matches!(self.peek(), Token::LBrace) {
11573 let block = self.parse_block()?;
11574 Expr {
11575 kind: ExprKind::CodeRef {
11576 params: vec![],
11577 body: block,
11578 },
11579 line,
11580 }
11581 } else {
11582 self.parse_one_arg_or_default()?
11583 };
11584 Ok(Expr {
11585 kind: ExprKind::Eval(Box::new(a)),
11586 line,
11587 })
11588 }
11589 "do" => {
11590 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11591 return Ok(e);
11592 }
11593 let a = self.parse_one_arg()?;
11594 Ok(Expr {
11595 kind: ExprKind::Do(Box::new(a)),
11596 line,
11597 })
11598 }
11599 "require" => {
11600 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11601 return Ok(e);
11602 }
11603 let a = self.parse_one_arg()?;
11604 Ok(Expr {
11605 kind: ExprKind::Require(Box::new(a)),
11606 line,
11607 })
11608 }
11609 "exit" => {
11610 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11611 return Ok(e);
11612 }
11613 if matches!(
11614 self.peek(),
11615 Token::Semicolon | Token::RBrace | Token::Eof | Token::PipeForward
11616 ) {
11617 Ok(Expr {
11618 kind: ExprKind::Exit(None),
11619 line,
11620 })
11621 } else {
11622 let a = self.parse_one_arg()?;
11623 Ok(Expr {
11624 kind: ExprKind::Exit(Some(Box::new(a))),
11625 line,
11626 })
11627 }
11628 }
11629 "chdir" => {
11630 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11631 return Ok(e);
11632 }
11633 let a = self.parse_one_arg_or_default()?;
11634 Ok(Expr {
11635 kind: ExprKind::Chdir(Box::new(a)),
11636 line,
11637 })
11638 }
11639 "mkdir" => {
11640 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11641 return Ok(e);
11642 }
11643 let args = self.parse_builtin_args()?;
11644 Ok(Expr {
11645 kind: ExprKind::Mkdir {
11646 path: Box::new(args[0].clone()),
11647 mode: args.get(1).cloned().map(Box::new),
11648 },
11649 line,
11650 })
11651 }
11652 "unlink" | "rm" => {
11653 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11654 return Ok(e);
11655 }
11656 let args = self.parse_builtin_args()?;
11657 Ok(Expr {
11658 kind: ExprKind::Unlink(args),
11659 line,
11660 })
11661 }
11662 "rename" => {
11663 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11664 return Ok(e);
11665 }
11666 let args = self.parse_builtin_args()?;
11667 if args.len() != 2 {
11668 return Err(self.syntax_err("rename requires two arguments", line));
11669 }
11670 Ok(Expr {
11671 kind: ExprKind::Rename {
11672 old: Box::new(args[0].clone()),
11673 new: Box::new(args[1].clone()),
11674 },
11675 line,
11676 })
11677 }
11678 "chmod" => {
11679 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11680 return Ok(e);
11681 }
11682 let args = self.parse_builtin_args()?;
11683 if args.len() < 2 {
11684 return Err(self.syntax_err("chmod requires mode and at least one file", line));
11685 }
11686 Ok(Expr {
11687 kind: ExprKind::Chmod(args),
11688 line,
11689 })
11690 }
11691 "chown" => {
11692 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11693 return Ok(e);
11694 }
11695 let args = self.parse_builtin_args()?;
11696 if args.len() < 3 {
11697 return Err(
11698 self.syntax_err("chown requires uid, gid, and at least one file", line)
11699 );
11700 }
11701 Ok(Expr {
11702 kind: ExprKind::Chown(args),
11703 line,
11704 })
11705 }
11706 "stat" => {
11707 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11708 return Ok(e);
11709 }
11710 let args = self.parse_builtin_args()?;
11711 let arg = if args.len() == 1 {
11712 args[0].clone()
11713 } else if args.is_empty() {
11714 Expr {
11715 kind: ExprKind::ScalarVar("_".into()),
11716 line,
11717 }
11718 } else {
11719 return Err(self.syntax_err("stat requires zero or one argument", line));
11720 };
11721 Ok(Expr {
11722 kind: ExprKind::Stat(Box::new(arg)),
11723 line,
11724 })
11725 }
11726 "lstat" => {
11727 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11728 return Ok(e);
11729 }
11730 let args = self.parse_builtin_args()?;
11731 let arg = if args.len() == 1 {
11732 args[0].clone()
11733 } else if args.is_empty() {
11734 Expr {
11735 kind: ExprKind::ScalarVar("_".into()),
11736 line,
11737 }
11738 } else {
11739 return Err(self.syntax_err("lstat requires zero or one argument", line));
11740 };
11741 Ok(Expr {
11742 kind: ExprKind::Lstat(Box::new(arg)),
11743 line,
11744 })
11745 }
11746 "link" => {
11747 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11748 return Ok(e);
11749 }
11750 let args = self.parse_builtin_args()?;
11751 if args.len() != 2 {
11752 return Err(self.syntax_err("link requires two arguments", line));
11753 }
11754 Ok(Expr {
11755 kind: ExprKind::Link {
11756 old: Box::new(args[0].clone()),
11757 new: Box::new(args[1].clone()),
11758 },
11759 line,
11760 })
11761 }
11762 "symlink" => {
11763 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11764 return Ok(e);
11765 }
11766 let args = self.parse_builtin_args()?;
11767 if args.len() != 2 {
11768 return Err(self.syntax_err("symlink requires two arguments", line));
11769 }
11770 Ok(Expr {
11771 kind: ExprKind::Symlink {
11772 old: Box::new(args[0].clone()),
11773 new: Box::new(args[1].clone()),
11774 },
11775 line,
11776 })
11777 }
11778 "readlink" => {
11779 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11780 return Ok(e);
11781 }
11782 let args = self.parse_builtin_args()?;
11783 let arg = if args.len() == 1 {
11784 args[0].clone()
11785 } else if args.is_empty() {
11786 Expr {
11787 kind: ExprKind::ScalarVar("_".into()),
11788 line,
11789 }
11790 } else {
11791 return Err(self.syntax_err("readlink requires zero or one argument", line));
11792 };
11793 Ok(Expr {
11794 kind: ExprKind::Readlink(Box::new(arg)),
11795 line,
11796 })
11797 }
11798 "files" => {
11799 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11800 return Ok(e);
11801 }
11802 let args = self.parse_builtin_args()?;
11803 Ok(Expr {
11804 kind: ExprKind::Files(args),
11805 line,
11806 })
11807 }
11808 "filesf" | "f" => {
11809 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11810 return Ok(e);
11811 }
11812 let args = self.parse_builtin_args()?;
11813 Ok(Expr {
11814 kind: ExprKind::Filesf(args),
11815 line,
11816 })
11817 }
11818 "fr" => {
11819 let args = self.parse_builtin_args()?;
11820 Ok(Expr {
11821 kind: ExprKind::FilesfRecursive(args),
11822 line,
11823 })
11824 }
11825 "dirs" | "d" => {
11826 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11827 return Ok(e);
11828 }
11829 let args = self.parse_builtin_args()?;
11830 Ok(Expr {
11831 kind: ExprKind::Dirs(args),
11832 line,
11833 })
11834 }
11835 "dr" => {
11836 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11837 return Ok(e);
11838 }
11839 let args = self.parse_builtin_args()?;
11840 Ok(Expr {
11841 kind: ExprKind::DirsRecursive(args),
11842 line,
11843 })
11844 }
11845 "sym_links" => {
11846 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11847 return Ok(e);
11848 }
11849 let args = self.parse_builtin_args()?;
11850 Ok(Expr {
11851 kind: ExprKind::SymLinks(args),
11852 line,
11853 })
11854 }
11855 "sockets" => {
11856 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11857 return Ok(e);
11858 }
11859 let args = self.parse_builtin_args()?;
11860 Ok(Expr {
11861 kind: ExprKind::Sockets(args),
11862 line,
11863 })
11864 }
11865 "pipes" => {
11866 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11867 return Ok(e);
11868 }
11869 let args = self.parse_builtin_args()?;
11870 Ok(Expr {
11871 kind: ExprKind::Pipes(args),
11872 line,
11873 })
11874 }
11875 "block_devices" => {
11876 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11877 return Ok(e);
11878 }
11879 let args = self.parse_builtin_args()?;
11880 Ok(Expr {
11881 kind: ExprKind::BlockDevices(args),
11882 line,
11883 })
11884 }
11885 "char_devices" => {
11886 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11887 return Ok(e);
11888 }
11889 let args = self.parse_builtin_args()?;
11890 Ok(Expr {
11891 kind: ExprKind::CharDevices(args),
11892 line,
11893 })
11894 }
11895 "exe" | "executables" => {
11896 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11897 return Ok(e);
11898 }
11899 let args = self.parse_builtin_args()?;
11900 Ok(Expr {
11901 kind: ExprKind::Executables(args),
11902 line,
11903 })
11904 }
11905 "glob" => {
11906 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11907 return Ok(e);
11908 }
11909 let args = self.parse_builtin_args()?;
11910 Ok(Expr {
11911 kind: ExprKind::Glob(args),
11912 line,
11913 })
11914 }
11915 "glob_par" => {
11916 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11917 return Ok(e);
11918 }
11919 let (args, progress) = self.parse_glob_par_or_par_sed_args()?;
11920 Ok(Expr {
11921 kind: ExprKind::GlobPar { args, progress },
11922 line,
11923 })
11924 }
11925 "par_sed" => {
11926 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11927 return Ok(e);
11928 }
11929 let (args, progress) = self.parse_glob_par_or_par_sed_args()?;
11930 Ok(Expr {
11931 kind: ExprKind::ParSed { args, progress },
11932 line,
11933 })
11934 }
11935 "bless" => {
11936 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11937 return Ok(e);
11938 }
11939 let args = self.parse_builtin_args()?;
11940 Ok(Expr {
11941 kind: ExprKind::Bless {
11942 ref_expr: Box::new(args[0].clone()),
11943 class: args.get(1).cloned().map(Box::new),
11944 },
11945 line,
11946 })
11947 }
11948 "caller" => {
11949 if matches!(self.peek(), Token::LParen) {
11950 self.advance();
11951 if matches!(self.peek(), Token::RParen) {
11952 self.advance();
11953 Ok(Expr {
11954 kind: ExprKind::Caller(None),
11955 line,
11956 })
11957 } else {
11958 let a = self.parse_expression()?;
11959 self.expect(&Token::RParen)?;
11960 Ok(Expr {
11961 kind: ExprKind::Caller(Some(Box::new(a))),
11962 line,
11963 })
11964 }
11965 } else {
11966 Ok(Expr {
11967 kind: ExprKind::Caller(None),
11968 line,
11969 })
11970 }
11971 }
11972 "wantarray" => {
11973 if matches!(self.peek(), Token::LParen) {
11974 self.advance();
11975 self.expect(&Token::RParen)?;
11976 }
11977 Ok(Expr {
11978 kind: ExprKind::Wantarray,
11979 line,
11980 })
11981 }
11982 "sub" => {
11983 if crate::no_interop_mode() {
11985 return Err(self.syntax_err(
11986 "stryke uses `fn {}` instead of `sub {}` (--no-interop)",
11987 line,
11988 ));
11989 }
11990 let (params, _prototype) = self.parse_sub_sig_or_prototype_opt()?;
11992 let body = self.parse_block()?;
11993 Ok(Expr {
11994 kind: ExprKind::CodeRef { params, body },
11995 line,
11996 })
11997 }
11998 "fn" => {
11999 let (params, _prototype) = self.parse_sub_sig_or_prototype_opt()?;
12001 self.parse_sub_attributes()?;
12002 let body = self.parse_fn_eq_body_or_block(false)?;
12003 Ok(Expr {
12004 kind: ExprKind::CodeRef { params, body },
12005 line,
12006 })
12007 }
12008 _ => {
12009 if matches!(self.peek(), Token::FatArrow) && !Self::is_underscore_topic_slot(&name)
12014 {
12015 return Ok(Expr {
12016 kind: ExprKind::String(name),
12017 line,
12018 });
12019 }
12020 if Self::is_underscore_topic_slot(&name) {
12036 if matches!(self.peek(), Token::LBracket) && self.peek_line() == line {
12037 self.advance(); let index = self.parse_expression()?;
12039 self.expect(&Token::RBracket)?;
12040 return Ok(Expr {
12041 kind: ExprKind::ArrayElement {
12042 array: format!("__topicstr__{}", name),
12043 index: Box::new(index),
12044 },
12045 line,
12046 });
12047 }
12048 return Ok(Expr {
12049 kind: ExprKind::ScalarVar(name.clone()),
12050 line,
12051 });
12052 }
12053 if matches!(self.peek(), Token::LParen) {
12055 self.advance();
12056 let args = self.parse_arg_list()?;
12057 self.expect(&Token::RParen)?;
12058 Ok(Expr {
12059 kind: ExprKind::FuncCall { name, args },
12060 line,
12061 })
12062 } else if self.peek().is_term_start()
12063 && !(matches!(self.peek(), Token::Ident(ref kw) if kw == "sub")
12064 && matches!(self.peek_at(1), Token::Ident(_)))
12065 && !(self.suppress_parenless_call > 0 && matches!(self.peek(), Token::Ident(_)))
12066 && !(matches!(self.peek(), Token::LBrace)
12067 && self.peek_line() > self.prev_line())
12068 && !(matches!(self.peek(), Token::BitNot)
12069 && self.suppress_tilde_range == 0
12070 && matches!(
12071 self.peek_at(1),
12072 Token::Ident(_) | Token::Integer(_) | Token::Float(_)
12073 ))
12074 {
12075 let args = self.parse_list_until_terminator()?;
12089 Ok(Expr {
12090 kind: ExprKind::FuncCall { name, args },
12091 line,
12092 })
12093 } else {
12094 Ok(Expr {
12100 kind: ExprKind::Bareword(name),
12101 line,
12102 })
12103 }
12104 }
12105 }
12106 }
12107
12108 fn parse_print_like(
12109 &mut self,
12110 make: impl FnOnce(Option<String>, Vec<Expr>) -> ExprKind,
12111 ) -> PerlResult<Expr> {
12112 let line = self.peek_line();
12113 let handle = if let Token::Ident(ref h) = self.peek().clone() {
12115 if h.chars().all(|c| c.is_uppercase() || c == '_')
12116 && !matches!(self.peek(), Token::LParen)
12117 {
12118 let h = h.clone();
12119 let saved = self.pos;
12120 self.advance();
12121 let is_tilde_range_after = matches!(self.peek(), Token::BitNot)
12128 && self.suppress_tilde_range == 0
12129 && matches!(
12130 self.peek_at(1),
12131 Token::Ident(_) | Token::Integer(_) | Token::Float(_)
12132 );
12133 if !is_tilde_range_after
12134 && (self.peek().is_term_start()
12135 || matches!(
12136 self.peek(),
12137 Token::DoubleString(_)
12138 | Token::BacktickString(_)
12139 | Token::SingleString(_)
12140 ))
12141 {
12142 Some(h)
12143 } else {
12144 self.pos = saved;
12145 None
12146 }
12147 } else {
12148 None
12149 }
12150 } else if let Token::ScalarVar(ref v) = self.peek().clone() {
12151 let v = v.clone();
12161 if v == "_" {
12162 None
12163 } else {
12164 let saved = self.pos;
12165 self.advance();
12166 let next = self.peek().clone();
12167 let is_stmt_modifier = matches!(&next, Token::Ident(kw)
12168 if matches!(kw.as_str(), "if" | "unless" | "while" | "until" | "for" | "foreach"));
12169 if !is_stmt_modifier
12170 && !matches!(next, Token::LBracket | Token::LBrace)
12171 && (next.is_term_start()
12172 || matches!(
12173 next,
12174 Token::DoubleString(_)
12175 | Token::BacktickString(_)
12176 | Token::SingleString(_)
12177 ))
12178 {
12179 Some(format!("${v}"))
12181 } else {
12182 self.pos = saved;
12183 None
12184 }
12185 }
12186 } else {
12187 None
12188 };
12189 let args =
12194 if matches!(self.peek(), Token::LParen) && matches!(self.peek_at(1), Token::RParen) {
12195 let line_topic = self.peek_line();
12196 self.advance(); self.advance(); vec![Expr {
12199 kind: ExprKind::ScalarVar("_".into()),
12200 line: line_topic,
12201 }]
12202 } else {
12203 self.parse_list_until_terminator()?
12204 };
12205 Ok(Expr {
12206 kind: make(handle, args),
12207 line,
12208 })
12209 }
12210
12211 fn parse_block_list(&mut self) -> PerlResult<(Block, Expr)> {
12212 let block = self.parse_block()?;
12213 let block_end_line = self.prev_line();
12214 self.eat(&Token::Comma);
12215 if self.in_pipe_rhs()
12219 && (matches!(
12220 self.peek(),
12221 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof | Token::PipeForward
12222 ) || self.peek_line() > block_end_line)
12223 {
12224 let line = self.peek_line();
12225 return Ok((block, self.pipe_placeholder_list(line)));
12226 }
12227 let list = self.parse_expression()?;
12228 Ok((block, list))
12229 }
12230
12231 fn parse_comma_expr_list_with_timeout_tail(
12234 &mut self,
12235 paren: bool,
12236 ) -> PerlResult<(Vec<Expr>, Option<Expr>)> {
12237 let mut parts = vec![self.parse_assign_expr()?];
12238 loop {
12239 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
12240 break;
12241 }
12242 if paren && matches!(self.peek(), Token::RParen) {
12243 break;
12244 }
12245 if matches!(
12246 self.peek(),
12247 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof
12248 ) {
12249 break;
12250 }
12251 if self.peek_is_postfix_stmt_modifier_keyword() {
12252 break;
12253 }
12254 if let Token::Ident(ref kw) = self.peek().clone() {
12255 if kw == "timeout" && matches!(self.peek_at(1), Token::FatArrow) {
12256 self.advance();
12257 self.expect(&Token::FatArrow)?;
12258 let t = self.parse_assign_expr()?;
12259 return Ok((parts, Some(t)));
12260 }
12261 }
12262 parts.push(self.parse_assign_expr()?);
12263 }
12264 Ok((parts, None))
12265 }
12266
12267 fn parse_init_block_then_list_optional_progress(
12269 &mut self,
12270 ) -> PerlResult<(Expr, Block, Expr, Option<Expr>)> {
12271 let init = self.parse_assign_expr()?;
12272 self.expect(&Token::Comma)?;
12273 let block = self.parse_block_or_bareword_block()?;
12274 self.eat(&Token::Comma);
12275 let line = self.peek_line();
12276 if let Token::Ident(ref kw) = self.peek().clone() {
12277 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
12278 self.advance();
12279 self.expect(&Token::FatArrow)?;
12280 let prog = self.parse_assign_expr()?;
12281 return Ok((
12282 init,
12283 block,
12284 Expr {
12285 kind: ExprKind::List(vec![]),
12286 line,
12287 },
12288 Some(prog),
12289 ));
12290 }
12291 }
12292 if matches!(
12293 self.peek(),
12294 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof
12295 ) {
12296 return Ok((
12297 init,
12298 block,
12299 Expr {
12300 kind: ExprKind::List(vec![]),
12301 line,
12302 },
12303 None,
12304 ));
12305 }
12306 let mut parts = vec![self.parse_assign_expr()?];
12307 loop {
12308 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
12309 break;
12310 }
12311 if matches!(
12312 self.peek(),
12313 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof
12314 ) {
12315 break;
12316 }
12317 if self.peek_is_postfix_stmt_modifier_keyword() {
12318 break;
12319 }
12320 if let Token::Ident(ref kw) = self.peek().clone() {
12321 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
12322 self.advance();
12323 self.expect(&Token::FatArrow)?;
12324 let prog = self.parse_assign_expr()?;
12325 return Ok((init, block, merge_expr_list(parts), Some(prog)));
12326 }
12327 }
12328 parts.push(self.parse_assign_expr()?);
12329 }
12330 Ok((init, block, merge_expr_list(parts), None))
12331 }
12332
12333 fn parse_cluster_block_then_list_optional_progress(
12335 &mut self,
12336 ) -> PerlResult<(Expr, Block, Expr, Option<Expr>)> {
12337 self.suppress_scalar_hash_brace = self.suppress_scalar_hash_brace.saturating_add(1);
12340 let cluster = self.parse_assign_expr();
12341 self.suppress_scalar_hash_brace = self.suppress_scalar_hash_brace.saturating_sub(1);
12342 let cluster = cluster?;
12343 self.eat(&Token::Comma);
12345 let block = self.parse_block_or_bareword_block()?;
12346 let block_end_line = self.prev_line();
12347 self.eat(&Token::Comma);
12348 let line = self.peek_line();
12349 if let Token::Ident(ref kw) = self.peek().clone() {
12350 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
12351 self.advance();
12352 self.expect(&Token::FatArrow)?;
12353 let prog = self.parse_assign_expr_stop_at_pipe()?;
12354 return Ok((
12355 cluster,
12356 block,
12357 Expr {
12358 kind: ExprKind::List(vec![]),
12359 line,
12360 },
12361 Some(prog),
12362 ));
12363 }
12364 }
12365 let empty_list_ok = matches!(
12366 self.peek(),
12367 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof | Token::PipeForward
12368 ) || (self.in_pipe_rhs()
12369 && (matches!(self.peek(), Token::Comma) || self.peek_line() > block_end_line));
12370 if empty_list_ok {
12371 return Ok((
12372 cluster,
12373 block,
12374 Expr {
12375 kind: ExprKind::List(vec![]),
12376 line,
12377 },
12378 None,
12379 ));
12380 }
12381 let mut parts = vec![self.parse_assign_expr_stop_at_pipe()?];
12382 loop {
12383 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
12384 break;
12385 }
12386 if matches!(
12387 self.peek(),
12388 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof | Token::PipeForward
12389 ) {
12390 break;
12391 }
12392 if self.peek_is_postfix_stmt_modifier_keyword() {
12393 break;
12394 }
12395 if let Token::Ident(ref kw) = self.peek().clone() {
12396 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
12397 self.advance();
12398 self.expect(&Token::FatArrow)?;
12399 let prog = self.parse_assign_expr_stop_at_pipe()?;
12400 return Ok((cluster, block, merge_expr_list(parts), Some(prog)));
12401 }
12402 }
12403 parts.push(self.parse_assign_expr_stop_at_pipe()?);
12404 }
12405 Ok((cluster, block, merge_expr_list(parts), None))
12406 }
12407
12408 fn parse_block_then_list_optional_progress(
12417 &mut self,
12418 ) -> PerlResult<(Block, Expr, Option<Expr>)> {
12419 let block = self.parse_block_or_bareword_block()?;
12420 let block_end_line = self.prev_line();
12421 self.eat(&Token::Comma);
12422 let line = self.peek_line();
12423 if let Token::Ident(ref kw) = self.peek().clone() {
12424 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
12425 self.advance();
12426 self.expect(&Token::FatArrow)?;
12427 let prog = self.parse_assign_expr_stop_at_pipe()?;
12428 return Ok((
12429 block,
12430 Expr {
12431 kind: ExprKind::List(vec![]),
12432 line,
12433 },
12434 Some(prog),
12435 ));
12436 }
12437 }
12438 let empty_list_ok = matches!(
12446 self.peek(),
12447 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof | Token::PipeForward
12448 ) || (self.in_pipe_rhs()
12449 && (matches!(self.peek(), Token::Comma) || self.peek_line() > block_end_line));
12450 if empty_list_ok {
12451 return Ok((
12452 block,
12453 Expr {
12454 kind: ExprKind::List(vec![]),
12455 line,
12456 },
12457 None,
12458 ));
12459 }
12460 let mut parts = vec![self.parse_assign_expr_stop_at_pipe()?];
12461 loop {
12462 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
12463 break;
12464 }
12465 if matches!(
12466 self.peek(),
12467 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof | Token::PipeForward
12468 ) {
12469 break;
12470 }
12471 if self.peek_is_postfix_stmt_modifier_keyword() {
12472 break;
12473 }
12474 if let Token::Ident(ref kw) = self.peek().clone() {
12475 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
12476 self.advance();
12477 self.expect(&Token::FatArrow)?;
12478 let prog = self.parse_assign_expr_stop_at_pipe()?;
12479 return Ok((block, merge_expr_list(parts), Some(prog)));
12480 }
12481 }
12482 parts.push(self.parse_assign_expr_stop_at_pipe()?);
12483 }
12484 Ok((block, merge_expr_list(parts), None))
12485 }
12486
12487 fn parse_fan_count_and_block(&mut self, line: usize) -> PerlResult<(Option<Box<Expr>>, Block)> {
12489 if matches!(self.peek(), Token::LBrace) {
12491 let block = self.parse_block()?;
12492 return Ok((None, block));
12493 }
12494 let saved = self.pos;
12495 let first = self.parse_postfix()?;
12497 if matches!(self.peek(), Token::LBrace) {
12498 let block = self.parse_block()?;
12500 Ok((Some(Box::new(first)), block))
12501 } else if matches!(self.peek(), Token::Semicolon | Token::RBrace | Token::Eof)
12502 || (matches!(self.peek(), Token::Comma)
12503 && matches!(self.peek_at(1), Token::Ident(ref kw) if kw == "progress"))
12504 {
12505 let block = self.bareword_to_no_arg_block(first);
12507 Ok((None, block))
12508 } else if matches!(first.kind, ExprKind::Integer(_)) {
12509 self.eat(&Token::Comma);
12511 let body = self.parse_fan_blockless_body(line)?;
12512 Ok((Some(Box::new(first)), body))
12513 } else {
12514 self.pos = saved;
12517 let body = self.parse_fan_blockless_body(line)?;
12518 Ok((None, body))
12519 }
12520 }
12521
12522 fn parse_fan_blockless_body(&mut self, line: usize) -> PerlResult<Block> {
12524 if matches!(self.peek(), Token::LBrace) {
12525 return self.parse_block();
12526 }
12527 if let Token::Ident(ref name) = self.peek().clone() {
12529 if matches!(
12530 self.peek_at(1),
12531 Token::Comma | Token::Semicolon | Token::RBrace | Token::Eof | Token::PipeForward
12532 ) {
12533 let name = name.clone();
12534 self.advance();
12535 let body = Expr {
12536 kind: ExprKind::FuncCall { name, args: vec![] },
12537 line,
12538 };
12539 return Ok(vec![Statement::new(StmtKind::Expression(body), line)]);
12540 }
12541 }
12542 let expr = self.parse_assign_expr_stop_at_pipe()?;
12544 Ok(vec![Statement::new(StmtKind::Expression(expr), line)])
12545 }
12546
12547 fn bareword_to_no_arg_block(&self, expr: Expr) -> Block {
12550 let line = expr.line;
12551 let body = match &expr.kind {
12552 ExprKind::Bareword(name) => Expr {
12553 kind: ExprKind::FuncCall {
12554 name: name.clone(),
12555 args: vec![],
12556 },
12557 line,
12558 },
12559 _ => expr,
12560 };
12561 vec![Statement::new(StmtKind::Expression(body), line)]
12562 }
12563
12564 fn parse_block_or_bareword_block(&mut self) -> PerlResult<Block> {
12573 if matches!(self.peek(), Token::LBrace) {
12574 return self.parse_block();
12575 }
12576 let line = self.peek_line();
12577 if let Token::Ident(ref name) = self.peek().clone() {
12580 if matches!(
12581 self.peek_at(1),
12582 Token::Comma | Token::Semicolon | Token::RBrace | Token::Eof | Token::PipeForward
12583 ) {
12584 let name = name.clone();
12585 self.advance();
12586 let body = Expr {
12587 kind: ExprKind::FuncCall {
12588 name,
12589 args: vec![Expr {
12590 kind: ExprKind::ScalarVar("_".to_string()),
12591 line,
12592 }],
12593 },
12594 line,
12595 };
12596 return Ok(vec![Statement::new(StmtKind::Expression(body), line)]);
12597 }
12598 }
12599 let expr = self.parse_assign_expr_stop_at_pipe()?;
12601 Ok(vec![Statement::new(StmtKind::Expression(expr), line)])
12602 }
12603
12604 fn parse_block_or_bareword_block_no_args(&mut self) -> PerlResult<Block> {
12609 if matches!(self.peek(), Token::LBrace) {
12610 return self.parse_block();
12611 }
12612 let line = self.peek_line();
12613 if let Token::Ident(ref name) = self.peek().clone() {
12614 if matches!(
12615 self.peek_at(1),
12616 Token::Comma
12617 | Token::Semicolon
12618 | Token::RBrace
12619 | Token::Eof
12620 | Token::PipeForward
12621 | Token::Integer(_)
12622 ) {
12623 let name = name.clone();
12624 self.advance();
12625 let body = Expr {
12626 kind: ExprKind::FuncCall { name, args: vec![] },
12627 line,
12628 };
12629 return Ok(vec![Statement::new(StmtKind::Expression(body), line)]);
12630 }
12631 }
12632 let expr = self.parse_postfix()?;
12633 Ok(vec![Statement::new(StmtKind::Expression(expr), line)])
12634 }
12635
12636 fn is_known_bareword(name: &str) -> bool {
12644 Self::is_perl5_core(name) || Self::stryke_extension_name(name).is_some()
12645 }
12646
12647 fn is_try_builtin_name(name: &str) -> bool {
12653 crate::builtins::BUILTIN_ARMS
12654 .iter()
12655 .any(|arm| arm.contains(&name))
12656 }
12657
12658 fn is_perl5_core(name: &str) -> bool {
12663 matches!(
12664 name,
12665 "map" | "grep" | "sort" | "reverse" | "join" | "split"
12667 | "push" | "pop" | "shift" | "unshift" | "splice"
12668 | "splice_last" | "splice1" | "spl_last"
12669 | "pack" | "unpack"
12670 | "unpack_first" | "unpack1" | "up1"
12671 | "keys" | "values" | "each"
12673 | "chomp" | "chop" | "chr" | "ord" | "hex" | "oct"
12675 | "lc" | "uc" | "lcfirst" | "ucfirst"
12676 | "length" | "substr" | "index" | "rindex"
12677 | "sprintf" | "printf" | "print" | "say"
12678 | "pos" | "quotemeta" | "study"
12679 | "abs" | "int" | "sqrt" | "sin" | "cos" | "atan2"
12681 | "exp" | "log" | "rand" | "srand"
12682 | "time" | "localtime" | "gmtime"
12684 | "defined" | "undef" | "ref" | "scalar" | "wantarray"
12686 | "caller" | "delete" | "exists" | "bless" | "prototype"
12687 | "tie" | "untie" | "tied"
12688 | "open" | "close" | "read" | "readline" | "write" | "seek" | "tell"
12690 | "eof" | "binmode" | "getc" | "fileno" | "truncate"
12691 | "format" | "formline" | "select" | "vec"
12692 | "sysopen" | "sysread" | "sysseek" | "syswrite"
12693 | "stat" | "lstat" | "rename" | "unlink" | "utime"
12695 | "mkdir" | "rmdir" | "chdir" | "chmod" | "chown"
12696 | "glob" | "opendir" | "readdir" | "closedir"
12697 | "link" | "readlink" | "symlink"
12698 | "fcntl" | "flock" | "ioctl" | "pipe" | "dbmopen" | "dbmclose"
12700 | "msgctl" | "msgget" | "msgrcv" | "msgsnd"
12702 | "semctl" | "semget" | "semop"
12703 | "shmctl" | "shmget" | "shmread" | "shmwrite"
12704 | "system" | "exec" | "exit" | "die" | "warn" | "dump"
12706 | "fork" | "wait" | "waitpid" | "kill" | "alarm" | "sleep"
12707 | "chroot" | "times" | "umask" | "reset"
12708 | "getpgrp" | "setpgrp" | "getppid"
12709 | "getpriority" | "setpriority"
12710 | "socket" | "socketpair" | "connect" | "listen" | "accept" | "shutdown"
12712 | "send" | "recv" | "bind" | "setsockopt" | "getsockopt"
12713 | "getpeername" | "getsockname"
12714 | "getpwnam" | "getpwuid" | "getpwent" | "setpwent"
12716 | "getgrnam" | "getgrgid" | "getgrent" | "setgrent"
12717 | "getlogin"
12718 | "gethostbyname" | "gethostbyaddr" | "gethostent"
12719 | "getnetbyname" | "getnetent"
12720 | "getprotobyname" | "getprotoent"
12721 | "getservbyname" | "getservent"
12722 | "sethostent" | "setnetent" | "setprotoent" | "setservent"
12723 | "endpwent" | "endgrent"
12724 | "endhostent" | "endnetent" | "endprotoent" | "endservent"
12725 | "return" | "do" | "eval" | "require"
12727 | "my" | "our" | "local" | "use" | "no"
12728 | "sub" | "if" | "unless" | "while" | "until"
12729 | "for" | "foreach" | "last" | "next" | "redo" | "goto"
12730 | "not" | "and" | "or"
12731 | "qw" | "qq" | "q"
12733 | "BEGIN" | "END"
12735 )
12736 }
12737
12738 fn stryke_extension_name(name: &str) -> Option<&str> {
12741 match name {
12742 | "proceed" | "intercept_list" | "intercept_remove" | "intercept_clear"
12744 | "pmap" | "pmap_on" | "pflat_map" | "pflat_map_on" | "pmap_chunked"
12746 | "pgrep" | "pfor" | "psort" | "preduce" | "preduce_init" | "pmap_reduce"
12747 | "pcache" | "pchannel" | "pselect" | "puniq" | "pfirst" | "pany"
12748 | "fan" | "fan_cap" | "par_lines" | "par_walk" | "par_sed"
12749 | "par_find_files" | "par_line_count" | "pwatch" | "par_pipeline_stream"
12750 | "glob_par" | "ppool" | "barrier" | "pipeline" | "cluster"
12751 | "pmaps" | "pflat_maps" | "pgreps"
12752 | "fore" | "e" | "ep" | "flat_map" | "flat_maps" | "maps" | "filter" | "fi" | "find_all" | "reduce" | "fold"
12754 | "inject" | "collect" | "uniq" | "distinct" | "any" | "all" | "none"
12755 | "first" | "detect" | "find" | "compact" | "concat" | "chain" | "reject" | "flatten" | "set"
12756 | "min_by" | "max_by" | "sort_by" | "tally" | "find_index"
12757 | "each_with_index" | "count" | "cnt" |"len" | "group_by" | "chunk_by"
12758 | "zip" | "chunk" | "chunked" | "sliding_window" | "windowed"
12759 | "enumerate" | "with_index" | "shuffle" | "shuffled"| "heap"
12760 | "take_while" | "drop_while" | "skip_while" | "tap" | "peek" | "partition"
12761 | "zip_with" | "count_by" | "skip" | "first_or"
12762 | "input" | "lines" | "words" | "chars" | "digits" | "letters" | "letters_uc" | "letters_lc"
12764 | "punctuation" | "punct"
12765 | "sentences" | "sents"
12766 | "paragraphs" | "paras" | "sections" | "sects"
12767 | "numbers" | "nums" | "graphemes" | "grs" | "columns" | "cols"
12768 | "trim" | "avg" | "stddev"
12769 | "squared" | "sq" | "square" | "cubed" | "cb" | "cube" | "expt" | "pow" | "pw"
12770 | "normalize" | "snake_case" | "camel_case" | "kebab_case"
12771 | "frequencies" | "freq" | "interleave" | "ddump" | "stringify" | "str" | "top"
12772 | "to_json" | "to_csv" | "to_toml" | "to_yaml" | "to_xml"
12773 | "to_html" | "to_markdown" | "to_table" | "xopen"
12774 | "from_json" | "from_csv" | "from_toml" | "from_yaml" | "from_xml"
12775 | "clip" | "clipboard" | "paste" | "pbcopy" | "pbpaste" | "preview"
12776 | "sparkline" | "spark" | "bar_chart" | "bars" | "flame" | "flamechart"
12777 | "histo" | "gauge" | "spinner" | "spinner_start" | "spinner_stop"
12778 | "to_hash" | "to_set"
12779 | "to_file" | "read_lines" | "append_file" | "write_json" | "read_json"
12780 | "tempfile" | "tempdir" | "list_count" | "list_size" | "size"
12781 | "clamp" | "grep_v" | "select_keys" | "pluck" | "glob_match" | "which_all"
12782 | "dedup" | "nth" | "tail" | "take" | "drop" | "tee" | "range"
12783 | "inc" | "dec" | "elapsed"
12784 | "files" | "filesf" | "f" | "fr" | "dirs" | "d" | "dr" | "sym_links"
12786 | "sockets" | "pipes" | "block_devices" | "char_devices" | "exe" | "executables"
12787 | "basename" | "dirname" | "fileparse" | "realpath" | "canonpath"
12788 | "copy" | "move" | "spurt" | "spit" | "read_bytes" | "which"
12789 | "getcwd" | "touch" | "gethostname" | "uname"
12790 | "csv_read" | "csv_write" | "dataframe" | "sqlite"
12792 | "fetch" | "fetch_json" | "fetch_async" | "fetch_async_json"
12793 | "par_fetch" | "par_csv_read" | "par_pipeline"
12794 | "json_encode" | "json_decode" | "json_jq"
12795 | "http_request" | "serve" | "ssh"
12796 | "html_parse" | "css_select" | "xml_parse" | "xpath"
12797 | "smtp_send"
12798 | "net_interfaces" | "net_ipv4" | "net_ipv6" | "net_mac"
12799 | "net_public_ip" | "net_dns" | "net_reverse_dns"
12800 | "net_ping" | "net_port_open" | "net_ports_scan"
12801 | "net_latency" | "net_download" | "net_headers"
12802 | "net_dns_servers" | "net_gateway" | "net_whois" | "net_hostname"
12803 | "git_log" | "git_status" | "git_diff" | "git_branches"
12805 | "git_tags" | "git_blame" | "git_authors" | "git_files"
12806 | "git_show" | "git_root"
12807 | "audio_convert" | "audio_info" | "id3_read" | "id3_write"
12809 | "to_pdf" | "pdf_text" | "pdf_pages"
12811 | "toml_encode" | "toml_decode"
12813 | "yaml_encode" | "yaml_decode"
12814 | "xml_encode" | "xml_decode"
12815 | "md5" | "sha1" | "sha224" | "sha256" | "sha384" | "sha512"
12817 | "sha3_256" | "s3_256" | "sha3_512" | "s3_512"
12818 | "shake128" | "shake256"
12819 | "hmac_sha256" | "hmac_sha1" | "hmac_sha384" | "hmac_sha512" | "hmac_md5"
12820 | "uuid" | "crc32"
12821 | "blake2b" | "b2b" | "blake2s" | "b2s" | "blake3" | "b3"
12822 | "ripemd160" | "rmd160" | "md4"
12823 | "xxh32" | "xxhash32" | "xxh64" | "xxhash64" | "xxh3" | "xxhash3" | "xxh3_128" | "xxhash3_128"
12824 | "murmur3" | "murmur3_32" | "murmur3_128"
12825 | "siphash" | "siphash_keyed"
12826 | "hkdf_sha256" | "hkdf" | "hkdf_sha512"
12827 | "poly1305" | "poly1305_mac"
12828 | "base32_encode" | "b32e" | "base32_decode" | "b32d"
12829 | "base58_encode" | "b58e" | "base58_decode" | "b58d"
12830 | "totp" | "totp_generate" | "totp_verify" | "hotp" | "hotp_generate"
12831 | "aes_cbc_encrypt" | "aes_cbc_enc" | "aes_cbc_decrypt" | "aes_cbc_dec"
12832 | "blowfish_encrypt" | "bf_enc" | "blowfish_decrypt" | "bf_dec"
12833 | "des3_encrypt" | "3des_enc" | "tdes_enc" | "des3_decrypt" | "3des_dec" | "tdes_dec"
12834 | "twofish_encrypt" | "tf_enc" | "twofish_decrypt" | "tf_dec"
12835 | "camellia_encrypt" | "cam_enc" | "camellia_decrypt" | "cam_dec"
12836 | "cast5_encrypt" | "cast5_enc" | "cast5_decrypt" | "cast5_dec"
12837 | "salsa20" | "salsa20_encrypt" | "salsa20_decrypt"
12838 | "xsalsa20" | "xsalsa20_encrypt" | "xsalsa20_decrypt"
12839 | "secretbox" | "secretbox_seal" | "secretbox_open"
12840 | "nacl_box_keygen" | "box_keygen" | "nacl_box" | "nacl_box_seal" | "box_seal"
12841 | "nacl_box_open" | "box_open"
12842 | "qr_ascii" | "qr" | "qr_png" | "qr_svg"
12843 | "barcode_code128" | "code128" | "barcode_code39" | "code39"
12844 | "barcode_ean13" | "ean13" | "barcode_svg"
12845 | "argon2_hash" | "argon2" | "argon2_verify"
12846 | "bcrypt_hash" | "bcrypt" | "bcrypt_verify"
12847 | "scrypt_hash" | "scrypt" | "scrypt_verify"
12848 | "pbkdf2" | "pbkdf2_derive"
12849 | "random_bytes" | "randbytes" | "random_bytes_hex" | "randhex"
12850 | "aes_encrypt" | "aes_enc" | "aes_decrypt" | "aes_dec"
12851 | "chacha_encrypt" | "chacha_enc" | "chacha_decrypt" | "chacha_dec"
12852 | "rsa_keygen" | "rsa_encrypt" | "rsa_enc" | "rsa_decrypt" | "rsa_dec"
12853 | "rsa_encrypt_pkcs1" | "rsa_decrypt_pkcs1" | "rsa_sign" | "rsa_verify"
12854 | "ecdsa_p256_keygen" | "p256_keygen" | "ecdsa_p256_sign" | "p256_sign"
12855 | "ecdsa_p256_verify" | "p256_verify"
12856 | "ecdsa_p384_keygen" | "p384_keygen" | "ecdsa_p384_sign" | "p384_sign"
12857 | "ecdsa_p384_verify" | "p384_verify"
12858 | "ecdsa_secp256k1_keygen" | "secp256k1_keygen"
12859 | "ecdsa_secp256k1_sign" | "secp256k1_sign"
12860 | "ecdsa_secp256k1_verify" | "secp256k1_verify"
12861 | "ecdh_p256" | "p256_dh" | "ecdh_p384" | "p384_dh"
12862 | "ed25519_keygen" | "ed_keygen" | "ed25519_sign" | "ed_sign"
12863 | "ed25519_verify" | "ed_verify"
12864 | "x25519_keygen" | "x_keygen" | "x25519_dh" | "x_dh"
12865 | "base64_encode" | "base64_decode"
12866 | "hex_encode" | "hex_decode"
12867 | "url_encode" | "url_decode"
12868 | "gzip" | "gunzip" | "gz" | "ugz" | "zstd" | "zstd_decode" | "zst" | "uzst"
12869 | "brotli" | "br" | "brotli_decode" | "ubr"
12870 | "xz" | "lzma" | "xz_decode" | "unxz" | "unlzma"
12871 | "bzip2" | "bz2" | "bzip2_decode" | "bunzip2" | "ubz2"
12872 | "lz4" | "lz4_decode" | "unlz4"
12873 | "snappy" | "snp" | "snappy_decode" | "unsnappy"
12874 | "lzw" | "lzw_decode" | "unlzw"
12875 | "tar_create" | "tar" | "tar_extract" | "untar" | "tar_list"
12876 | "tar_gz_create" | "tgz" | "tar_gz_extract" | "untgz"
12877 | "zip_create" | "zip_archive" | "zip_extract" | "unzip_archive" | "zip_list"
12878 | "erf" | "erfc" | "gamma" | "tgamma" | "lgamma" | "ln_gamma"
12880 | "digamma" | "psi" | "beta_fn" | "lbeta" | "ln_beta"
12881 | "betainc" | "beta_reg" | "gammainc" | "gamma_li"
12882 | "gammaincc" | "gamma_ui" | "gammainc_reg" | "gamma_lr"
12883 | "gammaincc_reg" | "gamma_ur"
12884 | "datetime_utc" | "datetime_now_tz"
12886 | "datetime_format_tz" | "datetime_add_seconds"
12887 | "datetime_from_epoch"
12888 | "datetime_parse_rfc3339" | "datetime_parse_local"
12889 | "datetime_strftime"
12890 | "dateseq" | "dategrep" | "dateround" | "datesort"
12891 | "jwt_encode" | "jwt_decode" | "jwt_decode_unsafe"
12893 | "log_info" | "log_warn" | "log_error"
12895 | "log_debug" | "log_trace" | "log_json" | "log_level"
12896 | "async" | "spawn" | "trace" | "timer" | "bench"
12898 | "eval_timeout" | "retry" | "rate_limit" | "every"
12899 | "gen" | "watch"
12900 | "cache_clear" | "cache_exists" | "cache_stats" | "cacheview"
12902 | "assert_eq" | "assert_ne" | "assert_ok" | "assert_err"
12904 | "assert_true" | "assert_false"
12905 | "assert_gt" | "assert_lt" | "assert_ge" | "assert_le"
12906 | "assert_match" | "assert_contains" | "assert_near" | "assert_dies"
12907 | "test_run" | "run_tests" | "test_skip" | "skip_test" | "skip_assert"
12908 | "mounts" | "du" | "du_tree" | "process_list"
12910 | "thread_count" | "pool_info" | "par_bench"
12911 | "stress_cpu" | "scpu" | "stress_mem" | "smem"
12913 | "stress_io" | "sio" | "stress_test" | "st"
12914 | "heat" | "fire" | "fire_and_forget" | "pin"
12915 | "slurp" | "cat" | "c" | "capture" | "pager" | "pg" | "less"
12917 | "stdin"
12918 | "__stryke_rust_compile"
12920 | "vec_set_value"
12921 | "p" | "rev"
12923 | "even" | "odd" | "zero" | "nonzero"
12925 | "positive" | "pos_n" | "negative" | "neg_n"
12926 | "sign" | "negate" | "double" | "triple" | "half"
12927 | "identity" | "id"
12928 | "round" | "floor" | "ceil" | "ceiling" | "trunc" | "truncn"
12929 | "gcd" | "lcm" | "min2" | "max2"
12930 | "log2" | "log10" | "hypot"
12931 | "rad_to_deg" | "r2d" | "deg_to_rad" | "d2r"
12932 | "pow2" | "abs_diff"
12933 | "factorial" | "fact" | "fibonacci" | "fib"
12934 | "is_prime" | "is_square" | "is_power_of_two" | "is_pow2"
12935 | "cbrt" | "exp2" | "percent" | "pct" | "inverse"
12936 | "median" | "mode_val" | "variance"
12937 | "is_empty" | "is_blank" | "is_numeric"
12939 | "is_upper" | "is_lower" | "is_alpha" | "is_digit" | "is_alnum"
12940 | "is_space" | "is_whitespace"
12941 | "starts_with" | "sw" | "ends_with" | "ew" | "contains"
12942 | "capitalize" | "cap" | "swap_case" | "repeat"
12943 | "title_case" | "title" | "squish"
12944 | "pad_left" | "lpad" | "pad_right" | "rpad" | "center"
12945 | "truncate_at" | "shorten" | "reverse_str" | "rev_str"
12946 | "char_count" | "word_count" | "wc" | "line_count" | "lc_lines"
12947 | "is_array" | "is_arrayref" | "is_hash" | "is_hashref"
12949 | "is_code" | "is_coderef" | "is_ref"
12950 | "is_undef" | "is_defined" | "is_def"
12951 | "is_string" | "is_str" | "is_int" | "is_integer" | "is_float"
12952 | "invert" | "merge_hash"
12954 | "has_key" | "hk" | "has_any_key" | "has_all_keys"
12955 | "both" | "either" | "neither" | "xor_bool" | "bool_to_int" | "b2i"
12957 | "riffle" | "intersperse" | "every_nth"
12959 | "drop_n" | "take_n" | "rotate" | "swap_pairs"
12960 | "to_bin" | "bin_of" | "to_hex" | "hex_of" | "to_oct" | "oct_of"
12962 | "from_bin" | "from_hex" | "from_oct" | "to_base" | "from_base"
12963 | "bits_count" | "popcount" | "leading_zeros" | "lz"
12964 | "trailing_zeros" | "tz" | "bit_length" | "bitlen"
12965 | "bit_and" | "bit_or" | "bit_xor" | "bit_not"
12967 | "shift_left" | "shl" | "shift_right" | "shr"
12968 | "bit_set" | "bit_clear" | "bit_toggle" | "bit_test"
12969 | "c_to_f" | "f_to_c" | "c_to_k" | "k_to_c" | "f_to_k" | "k_to_f"
12971 | "miles_to_km" | "km_to_miles" | "miles_to_m" | "m_to_miles"
12973 | "feet_to_m" | "m_to_feet" | "inches_to_cm" | "cm_to_inches"
12974 | "yards_to_m" | "m_to_yards"
12975 | "kg_to_lbs" | "lbs_to_kg" | "g_to_oz" | "oz_to_g"
12977 | "stone_to_kg" | "kg_to_stone"
12978 | "bytes_to_kb" | "b_to_kb" | "kb_to_bytes" | "kb_to_b"
12980 | "bytes_to_mb" | "mb_to_bytes" | "bytes_to_gb" | "gb_to_bytes"
12981 | "kb_to_mb" | "mb_to_gb"
12982 | "bits_to_bytes" | "bytes_to_bits"
12983 | "seconds_to_minutes" | "s_to_m" | "minutes_to_seconds" | "m_to_s"
12985 | "seconds_to_hours" | "hours_to_seconds"
12986 | "seconds_to_days" | "days_to_seconds"
12987 | "minutes_to_hours" | "hours_to_minutes"
12988 | "hours_to_days" | "days_to_hours"
12989 | "is_leap_year" | "is_leap" | "days_in_month"
12991 | "month_name" | "month_short"
12992 | "weekday_name" | "weekday_short" | "quarter_of"
12993 | "now_ms" | "now_us" | "now_ns"
12995 | "unix_epoch" | "epoch" | "unix_epoch_ms" | "epoch_ms"
12996 | "rgb_to_hex" | "hex_to_rgb"
12998 | "ansi_red" | "ansi_green" | "ansi_yellow" | "ansi_blue"
12999 | "ansi_magenta" | "ansi_cyan" | "ansi_white" | "ansi_black"
13000 | "ansi_bold" | "ansi_dim" | "ansi_underline" | "ansi_reverse"
13001 | "strip_ansi"
13002 | "red" | "green" | "yellow" | "blue" | "magenta" | "purple" | "cyan"
13003 | "white" | "black" | "bold" | "dim" | "italic" | "underline"
13004 | "strikethrough" | "ansi_off" | "off" | "gray" | "grey"
13005 | "bright_red" | "bright_green" | "bright_yellow" | "bright_blue"
13006 | "bright_magenta" | "bright_cyan" | "bright_white"
13007 | "bg_red" | "bg_green" | "bg_yellow" | "bg_blue"
13008 | "bg_magenta" | "bg_cyan" | "bg_white" | "bg_black"
13009 | "red_bold" | "bold_red" | "green_bold" | "bold_green"
13010 | "yellow_bold" | "bold_yellow" | "blue_bold" | "bold_blue"
13011 | "magenta_bold" | "bold_magenta" | "cyan_bold" | "bold_cyan"
13012 | "white_bold" | "bold_white"
13013 | "blink" | "rapid_blink" | "hidden" | "overline"
13014 | "bg_bright_red" | "bg_bright_green" | "bg_bright_yellow" | "bg_bright_blue"
13015 | "bg_bright_magenta" | "bg_bright_cyan" | "bg_bright_white"
13016 | "rgb" | "bg_rgb" | "color256" | "c256" | "bg_color256" | "bg_c256"
13017 | "ipv4_to_int" | "int_to_ipv4"
13019 | "is_valid_ipv4" | "is_valid_ipv6" | "is_valid_email" | "is_valid_url"
13020 | "path_ext" | "path_stem" | "path_parent" | "path_join" | "path_split"
13022 | "strip_prefix" | "strip_suffix" | "ensure_prefix" | "ensure_suffix"
13023 | "const_fn" | "always_true" | "always_false"
13025 | "flip_args" | "first_arg" | "second_arg" | "last_arg"
13026 | "count_eq" | "count_ne" | "all_eq"
13028 | "all_distinct" | "all_unique" | "has_duplicates"
13029 | "sum_of" | "product_of" | "max_of" | "min_of" | "range_of"
13030 | "quote" | "single_quote" | "unquote"
13032 | "extract_between" | "ellipsis"
13033 | "coin_flip" | "dice_roll"
13035 | "random_int" | "random_float" | "random_bool"
13036 | "random_choice" | "random_between"
13037 | "random_string" | "random_alpha" | "random_digit"
13038 | "refresh_stashes"
13040 | "os_name" | "os_arch" | "num_cpus"
13042 | "pid" | "ppid" | "uid" | "gid"
13043 | "username" | "home_dir" | "temp_dir"
13044 | "mem_total" | "mem_free" | "mem_used"
13045 | "swap_total" | "swap_free" | "swap_used"
13046 | "disk_total" | "disk_free" | "disk_avail" | "disk_used"
13047 | "load_avg" | "sys_uptime" | "page_size"
13048 | "os_version" | "os_family" | "endianness" | "pointer_width"
13049 | "proc_mem" | "rss"
13050 | "transpose" | "unzip"
13052 | "run_length_encode" | "rle" | "run_length_decode" | "rld"
13053 | "sliding_pairs" | "consecutive_eq" | "flatten_deep"
13054 | "tan" | "asin" | "acos" | "atan"
13056 | "sinh" | "cosh" | "tanh" | "asinh" | "acosh" | "atanh"
13057 | "sqr" | "cube_fn"
13058 | "mod_op" | "ceil_div" | "floor_div"
13059 | "is_finite" | "is_infinite" | "is_inf" | "is_nan"
13060 | "degrees" | "radians"
13061 | "min_abs" | "max_abs"
13062 | "saturate" | "sat01" | "wrap_around"
13063 | "rot13" | "rot47" | "caesar_shift" | "reverse_words"
13065 | "count_vowels" | "count_consonants" | "is_vowel" | "is_consonant"
13066 | "first_word" | "last_word"
13067 | "left_str" | "head_str" | "right_str" | "tail_str" | "mid_str"
13068 | "lowercase" | "uppercase"
13069 | "pascal_case" | "pc_case"
13070 | "constant_case" | "upper_snake" | "dot_case" | "path_case"
13071 | "is_palindrome" | "hamming_distance"
13072 | "longest_common_prefix" | "lcp"
13073 | "ascii_ord" | "ascii_chr" | "count_char" | "indexes_of"
13074 | "replace_first" | "replace_all_str"
13075 | "contains_any" | "contains_all"
13076 | "starts_with_any" | "ends_with_any"
13077 | "is_pair" | "is_triple"
13079 | "is_sorted" | "is_asc" | "is_sorted_desc" | "is_desc"
13080 | "is_empty_arr" | "is_empty_hash"
13081 | "is_subset" | "is_superset" | "is_permutation"
13082 | "first_eq" | "last_eq"
13084 | "index_of" | "last_index_of" | "positions_of"
13085 | "batch" | "binary_search" | "bsearch" | "linear_search" | "lsearch"
13086 | "distinct_count" | "longest" | "shortest"
13087 | "array_union" | "list_union"
13088 | "array_intersection" | "list_intersection"
13089 | "array_difference" | "list_difference"
13090 | "symmetric_diff" | "group_of_n" | "chunk_n"
13091 | "repeat_list" | "cycle_n" | "random_sample" | "sample_n"
13092 | "pick_keys" | "pick" | "omit_keys" | "omit"
13094 | "map_keys_fn" | "map_values_fn"
13095 | "hash_size" | "hash_from_pairs" | "pairs_from_hash"
13096 | "hash_eq" | "keys_sorted" | "values_sorted" | "remove_keys"
13097 | "today" | "yesterday" | "tomorrow" | "is_weekend" | "is_weekday"
13099 | "json_pretty" | "json_minify" | "escape_json" | "json_escape"
13101 | "cmd_exists" | "env_get" | "env_has" | "env_keys"
13103 | "argc" | "script_name"
13104 | "has_stdin_tty" | "has_stdout_tty" | "has_stderr_tty"
13105 | "uuid_v4" | "nanoid" | "short_id" | "is_uuid" | "token"
13107 | "email_domain" | "email_local"
13109 | "url_host" | "url_path" | "url_query" | "url_scheme"
13110 | "file_size" | "fsize" | "file_mtime" | "mtime"
13112 | "file_atime" | "atime" | "file_ctime" | "ctime"
13113 | "is_symlink" | "is_readable" | "is_writable" | "is_executable"
13114 | "path_is_abs" | "path_is_rel"
13115 | "min_max" | "percentile" | "harmonic_mean" | "geometric_mean" | "zscore"
13117 | "sorted" | "sorted_desc" | "sorted_nums" | "sorted_by_length"
13118 | "reverse_list" | "list_reverse"
13119 | "without" | "without_nth" | "take_last" | "drop_last"
13120 | "pairwise" | "zipmap"
13121 | "format_bytes" | "human_bytes"
13122 | "format_duration" | "human_duration"
13123 | "format_number" | "group_number"
13124 | "format_percent" | "pad_number"
13125 | "spaceship" | "cmp_num" | "cmp_str"
13126 | "compare_versions" | "version_cmp"
13127 | "hash_insert" | "hash_update" | "hash_delete"
13128 | "matches_regex" | "re_match"
13129 | "count_regex_matches" | "regex_extract"
13130 | "regex_split_str" | "regex_replace_str"
13131 | "shuffle_chars" | "random_char" | "nth_word"
13132 | "head_lines" | "tail_lines" | "count_substring"
13133 | "is_valid_hex" | "hex_upper" | "hex_lower"
13134 | "ms_to_s" | "s_to_ms" | "ms_to_ns" | "ns_to_ms"
13135 | "us_to_ns" | "ns_to_us"
13136 | "liters_to_gallons" | "gallons_to_liters"
13137 | "liters_to_ml" | "ml_to_liters"
13138 | "cups_to_ml" | "ml_to_cups"
13139 | "newtons_to_lbf" | "lbf_to_newtons"
13140 | "joules_to_cal" | "cal_to_joules"
13141 | "watts_to_hp" | "hp_to_watts"
13142 | "pascals_to_psi" | "psi_to_pascals"
13143 | "bar_to_pascals" | "pascals_to_bar"
13144 | "match"
13146 | "fst" | "rest" | "rst" | "second" | "snd"
13148 | "last_clj" | "lastc" | "butlast" | "bl"
13149 | "ffirst" | "ffs" | "fnext" | "fne" | "nfirst" | "nfs" | "nnext" | "nne"
13150 | "cons" | "conj"
13151 | "peek_clj" | "pkc" | "pop_clj" | "popc"
13152 | "some" | "not_any" | "not_every"
13153 | "comp" | "compose" | "partial" | "constantly" | "complement" | "compl"
13154 | "fnil" | "juxt"
13155 | "memoize" | "memo" | "curry" | "once"
13156 | "deep_clone" | "dclone" | "deep_merge" | "dmerge" | "deep_equal" | "deq"
13157 | "iterate" | "iter" | "repeatedly" | "rptd" | "cycle" | "cyc"
13158 | "mapcat" | "mcat" | "keep" | "kp" | "remove_clj" | "remc"
13159 | "reductions" | "rdcs"
13160 | "partition_by" | "pby" | "partition_all" | "pall"
13161 | "split_at" | "spat" | "split_with" | "spw"
13162 | "assoc" | "dissoc" | "get_in" | "gin" | "assoc_in" | "ain" | "update_in" | "uin"
13163 | "into" | "empty_clj" | "empc" | "seq" | "vec_clj" | "vecc"
13164 | "apply" | "appl"
13165 | "divmod" | "dm" | "accumulate" | "accum" | "starmap" | "smap"
13167 | "zip_longest" | "zipl" | "zip_fill" | "zipf" | "combinations" | "comb" | "permutations" | "perm"
13168 | "cartesian_product" | "cprod" | "compress" | "cmpr" | "filterfalse" | "falf"
13169 | "islice" | "isl" | "chain_from" | "chfr" | "pairwise_iter" | "pwi"
13170 | "tee_iter" | "teei" | "groupby_iter" | "gbi"
13171 | "each_slice" | "eslice" | "each_cons" | "econs"
13172 | "one" | "none_match" | "nonem"
13173 | "find_index_fn" | "fidx" | "rindex_fn" | "ridx"
13174 | "minmax" | "mmx" | "minmax_by" | "mmxb"
13175 | "dig" | "values_at" | "vat" | "fetch_val" | "fv" | "slice_arr" | "sla"
13176 | "transform_keys" | "tkeys" | "transform_values" | "tvals"
13177 | "sum_by" | "sumb" | "uniq_by" | "uqb"
13178 | "flat_map_fn" | "fmf" | "then_fn" | "thfn" | "times_fn" | "timf"
13179 | "step" | "upto" | "downto"
13180 | "find_last" | "fndl" | "find_last_index" | "fndli"
13182 | "at_index" | "ati" | "replace_at" | "repa"
13183 | "to_sorted" | "tsrt" | "to_reversed" | "trev" | "to_spliced" | "tspl"
13184 | "flat_depth" | "fltd" | "fill_arr" | "filla" | "includes_val" | "incv"
13185 | "object_keys" | "okeys" | "object_values" | "ovals"
13186 | "object_entries" | "oents" | "object_from_entries" | "ofents"
13187 | "span_fn" | "spanf" | "break_fn" | "brkf" | "group_runs" | "gruns"
13189 | "nub" | "sort_on" | "srton"
13190 | "intersperse_val" | "isp" | "intercalate" | "ical"
13191 | "replicate_val" | "repv" | "elem_of" | "elof" | "not_elem" | "ntelm"
13192 | "lookup_assoc" | "lkpa" | "scanl" | "scanr" | "unfoldr" | "unfr"
13193 | "find_map" | "fndm" | "filter_map" | "fltm" | "fold_right" | "fldr"
13195 | "partition_either" | "peith" | "try_fold" | "tfld"
13196 | "map_while" | "mapw" | "inspect" | "insp"
13197 | "tally_by" | "talb" | "sole" | "chunk_while" | "chkw" | "count_while" | "cntw"
13199 | "insert_at" | "insa" | "delete_at" | "dela" | "update_at" | "upda"
13201 | "split_on" | "spon" | "words_from" | "wfrm" | "unwords" | "unwds"
13202 | "lines_from" | "lfrm" | "unlines" | "unlns"
13203 | "window_n" | "winn" | "adjacent_pairs" | "adjp"
13204 | "zip_all" | "zall" | "unzip_pairs" | "uzp"
13205 | "interpose" | "ipos" | "partition_n" | "partn"
13206 | "map_indexed" | "mapi" | "reduce_indexed" | "redi" | "filter_indexed" | "flti"
13207 | "group_by_fn" | "gbf" | "index_by" | "idxb" | "associate" | "assoc_fn"
13208 | "combinations_rep" | "combrep" | "inits" | "tails" | "subsequences" | "subseqs"
13210 | "nub_by" | "nubb" | "slice_when" | "slcw" | "slice_before" | "slcb" | "slice_after" | "slca"
13211 | "each_with_object" | "ewo" | "reduce_right" | "redr"
13212 | "is_sorted_by" | "issrtb" | "intersperse_with" | "ispw"
13213 | "running_reduce" | "runred" | "windowed_circular" | "wincirc"
13214 | "distinct_by" | "distb" | "average" | "mean" | "copy_within" | "cpyw"
13215 | "and_list" | "andl" | "or_list" | "orl" | "concat_map" | "cmap"
13216 | "elem_index" | "elidx" | "elem_indices" | "elidxs" | "find_indices" | "fndidxs"
13217 | "delete_first" | "delfst" | "delete_by" | "delby" | "insert_sorted" | "inssrt"
13218 | "union_list" | "unionl" | "intersect_list" | "intl"
13219 | "maximum_by" | "maxby" | "minimum_by" | "minby" | "batched" | "btch"
13220 | "match_all" | "mall" | "capture_groups" | "capg" | "is_match" | "ism"
13222 | "split_regex" | "splre" | "replace_regex" | "replre"
13223 | "is_ascii" | "isasc" | "to_ascii" | "toasc"
13224 | "char_at" | "chat" | "code_point_at" | "cpat" | "from_code_point" | "fcp"
13225 | "normalize_spaces" | "nrmsp" | "remove_whitespace" | "rmws"
13226 | "pluralize" | "plur" | "ordinalize" | "ordn"
13227 | "parse_int" | "pint" | "parse_float" | "pflt" | "parse_bool" | "pbool"
13228 | "levenshtein" | "lev" | "soundex" | "sdx" | "similarity" | "sim"
13229 | "common_prefix" | "cpfx" | "common_suffix" | "csfx"
13230 | "wrap_text" | "wrpt" | "dedent" | "ddt" | "indent" | "idt"
13231 | "lerp" | "inv_lerp" | "ilerp" | "smoothstep" | "smst" | "remap"
13233 | "dot_product" | "dotp" | "cross_product" | "crossp"
13234 | "matrix_mul" | "matmul" | "mm"
13235 | "magnitude" | "mag" | "normalize_vec" | "nrmv"
13236 | "distance" | "dist" | "manhattan_distance" | "mdist"
13237 | "covariance" | "cov" | "correlation" | "corr"
13238 | "iqr" | "quantile" | "qntl" | "clamp_int" | "clpi"
13239 | "in_range" | "inrng" | "wrap_range" | "wrprng"
13240 | "sum_squares" | "sumsq" | "rms" | "cumsum" | "csum" | "cumprod" | "cprod_acc" | "diff"
13241 | "add_days" | "addd" | "add_hours" | "addh" | "add_minutes" | "addm"
13243 | "diff_days" | "diffd" | "diff_hours" | "diffh"
13244 | "start_of_day" | "sod" | "end_of_day" | "eod"
13245 | "start_of_hour" | "soh" | "start_of_minute" | "som"
13246 | "urle" | "urld"
13248 | "html_encode" | "htmle" | "html_decode" | "htmld"
13249 | "adler32" | "adl32" | "fnv1a" | "djb2"
13250 | "is_credit_card" | "iscc" | "is_isbn10" | "isbn10" | "is_isbn13" | "isbn13"
13252 | "is_iban" | "isiban" | "is_hex_str" | "ishex" | "is_binary_str" | "isbin"
13253 | "is_octal_str" | "isoct" | "is_json" | "isjson" | "is_base64" | "isb64"
13254 | "is_semver" | "issv" | "is_slug" | "isslug" | "slugify" | "slug"
13255 | "mode_stat" | "mstat" | "sampn" | "weighted_sample" | "wsamp"
13257 | "shuffle_arr" | "shuf" | "argmax" | "amax" | "argmin" | "amin"
13258 | "argsort" | "asrt" | "rank" | "rnk" | "dense_rank" | "drnk"
13259 | "partition_point" | "ppt" | "lower_bound" | "lbound"
13260 | "upper_bound" | "ubound" | "equal_range" | "eqrng"
13261 | "matrix_add" | "madd" | "matrix_sub" | "msub" | "matrix_mult" | "mmult"
13263 | "matrix_scalar" | "mscal" | "matrix_identity" | "mident"
13264 | "matrix_zeros" | "mzeros" | "matrix_ones" | "mones"
13265 | "matrix_diag" | "mdiag" | "matrix_trace" | "mtrace"
13266 | "matrix_row" | "mrow" | "matrix_col" | "mcol"
13267 | "matrix_shape" | "mshape" | "matrix_det" | "mdet"
13268 | "matrix_scale" | "mat_scale" | "diagonal" | "diag"
13269 | "topological_sort" | "toposort" | "bfs_traverse" | "bfs"
13271 | "dfs_traverse" | "dfs" | "shortest_path_bfs" | "spbfs"
13272 | "connected_components_graph" | "ccgraph"
13273 | "has_cycle_graph" | "hascyc" | "is_bipartite_graph" | "isbip"
13274 | "is_ipv4_addr" | "isip4" | "is_ipv6_addr" | "isip6"
13276 | "is_mac_addr" | "ismac" | "is_port_num" | "isport"
13277 | "is_hostname_valid" | "ishost"
13278 | "is_iso_date" | "isisodt" | "is_iso_time" | "isisotm"
13279 | "is_iso_datetime" | "isisodtm"
13280 | "is_phone_num" | "isphone" | "is_us_zip" | "iszip"
13281 | "word_wrap_text" | "wwrap" | "center_text" | "ctxt"
13283 | "ljust_text" | "ljt" | "rjust_text" | "rjt" | "zfill_num" | "zfill"
13284 | "remove_all_str" | "rmall" | "replace_n_times" | "repln"
13285 | "find_all_indices" | "fndalli"
13286 | "text_between" | "txbtwn" | "text_before" | "txbef" | "text_after" | "txaft"
13287 | "text_before_last" | "txbefl" | "text_after_last" | "txaftl"
13288 | "is_even_num" | "iseven" | "is_odd_num" | "isodd"
13290 | "is_positive_num" | "ispos" | "is_negative_num" | "isneg"
13291 | "is_zero_num" | "iszero" | "is_whole_num" | "iswhole"
13292 | "log_with_base" | "logb" | "nth_root_of" | "nroot"
13293 | "frac_part" | "fracp" | "reciprocal_of" | "recip"
13294 | "copy_sign" | "cpsgn" | "fused_mul_add" | "fmadd"
13295 | "floor_mod" | "fmod" | "floor_div_op" | "fdivop"
13296 | "signum_of" | "sgnum" | "midpoint_of" | "midpt"
13297 | "longest_run" | "lrun" | "longest_increasing" | "linc"
13299 | "longest_decreasing" | "ldec" | "max_sum_subarray" | "maxsub"
13300 | "majority_element" | "majority" | "kth_largest" | "kthl"
13301 | "kth_smallest" | "kths" | "count_inversions" | "cinv"
13302 | "is_monotonic" | "ismono" | "equilibrium_index" | "eqidx"
13303 | "jaccard_index" | "jaccard" | "dice_coefficient" | "dicecoef"
13305 | "overlap_coefficient" | "overlapcoef"
13306 | "power_set" | "powerset" | "cartesian_power" | "cartpow"
13307 | "is_isogram" | "isiso" | "is_heterogram" | "ishet"
13309 | "hamdist" | "jaro_similarity" | "jarosim"
13310 | "longest_common_substring" | "lcsub"
13311 | "longest_common_subsequence" | "lcseq"
13312 | "count_words" | "wcount" | "count_lines" | "lcount"
13313 | "count_chars" | "ccount" | "count_bytes" | "bcount"
13314 | "binomial" | "binom" | "catalan" | "catn" | "pascal_row" | "pascrow"
13316 | "is_coprime" | "iscopr" | "euler_totient" | "etot"
13317 | "mobius" | "mob" | "is_squarefree" | "issqfr"
13318 | "digital_root" | "digroot" | "is_narcissistic" | "isnarc"
13319 | "is_harshad" | "isharsh" | "is_kaprekar" | "iskap"
13320 | "day_of_year" | "doy" | "week_of_year" | "woy"
13322 | "days_in_month_fn" | "daysinmo" | "is_valid_date" | "isvdate"
13323 | "age_in_years" | "ageyrs"
13324 | "when_true" | "when_false" | "if_else" | "clamp_fn"
13327 | "attempt" | "try_fn" | "safe_div" | "safe_mod" | "safe_sqrt" | "safe_log"
13328 | "juxt2" | "juxt3" | "tap_val" | "debug_val" | "converge"
13329 | "iterate_n" | "unfold" | "arity_of" | "is_callable"
13330 | "coalesce" | "default_to" | "fallback"
13331 | "apply_list" | "zip_apply" | "scan"
13332 | "keep_if" | "reject_if" | "group_consecutive"
13333 | "after_n" | "before_n" | "clamp_list" | "normalize_list" | "softmax"
13334
13335 | "matrix_multiply" | "mat_mul"
13339 | "identity_matrix" | "eye" | "zeros_matrix" | "zeros" | "ones_matrix" | "ones"
13340
13341
13342
13343 | "vec_normalize" | "unit_vec" | "vec_add" | "vec_sub" | "vec_scale"
13344 | "linspace" | "arange"
13345 | "re_test" | "re_find_all" | "re_groups" | "re_escape"
13347 | "re_split_limit" | "glob_to_regex" | "is_regex_valid"
13348 | "cwd" | "pwd_str" | "cpu_count" | "is_root" | "uptime_secs"
13350 | "env_pairs" | "env_set" | "env_remove" | "hostname_str" | "is_tty" | "signal_name"
13351 | "stack_new" | "queue_new" | "lru_new"
13353 | "counter" | "counter_most_common" | "defaultdict" | "ordered_set"
13354 | "bitset_new" | "bitset_set" | "bitset_test" | "bitset_clear"
13355 | "abs_ceil" | "abs_each" | "abs_floor" | "ceil_each" | "dec_each"
13357 | "double_each" | "floor_each" | "half_each" | "inc_each" | "length_each"
13358 | "negate_each" | "not_each" | "offset_each" | "reverse_each" | "round_each"
13359 | "scale_each" | "sqrt_each" | "square_each" | "to_float_each" | "to_int_each"
13360 | "trim_each" | "type_each" | "upcase_each" | "downcase_each" | "bool_each"
13361 | "avogadro" | "boltzmann" | "golden_ratio" | "gravity" | "ln10" | "ln2"
13363 | "planck" | "speed_of_light" | "sqrt2"
13364 | "bmi_calc" | "compound_interest" | "dew_point" | "discount_amount"
13366 | "force_mass_acc" | "freq_wavelength" | "future_value" | "haversine"
13367 | "heat_index" | "kinetic_energy" | "margin_price" | "markup_price"
13368 | "mortgage_payment" | "ohms_law_i" | "ohms_law_r" | "ohms_law_v"
13369 | "potential_energy" | "present_value" | "simple_interest" | "speed_distance_time"
13370 | "tax_amount" | "tip_amount" | "wavelength_freq" | "wind_chill"
13371 | "angle_between_deg" | "approx_eq" | "chebyshev_distance" | "copysign"
13373 | "cosine_similarity" | "cube_root" | "entropy" | "float_bits" | "fma"
13374 | "int_bits" | "jaccard_similarity" | "log_base" | "mae" | "mse" | "nth_root"
13375 | "r_squared" | "reciprocal" | "relu" | "rmse" | "rotate_point" | "round_to"
13376 | "sigmoid" | "signum" | "square_root"
13377 | "cubes_seq" | "fibonacci_seq" | "powers_of_seq" | "primes_seq"
13379 | "squares_seq" | "triangular_seq"
13380 | "alternate_case" | "angle_bracket" | "bracket" | "byte_length"
13382 | "bytes_to_hex_str" | "camel_words" | "char_length" | "chars_to_string"
13383 | "chomp_str" | "chop_str" | "filter_chars" | "from_csv_line" | "hex_to_bytes"
13384 | "insert_str" | "intersperse_char" | "ljust" | "map_chars" | "mirror_string"
13385 | "normalize_whitespace" | "only_alnum" | "only_alpha" | "only_ascii"
13386 | "only_digits" | "parenthesize" | "remove_str" | "repeat_string" | "rjust"
13387 | "sentence_case" | "string_count" | "string_sort" | "string_to_chars"
13388 | "string_unique_chars" | "substring" | "to_csv_line" | "trim_left" | "trim_right"
13389 | "xor_strings"
13390 | "adjacent_difference" | "append_elem" | "consecutive_pairs" | "contains_elem"
13392 | "count_elem" | "drop_every" | "duplicate_count" | "elem_at" | "find_first"
13393 | "first_elem" | "flatten_once" | "fold_left" | "from_digits" | "from_pairs"
13394 | "group_by_size" | "hash_filter_keys" | "hash_from_list" | "hash_map_values"
13395 | "hash_merge_deep" | "hash_to_list" | "hash_zip" | "head_n" | "histogram_bins"
13396 | "index_of_elem" | "init_list" | "interleave_lists" | "last_elem" | "least_common"
13397 | "list_compact" | "list_eq" | "list_flatten_deep" | "max_list" | "mean_list"
13398 | "min_list" | "mode_list" | "most_common" | "partition_two" | "prefix_sums"
13399 | "prepend" | "product_list" | "remove_at" | "remove_elem" | "remove_first_elem"
13400 | "repeat_elem" | "running_max" | "running_min" | "sample_one" | "scan_left"
13401 | "second_elem" | "span" | "suffix_sums" | "sum_list" | "tail_n" | "take_every"
13402 | "third_elem" | "to_array" | "to_pairs" | "trimmed_mean" | "unique_count_of"
13403 | "wrap_index" | "digits_of"
13404 | "all_match" | "any_match" | "is_between" | "is_blank_or_nil" | "is_divisible_by"
13406 | "is_email" | "is_even" | "is_falsy" | "is_fibonacci" | "is_hex_color"
13407 | "is_in_range" | "is_ipv4" | "is_multiple_of" | "is_negative" | "is_nil"
13408 | "is_nonzero" | "is_odd" | "is_perfect_square" | "is_positive" | "is_power_of"
13409 | "is_prefix" | "is_present" | "is_strictly_decreasing" | "is_strictly_increasing"
13410 | "is_suffix" | "is_triangular" | "is_truthy" | "is_url" | "is_whole" | "is_zero"
13411 | "count_digits" | "count_letters" | "count_lower" | "count_match"
13413 | "count_punctuation" | "count_spaces" | "count_upper" | "defined_count"
13414 | "empty_count" | "falsy_count" | "nonempty_count" | "numeric_count"
13415 | "truthy_count" | "undef_count"
13416 | "assert_type" | "between" | "clamp_each" | "die_if" | "die_unless"
13418 | "join_colons" | "join_commas" | "join_dashes" | "join_dots" | "join_lines"
13419 | "join_pipes" | "join_slashes" | "join_spaces" | "join_tabs" | "measure"
13420 | "max_float" | "min_float" | "noop_val" | "nop" | "pass" | "pred" | "succ"
13421 | "tap_debug" | "to_bool" | "to_float" | "to_int" | "to_string" | "void"
13422 | "range_exclusive" | "range_inclusive"
13423 | "aliquot_sum" | "autocorrelation" | "bell_number" | "cagr" | "coeff_of_variation"
13425 | "collatz_length" | "collatz_sequence" | "convolution" | "cross_entropy"
13426 | "depreciation_double" | "depreciation_linear" | "discount" | "divisors"
13427 | "epsilon" | "euclidean_distance" | "euler_number" | "exponential_moving_average"
13428 | "f64_max" | "f64_min" | "fft_magnitude" | "goldbach" | "i64_max" | "i64_min"
13429 | "kurtosis" | "linear_regression" | "look_and_say" | "lucas" | "luhn_check"
13430 | "mean_absolute_error" | "mean_squared_error" | "median_absolute_deviation"
13431 | "minkowski_distance" | "moving_average" | "multinomial" | "neg_inf" | "npv"
13432 | "num_divisors" | "partition_number" | "pascals_triangle" | "skewness"
13433 | "standard_error" | "subfactorial" | "sum_divisors" | "totient_sum"
13434 | "tribonacci" | "weighted_mean" | "winsorize"
13435 | "chi_square_stat" | "describe" | "five_number_summary"
13437 | "gini" | "gini_coefficient" | "lorenz_curve" | "outliers_iqr"
13438 | "percentile_rank" | "quartiles" | "sample_stddev" | "sample_variance"
13439 | "spearman_correlation" | "t_test_one_sample" | "t_test_two_sample"
13440 | "z_score" | "z_scores"
13441 | "abundant_numbers" | "deficient_numbers" | "is_abundant" | "is_deficient"
13443 | "is_pentagonal" | "is_perfect" | "is_smith" | "next_prime" | "nth_prime"
13444 | "pentagonal_number" | "perfect_numbers" | "prev_prime" | "prime_factors"
13445 | "prime_pi" | "primes_up_to" | "triangular_number" | "twin_primes"
13446 | "area_circle" | "area_ellipse" | "area_rectangle" | "area_trapezoid" | "area_triangle"
13448 | "bearing" | "circumference" | "cone_volume" | "cylinder_volume" | "heron_area"
13449 | "midpoint" | "perimeter_rectangle" | "perimeter_triangle" | "point_distance"
13450 | "polygon_area" | "slope" | "sphere_surface" | "sphere_volume" | "triangle_hypotenuse"
13451 | "angle_between" | "arc_length" | "bounding_box" | "centroid"
13453 | "circle_from_three_points" | "convex_hull" | "ellipse_perimeter"
13454 | "frustum_volume" | "haversine_distance" | "line_intersection"
13455 | "point_in_polygon" | "polygon_perimeter" | "pyramid_volume"
13456 | "reflect_point" | "scale_point" | "sector_area"
13457 | "torus_surface" | "torus_volume" | "translate_point"
13458 | "vector_angle" | "vector_cross" | "vector_dot" | "vector_magnitude" | "vector_normalize"
13459 | "avogadro_number" | "boltzmann_constant" | "electron_mass" | "elementary_charge"
13461 | "gravitational_constant" | "phi" | "pi" | "planck_constant" | "proton_mass"
13462 | "sol" | "tau"
13463 | "bac_estimate" | "bmi" | "break_even" | "margin" | "markup" | "roi" | "tax" | "tip"
13465 | "amortization_schedule" | "black_scholes_call" | "black_scholes_put"
13467 | "bond_price" | "bond_yield" | "capm" | "continuous_compound"
13468 | "discounted_payback" | "duration" | "irr"
13469 | "max_drawdown" | "modified_duration" | "nper" | "num_periods" | "payback_period"
13470 | "pmt" | "pv" | "rule_of_72" | "sharpe_ratio" | "sortino_ratio"
13471 | "wacc" | "xirr"
13472 | "acronym" | "atbash" | "bigrams" | "camel_to_snake" | "char_frequencies"
13474 | "chunk_string" | "collapse_whitespace" | "dedent_text" | "indent_text"
13475 | "initials" | "leetspeak" | "mask_string" | "ngrams" | "pig_latin"
13476 | "remove_consonants" | "remove_vowels" | "reverse_each_word" | "snake_to_camel"
13477 | "sort_words" | "string_distance" | "string_multiply" | "strip_html"
13478 | "trigrams" | "unique_words" | "word_frequencies" | "zalgo"
13479 | "braille_encode" | "double_metaphone" | "metaphone" | "morse_decode"
13481 | "morse_encode" | "nato_phonetic" | "phonetic_digit" | "subscript" | "superscript"
13482 | "to_emoji_num"
13483 | "int_to_roman" | "roman_add" | "roman_numeral_list" | "roman_to_int"
13485 | "base_convert" | "binary_to_gray" | "gray_code_sequence" | "gray_to_binary"
13487 | "ansi_256" | "ansi_truecolor" | "color_blend" | "color_complement"
13489 | "color_darken" | "color_distance" | "color_grayscale" | "color_invert"
13490 | "color_lighten" | "hsl_to_rgb" | "hsv_to_rgb" | "random_color"
13491 | "rgb_to_hsl" | "rgb_to_hsv"
13492 | "matrix_flatten" | "matrix_from_rows" | "matrix_hadamard" | "matrix_inverse"
13494 | "matrix_map" | "matrix_max" | "matrix_min" | "matrix_power" | "matrix_sum"
13495 | "matrix_transpose"
13496 | "binary_insert" | "bucket" | "clamp_array" | "group_consecutive_by"
13498 | "histogram" | "merge_sorted" | "next_permutation" | "normalize_array"
13499 | "normalize_range" | "peak_detect" | "range_compress" | "range_expand"
13500 | "reservoir_sample" | "run_length_decode_str" | "run_length_encode_str"
13501 | "zero_crossings"
13502 | "apply_window" | "bandpass_filter" | "cross_correlation" | "dft"
13504 | "downsample" | "energy" | "envelope" | "highpass_filter" | "idft"
13505 | "lowpass_filter" | "median_filter" | "normalize_signal" | "phase_spectrum"
13506 | "power_spectrum" | "resample" | "spectral_centroid" | "spectrogram" | "upsample"
13507 | "window_blackman" | "window_hamming" | "window_hann" | "window_kaiser"
13508 | "is_anagram" | "is_balanced_parens" | "is_control" | "is_numeric_string"
13510 | "is_pangram" | "is_printable" | "is_valid_cidr" | "is_valid_cron"
13511 | "is_valid_hex_color" | "is_valid_latitude" | "is_valid_longitude" | "is_valid_mime"
13512 | "eval_rpn" | "fizzbuzz" | "game_of_life_step" | "mandelbrot_char"
13514 | "sierpinski" | "tower_of_hanoi" | "truth_table"
13515 | "byte_size" | "degrees_to_compass" | "to_string_val" | "type_of"
13517 | "quadratic_roots" | "quadratic_discriminant" | "arithmetic_series"
13519 | "geometric_series" | "stirling_approx"
13520 | "double_factorial" | "rising_factorial" | "falling_factorial"
13521 | "gamma_approx" | "erf_approx" | "normal_pdf" | "normal_cdf"
13522 | "poisson_pmf" | "exponential_pdf" | "inverse_lerp"
13523 | "map_range"
13524 | "momentum" | "impulse" | "work" | "power_phys" | "torque" | "angular_velocity"
13526 | "centripetal_force" | "escape_velocity" | "orbital_velocity" | "orbital_period"
13527 | "gravitational_force" | "coulomb_force" | "electric_field" | "capacitance"
13528 | "capacitor_energy" | "inductor_energy" | "resonant_frequency"
13529 | "rc_time_constant" | "rl_time_constant" | "impedance_rlc"
13530 | "relativistic_mass" | "lorentz_factor" | "time_dilation" | "length_contraction"
13531 | "relativistic_energy" | "rest_energy" | "de_broglie_wavelength"
13532 | "photon_energy" | "photon_energy_wavelength" | "schwarzschild_radius"
13533 | "stefan_boltzmann" | "wien_displacement" | "ideal_gas_pressure" | "ideal_gas_volume"
13534 | "projectile_range" | "projectile_max_height" | "projectile_time"
13535 | "spring_force" | "spring_energy" | "pendulum_period" | "doppler_frequency"
13536 | "decibel_ratio" | "snells_law" | "brewster_angle" | "critical_angle"
13537 | "lens_power" | "thin_lens" | "magnification_lens"
13538 | "euler_mascheroni" | "apery_constant" | "feigenbaum_delta" | "feigenbaum_alpha"
13540 | "catalan_constant" | "khinchin_constant" | "glaisher_constant"
13541 | "plastic_number" | "silver_ratio" | "supergolden_ratio"
13542 | "vacuum_permittivity" | "vacuum_permeability" | "coulomb_constant"
13544 | "fine_structure_constant" | "rydberg_constant" | "bohr_radius"
13545 | "bohr_magneton" | "nuclear_magneton" | "stefan_boltzmann_constant"
13546 | "wien_constant" | "gas_constant" | "faraday_constant" | "neutron_mass"
13547 | "atomic_mass_unit" | "earth_mass" | "earth_radius" | "sun_mass" | "sun_radius"
13548 | "astronomical_unit" | "light_year" | "parsec" | "hubble_constant"
13549 | "planck_length" | "planck_time" | "planck_mass" | "planck_temperature"
13550 | "matrix_solve" | "msolve" | "solve"
13552 | "matrix_lu" | "mlu" | "matrix_qr" | "mqr"
13553 | "matrix_eigenvalues" | "meig" | "eigenvalues" | "eig"
13554 | "matrix_norm" | "mnorm" | "matrix_cond" | "mcond" | "cond"
13555 | "matrix_pinv" | "mpinv" | "pinv"
13556 | "matrix_cholesky" | "mchol" | "cholesky"
13557 | "matrix_det_general" | "mdetg" | "det"
13558 | "welch_ttest" | "welcht" | "paired_ttest" | "pairedt"
13560 | "cohen_d" | "cohend" | "anova_oneway" | "anova" | "anova1"
13561 | "spearman_corr" | "rho" | "kendall_tau" | "kendall" | "ktau"
13562 | "confidence_interval" | "ci"
13563 | "beta_pdf" | "betapdf" | "gamma_pdf" | "gammapdf"
13565 | "chi2_pdf" | "chi2pdf" | "chi_squared_pdf"
13566 | "t_pdf" | "tpdf" | "student_pdf"
13567 | "f_pdf" | "fpdf" | "fisher_pdf"
13568 | "lognormal_pdf" | "lnormpdf" | "weibull_pdf" | "weibpdf"
13569 | "cauchy_pdf" | "cauchypdf" | "laplace_pdf" | "laplacepdf"
13570 | "pareto_pdf" | "paretopdf"
13571 | "lagrange_interp" | "lagrange" | "linterp"
13573 | "cubic_spline" | "cspline" | "spline"
13574 | "poly_eval" | "polyval" | "polynomial_fit" | "polyfit"
13575 | "trapz" | "trapezoid" | "simpson" | "simps"
13577 | "numerical_diff" | "numdiff" | "diff_array"
13578 | "cumtrapz" | "cumulative_trapz"
13579 | "bisection" | "bisect" | "newton_method" | "newton" | "newton_raphson"
13581 | "golden_section" | "golden" | "gss"
13582 | "rk4" | "runge_kutta" | "rk4_ode" | "euler_ode" | "euler_method"
13584 | "dijkstra" | "shortest_path" | "bellman_ford" | "bellmanford"
13586 | "floyd_warshall" | "floydwarshall" | "apsp"
13587 | "prim_mst" | "mst" | "prim"
13588 | "cot" | "sec" | "csc" | "acot" | "asec" | "acsc" | "sinc" | "versin" | "versine"
13590 | "leaky_relu" | "lrelu" | "elu" | "selu" | "gelu"
13592 | "silu" | "swish" | "mish" | "softplus"
13593 | "hard_sigmoid" | "hardsigmoid" | "hard_swish" | "hardswish"
13594 | "bessel_j0" | "j0" | "bessel_j1" | "j1"
13596 | "lambert_w" | "lambertw" | "productlog"
13597 | "mod_exp" | "modexp" | "powmod"
13599 | "mod_inv" | "modinv" | "chinese_remainder" | "crt"
13600 | "miller_rabin" | "millerrabin" | "is_probable_prime"
13601 | "derangements" | "stirling2" | "stirling_second"
13603 | "bernoulli_number" | "bernoulli" | "harmonic_number" | "harmonic"
13604 | "drag_force" | "fdrag" | "ideal_gas" | "pv_nrt"
13606 | "bs_delta" | "bsdelta" | "option_delta"
13608 | "bs_gamma" | "bsgamma" | "option_gamma"
13609 | "bs_vega" | "bsvega" | "option_vega"
13610 | "bs_theta" | "bstheta" | "option_theta"
13611 | "bs_rho" | "bsrho" | "option_rho"
13612 | "bond_duration" | "mac_duration"
13613 | "dct" | "idct" | "goertzel" | "chirp" | "chirp_signal"
13615 | "base85_encode" | "b85e" | "ascii85_encode" | "a85e"
13617 | "base85_decode" | "b85d" | "ascii85_decode" | "a85d"
13618 | "pnorm" | "qnorm" | "pbinom" | "dbinom" | "ppois"
13620 | "punif" | "pexp" | "pweibull" | "plnorm" | "pcauchy"
13621 | "rbind" | "cbind"
13623 | "row_sums" | "rowSums" | "col_sums" | "colSums"
13624 | "row_means" | "rowMeans" | "col_means" | "colMeans"
13625 | "outer_product" | "outer" | "crossprod" | "tcrossprod"
13626 | "nrow" | "ncol" | "prop_table" | "proptable"
13627 | "cummax" | "cummin" | "scale_vec" | "scale"
13629 | "which_fn" | "tabulate"
13630 | "duplicated" | "duped" | "rev_vec"
13631 | "seq_fn" | "rep_fn" | "rep"
13632 | "cut_bins" | "cut" | "find_interval" | "findInterval"
13633 | "ecdf_fn" | "ecdf" | "density_est" | "density"
13634 | "embed_ts" | "embed"
13635 | "shapiro_test" | "shapiro" | "ks_test" | "ks"
13637 | "wilcox_test" | "wilcox" | "mann_whitney"
13638 | "prop_test" | "proptest" | "binom_test" | "binomtest"
13639 | "sapply" | "tapply" | "do_call" | "docall"
13641 | "kmeans" | "prcomp" | "pca"
13643 | "rnorm" | "runif" | "rexp" | "rbinom" | "rpois" | "rgeom"
13645 | "rgamma" | "rbeta" | "rchisq" | "rt" | "rf"
13646 | "rweibull" | "rlnorm" | "rcauchy"
13647 | "qunif" | "qexp" | "qweibull" | "qlnorm" | "qcauchy"
13649 | "pgamma" | "pbeta" | "pchisq" | "pt_cdf" | "pt" | "pf_cdf" | "pf"
13651 | "dgeom" | "dunif" | "dnbinom" | "dhyper"
13653 | "lowess" | "loess" | "approx_fn" | "approx"
13655 | "lm_fit" | "lm"
13657 | "qgamma" | "qbeta" | "qchisq" | "qt_fn" | "qt" | "qf_fn" | "qf"
13659 | "qbinom" | "qpois"
13660 | "acf_fn" | "acf" | "pacf_fn" | "pacf"
13662 | "diff_lag" | "diff_ts" | "ts_filter" | "filter_ts"
13663 | "predict_lm" | "predict" | "confint_lm" | "confint"
13665 | "cor_matrix" | "cor_mat" | "cov_matrix" | "cov_mat"
13667 | "mahalanobis" | "mahal" | "dist_matrix" | "dist_mat"
13668 | "hclust" | "cutree" | "weighted_var" | "wvar" | "cov2cor"
13669 | "scatter_svg" | "scatter_plot" | "line_svg" | "line_plot"
13671 | "plot_svg" | "hist_svg" | "histogram_svg"
13672 | "boxplot_svg" | "box_plot" | "bar_svg" | "barchart_svg"
13673 | "pie_svg" | "pie_chart" | "heatmap_svg" | "heatmap"
13674 | "donut_svg" | "donut" | "area_svg" | "area_chart"
13675 | "hbar_svg" | "hbar" | "radar_svg" | "radar" | "spider"
13676 | "candlestick_svg" | "candlestick" | "ohlc"
13677 | "violin_svg" | "violin" | "cor_heatmap" | "cor_matrix_svg"
13678 | "stacked_bar_svg" | "stacked_bar"
13679 | "wordcloud_svg" | "wordcloud" | "wcloud"
13680 | "treemap_svg" | "treemap"
13681 | "pvw"
13682 | "cyber_city" | "cyber_grid" | "cyber_rain" | "matrix_rain"
13684 | "cyber_glitch" | "glitch_text" | "cyber_banner" | "neon_banner"
13685 | "cyber_circuit" | "cyber_skull" | "cyber_eye"
13686 | "ai" | "ai_agent" | "prompt" | "stream_prompt" | "stream_prompt_cb"
13688 | "tokens_of"
13689 | "ai_estimate" | "ai_cost" | "ai_history" | "ai_history_clear"
13690 | "ai_cache_clear" | "ai_cache_size"
13691 | "ai_mock_install" | "ai_mock_clear"
13692 | "ai_config_get" | "ai_config_set" | "ai_routing_get" | "ai_routing_set"
13693 | "ai_register_tool" | "ai_unregister_tool" | "ai_clear_tools" | "ai_tools_list"
13694 | "ai_filter" | "ai_map" | "ai_classify" | "ai_match" | "ai_sort" | "ai_dedupe"
13695 | "ai_extract" | "ai_summarize" | "ai_translate" | "ai_template"
13696 | "ai_session_new" | "ai_session_send" | "ai_session_history"
13697 | "ai_session_close" | "ai_session_reset"
13698 | "ai_session_export" | "ai_session_import"
13699 | "ai_memory_save" | "ai_memory_recall" | "ai_memory_forget"
13700 | "ai_memory_count" | "ai_memory_clear"
13701 | "ai_vision" | "ai_pdf" | "ai_grounded" | "ai_citations"
13702 | "ai_transcribe" | "ai_speak" | "ai_image" | "ai_image_edit" | "ai_image_variation"
13703 | "ai_models" | "ai_describe" | "ai_pricing" | "ai_dashboard"
13704 | "ai_moderate" | "ai_chunk" | "ai_warm" | "ai_compare"
13705 | "ai_last_thinking" | "ai_budget" | "ai_batch" | "ai_pmap"
13706 | "ai_file_upload" | "ai_file_list" | "ai_file_get" | "ai_file_delete"
13707 | "ai_file_anthropic_upload" | "ai_file_anthropic_list" | "ai_file_anthropic_delete"
13708 | "vec_cosine" | "vec_search" | "vec_topk"
13709 | "web_search_tool" | "fetch_url_tool" | "read_file_tool" | "run_code_tool"
13711 | "mcp_connect" | "mcp_close" | "mcp_tools" | "mcp_call"
13713 | "mcp_resource" | "mcp_resources" | "mcp_prompt" | "mcp_prompts"
13714 | "mcp_attach_to_ai" | "mcp_detach_from_ai" | "mcp_attached"
13715 | "mcp_server_start" | "mcp_serve_registered_tools"
13716 | "pty_spawn" | "pty_send" | "pty_read" | "pty_expect" | "pty_expect_table"
13718 | "pty_buffer" | "pty_alive" | "pty_eof" | "pty_close" | "pty_interact"
13719 | "pty_strip_ansi" | "pty_after_eof" | "pty_pending_events"
13720 | "stress_fp" | "stress_int" | "stress_cache" | "stress_branch"
13722 | "stress_sort" | "stress_alloc" | "stress_mmap" | "stress_disk"
13723 | "stress_iops" | "stress_net" | "stress_http" | "stress_dns"
13724 | "stress_fork" | "stress_thread" | "stress_aes" | "stress_compress"
13725 | "stress_regex" | "stress_json" | "stress_burst" | "stress_ramp"
13726 | "stress_oscillate" | "stress_all" | "stress_temp" | "stress_thermal_zones"
13727 | "stress_freq" | "stress_throttled" | "stress_load" | "stress_meminfo"
13728 | "stress_cores" | "stress_arm_kill_switch" | "stress_killed"
13729 | "stress_disarm_kill_switch"
13730 | "stress_metrics_record" | "stress_metrics_clear" | "stress_metrics_count"
13731 | "stress_metrics_export" | "stress_metrics_prometheus"
13732 | "stress_metrics_json" | "stress_metrics_csv" | "stress_metrics_watch"
13733 | "audit_log" | "audit_log_path"
13735 | "secrets_encrypt" | "secrets_decrypt" | "secrets_random_key" | "secrets_kdf"
13736 | "web_route" | "web_resources" | "web_root" | "web_routes_table"
13738 | "web_application_config" | "web_boot_application"
13739 | "web_render" | "web_render_partial" | "web_redirect"
13740 | "web_json" | "web_text" | "web_csv" | "web_markdown"
13741 | "web_params" | "web_request" | "web_set_header" | "web_status"
13742 | "web_before_action" | "web_after_action"
13743 | "web_session" | "web_session_set" | "web_session_get" | "web_session_clear"
13744 | "web_signed" | "web_unsigned"
13745 | "web_cookies" | "web_set_cookie"
13746 | "web_flash" | "web_flash_set" | "web_flash_get"
13747 | "web_validate" | "web_permit"
13748 | "web_password_hash" | "web_password_verify"
13749 | "web_token_for" | "web_token_consume" | "web_csrf_meta_tag"
13750 | "web_security_headers" | "web_can"
13751 | "web_h" | "web_truncate" | "web_pluralize" | "web_time_ago_in_words"
13752 | "web_image_tag" | "web_link_to" | "web_button_to"
13753 | "web_form_with" | "web_form_close"
13754 | "web_text_field" | "web_text_area" | "web_check_box"
13755 | "web_stylesheet_link_tag" | "web_javascript_link_tag"
13756 | "web_yield_content" | "web_content_for"
13757 | "web_etag" | "web_cache_get" | "web_cache_set"
13758 | "web_cache_delete" | "web_cache_clear"
13759 | "web_db_connect" | "web_db_execute" | "web_db_query"
13760 | "web_db_begin" | "web_db_commit" | "web_db_rollback"
13761 | "web_create_table" | "web_drop_table"
13762 | "web_add_column" | "web_remove_column"
13763 | "web_migrate" | "web_rollback"
13764 | "web_model_all" | "web_model_find" | "web_model_first" | "web_model_last"
13765 | "web_model_where" | "web_model_create" | "web_model_update"
13766 | "web_model_destroy" | "web_model_count" | "web_model_increment"
13767 | "web_model_paginate" | "web_model_search" | "web_model_soft_destroy"
13768 | "web_model_with"
13769 | "web_jobs_init" | "web_job_enqueue" | "web_job_dequeue"
13770 | "web_job_complete" | "web_job_fail"
13771 | "web_jobs_list" | "web_jobs_stats" | "web_job_purge"
13772 | "web_jsonapi_resource" | "web_jsonapi_collection" | "web_jsonapi_error"
13773 | "web_bearer_token" | "web_jwt_encode" | "web_jwt_decode"
13774 | "web_otp_secret" | "web_otp_generate" | "web_otp_verify"
13775 | "web_uuid" | "web_now" | "web_log" | "web_rate_limit"
13776 | "web_t" | "web_load_locale" | "web_openapi"
13777 | "web_faker_int" | "web_faker_email" | "web_faker_name"
13778 | "web_faker_sentence" | "web_faker_paragraph"
13779 => Some(name),
13780 _ => None,
13781 }
13782 }
13783
13784 fn is_reserved_hash_name(name: &str) -> bool {
13787 matches!(
13788 name,
13789 "b" | "pc"
13790 | "e"
13791 | "a"
13792 | "d"
13793 | "c"
13794 | "p"
13795 | "all"
13796 | "stryke::builtins"
13797 | "stryke::perl_compats"
13798 | "stryke::extensions"
13799 | "stryke::aliases"
13800 | "stryke::descriptions"
13801 | "stryke::categories"
13802 | "stryke::primaries"
13803 | "stryke::all"
13804 )
13805 }
13806
13807 const RESERVED_FUNCTION_NAMES: &'static [&'static str] = &[
13812 "y",
13813 "tr",
13814 "s",
13815 "m",
13816 "q",
13817 "qq",
13818 "qw",
13819 "qx",
13820 "qr",
13821 "if",
13822 "unless",
13823 "while",
13824 "until",
13825 "for",
13826 "foreach",
13827 "given",
13828 "when",
13829 "else",
13830 "elsif",
13831 "do",
13832 "eval",
13833 "return",
13834 "last",
13835 "next",
13836 "redo",
13837 "goto",
13838 "my",
13839 "our",
13840 "local",
13841 "state",
13842 "sub",
13843 "fn",
13844 "class",
13845 "struct",
13846 "enum",
13847 "trait",
13848 "use",
13849 "no",
13850 "require",
13851 "package",
13852 "BEGIN",
13853 "END",
13854 "CHECK",
13855 "INIT",
13856 "UNITCHECK",
13857 "and",
13858 "or",
13859 "not",
13860 "x",
13861 "eq",
13862 "ne",
13863 "lt",
13864 "gt",
13865 "le",
13866 "ge",
13867 "cmp",
13868 ];
13869
13870 fn check_udf_shadows_builtin(&self, name: &str, line: usize) -> PerlResult<()> {
13871 if !name.contains("::") {
13873 if Self::RESERVED_FUNCTION_NAMES.contains(&name) {
13874 return Err(self.syntax_err(
13875 format!("`{name}` is a reserved word and cannot be used as a function name"),
13876 line,
13877 ));
13878 }
13879 if Self::is_known_bareword(name)
13880 || Self::is_try_builtin_name(name)
13881 || crate::list_builtins::is_list_builtin_name(name)
13882 {
13883 return Err(self.syntax_err(
13884 format!(
13885"`{name}` is a stryke builtin and cannot be redefined (this is not Perl 5; use `fn` not `sub`, or pass --compat)"
13886 ),
13887 line,
13888 ));
13889 }
13890 }
13891 Ok(())
13892 }
13893
13894 fn check_hash_shadows_reserved(&self, name: &str, line: usize) -> PerlResult<()> {
13897 if Self::is_reserved_hash_name(name) {
13898 return Err(self.syntax_err(
13899 format!(
13900"`%{name}` is a stryke reserved hash and cannot be redefined (this is not Perl 5; pass --compat for Perl 5 mode)"
13901 ),
13902 line,
13903 ));
13904 }
13905 Ok(())
13906 }
13907
13908 fn validate_hash_assignment(&self, value: &Expr, line: usize) -> PerlResult<()> {
13911 match &value.kind {
13912 ExprKind::Integer(_) | ExprKind::Float(_) => {
13913 return Err(self.syntax_err(
13914 "cannot assign scalar to hash — use %h = (key => value) or %h = %{$hashref}",
13915 line,
13916 ));
13917 }
13918 ExprKind::String(_) | ExprKind::InterpolatedString(_) | ExprKind::Bareword(_) => {
13919 return Err(self.syntax_err(
13920 "cannot assign string to hash — use %h = (key => value) or %h = %{$hashref}",
13921 line,
13922 ));
13923 }
13924 ExprKind::ArrayRef(_) => {
13925 return Err(self.syntax_err(
13926 "cannot assign arrayref to hash — use %h = @{$arrayref} for even-length list",
13927 line,
13928 ));
13929 }
13930 ExprKind::ScalarRef(inner) => {
13931 if matches!(inner.kind, ExprKind::ArrayVar(_)) {
13932 return Err(self.syntax_err(
13933 "cannot assign \\@array to hash — use %h = @array for even-length list",
13934 line,
13935 ));
13936 }
13937 if matches!(inner.kind, ExprKind::HashVar(_)) {
13938 return Err(self.syntax_err(
13939 "cannot assign \\%hash to hash — use %h = %other directly",
13940 line,
13941 ));
13942 }
13943 }
13944 ExprKind::HashRef(_) => {
13945 return Err(self.syntax_err(
13946 "cannot assign hashref to hash — use %h = %{$hashref} to dereference",
13947 line,
13948 ));
13949 }
13950 ExprKind::CodeRef { .. } => {
13951 return Err(self.syntax_err("cannot assign coderef to hash", line));
13952 }
13953 ExprKind::Undef => {
13954 return Err(
13955 self.syntax_err("cannot assign undef to hash — use %h = () to empty", line)
13956 );
13957 }
13958 ExprKind::List(items)
13959 if items.len() % 2 != 0
13960 && !items.iter().any(|e| {
13961 matches!(
13962 e.kind,
13963 ExprKind::ArrayVar(_)
13964 | ExprKind::HashVar(_)
13965 | ExprKind::FuncCall { .. }
13966 | ExprKind::Deref { .. }
13967 | ExprKind::ScalarVar(_)
13968 )
13969 }) =>
13970 {
13971 return Err(self.syntax_err(
13972 format!(
13973 "odd-length list ({} elements) in hash assignment — missing value for last key",
13974 items.len()
13975 ),
13976 line,
13977 ));
13978 }
13979 _ => {}
13980 }
13981 Ok(())
13982 }
13983
13984 fn validate_array_assignment(&self, value: &Expr, line: usize) -> PerlResult<()> {
13989 if let ExprKind::Undef = &value.kind {
13990 return Err(
13991 self.syntax_err("cannot assign undef to array — use @a = () to empty", line)
13992 );
13993 }
13994 Ok(())
13995 }
13996
13997 fn validate_scalar_assignment(&self, value: &Expr, line: usize) -> PerlResult<()> {
14000 if let ExprKind::List(items) = &value.kind {
14001 if items.len() > 1 {
14002 return Err(self.syntax_err(
14003 format!(
14004 "cannot assign {}-element list to scalar — Perl 5 silently takes last element; use ($x) = (list) or $x = $list[-1]",
14005 items.len()
14006 ),
14007 line,
14008 ));
14009 }
14010 }
14011 Ok(())
14012 }
14013
14014 fn validate_assignment(&self, target: &Expr, value: &Expr, line: usize) -> PerlResult<()> {
14016 if crate::compat_mode() {
14017 return Ok(());
14018 }
14019 match &target.kind {
14020 ExprKind::HashVar(_) => self.validate_hash_assignment(value, line),
14021 ExprKind::ArrayVar(_) => self.validate_array_assignment(value, line),
14022 ExprKind::ScalarVar(_) => self.validate_scalar_assignment(value, line),
14023 _ => Ok(()),
14024 }
14025 }
14026
14027 fn parse_block_or_bareword_cmp_block(&mut self) -> PerlResult<Block> {
14031 if matches!(self.peek(), Token::LBrace) {
14032 return self.parse_block();
14033 }
14034 let line = self.peek_line();
14035 if let Token::Ident(ref name) = self.peek().clone() {
14037 if matches!(
14038 self.peek_at(1),
14039 Token::Comma | Token::Semicolon | Token::RBrace | Token::Eof | Token::PipeForward
14040 ) {
14041 let name = name.clone();
14042 self.advance();
14043 let body = Expr {
14044 kind: ExprKind::FuncCall {
14045 name,
14046 args: vec![
14047 Expr {
14048 kind: ExprKind::ScalarVar("a".to_string()),
14049 line,
14050 },
14051 Expr {
14052 kind: ExprKind::ScalarVar("b".to_string()),
14053 line,
14054 },
14055 ],
14056 },
14057 line,
14058 };
14059 return Ok(vec![Statement::new(StmtKind::Expression(body), line)]);
14060 }
14061 }
14062 let expr = self.parse_assign_expr_stop_at_pipe()?;
14064 Ok(vec![Statement::new(StmtKind::Expression(expr), line)])
14065 }
14066
14067 fn parse_fan_optional_progress(
14069 &mut self,
14070 which: &'static str,
14071 ) -> PerlResult<Option<Box<Expr>>> {
14072 let line = self.peek_line();
14073 if self.eat(&Token::Comma) {
14074 match self.peek() {
14075 Token::Ident(ref kw)
14076 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) =>
14077 {
14078 self.advance();
14079 self.expect(&Token::FatArrow)?;
14080 return Ok(Some(Box::new(self.parse_assign_expr()?)));
14081 }
14082 _ => {
14083 return Err(self.syntax_err(
14084 format!("{which}: expected `progress => EXPR` after comma"),
14085 line,
14086 ));
14087 }
14088 }
14089 }
14090 if let Token::Ident(ref kw) = self.peek().clone() {
14091 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
14092 self.advance();
14093 self.expect(&Token::FatArrow)?;
14094 return Ok(Some(Box::new(self.parse_assign_expr()?)));
14095 }
14096 }
14097 Ok(None)
14098 }
14099
14100 fn parse_assign_expr_list_optional_progress(&mut self) -> PerlResult<(Expr, Option<Expr>)> {
14107 if self.in_pipe_rhs()
14113 && matches!(
14114 self.peek(),
14115 Token::Semicolon
14116 | Token::RBrace
14117 | Token::RParen
14118 | Token::Eof
14119 | Token::PipeForward
14120 | Token::Comma
14121 )
14122 {
14123 return Ok((self.pipe_placeholder_list(self.peek_line()), None));
14124 }
14125 let mut parts = vec![self.parse_assign_expr_stop_at_pipe()?];
14126 loop {
14127 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
14128 break;
14129 }
14130 if matches!(
14131 self.peek(),
14132 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof | Token::PipeForward
14133 ) {
14134 break;
14135 }
14136 if self.peek_is_postfix_stmt_modifier_keyword() {
14137 break;
14138 }
14139 if let Token::Ident(ref kw) = self.peek().clone() {
14140 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
14141 self.advance();
14142 self.expect(&Token::FatArrow)?;
14143 let prog = self.parse_assign_expr_stop_at_pipe()?;
14144 return Ok((merge_expr_list(parts), Some(prog)));
14145 }
14146 }
14147 parts.push(self.parse_assign_expr_stop_at_pipe()?);
14148 }
14149 Ok((merge_expr_list(parts), None))
14150 }
14151
14152 fn parse_one_arg(&mut self) -> PerlResult<Expr> {
14153 if matches!(self.peek(), Token::LParen) {
14154 self.advance();
14155 let expr = self.parse_expression()?;
14156 self.expect(&Token::RParen)?;
14157 Ok(expr)
14158 } else {
14159 self.parse_assign_expr_stop_at_pipe()
14160 }
14161 }
14162
14163 fn parse_named_unary_arg(&mut self) -> PerlResult<Expr> {
14172 if matches!(self.peek(), Token::LParen) {
14173 self.advance();
14174 let expr = self.parse_expression()?;
14175 self.expect(&Token::RParen)?;
14176 Ok(expr)
14177 } else {
14178 self.parse_shift()
14179 }
14180 }
14181
14182 fn parse_one_arg_or_default(&mut self) -> PerlResult<Expr> {
14183 if matches!(
14190 self.peek(),
14191 Token::Semicolon
14193 | Token::RBrace
14194 | Token::RParen
14195 | Token::RBracket
14196 | Token::Eof
14197 | Token::Comma
14198 | Token::FatArrow
14199 | Token::PipeForward
14200 | Token::Question
14202 | Token::Colon
14203 | Token::NumEq | Token::NumNe | Token::NumLt | Token::NumGt
14205 | Token::NumLe | Token::NumGe | Token::Spaceship
14206 | Token::StrEq | Token::StrNe | Token::StrLt | Token::StrGt
14207 | Token::StrLe | Token::StrGe | Token::StrCmp
14208 | Token::LogAnd | Token::LogOr | Token::LogNot
14210 | Token::LogAndWord | Token::LogOrWord | Token::LogNotWord
14211 | Token::DefinedOr
14212 | Token::Range | Token::RangeExclusive
14214 | Token::Assign | Token::PlusAssign | Token::MinusAssign
14216 | Token::MulAssign | Token::DivAssign | Token::ModAssign
14217 | Token::PowAssign | Token::DotAssign | Token::AndAssign
14218 | Token::OrAssign | Token::XorAssign | Token::DefinedOrAssign
14219 | Token::ShiftLeftAssign | Token::ShiftRightAssign
14220 | Token::BitAndAssign | Token::BitOrAssign
14221 ) {
14222 return Ok(Expr {
14223 kind: ExprKind::ScalarVar("_".into()),
14224 line: self.peek_line(),
14225 });
14226 }
14227 if matches!(self.peek(), Token::LParen) && matches!(self.peek_at(1), Token::RParen) {
14231 let line = self.peek_line();
14232 self.advance(); self.advance(); return Ok(Expr {
14235 kind: ExprKind::ScalarVar("_".into()),
14236 line,
14237 });
14238 }
14239 self.parse_named_unary_arg()
14244 }
14245
14246 fn parse_one_arg_or_argv(&mut self) -> PerlResult<Expr> {
14248 let line = self.prev_line(); if matches!(self.peek(), Token::LParen) {
14250 self.advance();
14251 if matches!(self.peek(), Token::RParen) {
14252 self.advance();
14253 return Ok(Expr {
14254 kind: ExprKind::ArrayVar("_".into()),
14255 line: self.peek_line(),
14256 });
14257 }
14258 let expr = self.parse_expression()?;
14259 self.expect(&Token::RParen)?;
14260 return Ok(expr);
14261 }
14262 if matches!(
14264 self.peek(),
14265 Token::Semicolon
14266 | Token::RBrace
14267 | Token::RParen
14268 | Token::Eof
14269 | Token::Comma
14270 | Token::PipeForward
14271 ) || self.peek_line() > line
14272 {
14273 Ok(Expr {
14274 kind: ExprKind::ArrayVar("_".into()),
14275 line,
14276 })
14277 } else {
14278 self.parse_assign_expr()
14279 }
14280 }
14281
14282 fn parse_builtin_args(&mut self) -> PerlResult<Vec<Expr>> {
14283 if matches!(self.peek(), Token::LParen) {
14284 self.advance();
14285 let args = self.parse_arg_list()?;
14286 self.expect(&Token::RParen)?;
14287 Ok(args)
14288 } else if self.suppress_parenless_call > 0 && matches!(self.peek(), Token::Ident(_)) {
14289 Ok(vec![])
14292 } else {
14293 self.parse_list_until_terminator()
14294 }
14295 }
14296
14297 #[inline]
14301 fn fat_arrow_autoquote(&self, name: &str, line: usize) -> Option<Expr> {
14302 if matches!(self.peek(), Token::FatArrow) {
14303 Some(Expr {
14304 kind: ExprKind::String(name.to_string()),
14305 line,
14306 })
14307 } else {
14308 None
14309 }
14310 }
14311
14312 fn parse_hash_subscript_key(&mut self) -> PerlResult<Expr> {
14323 let line = self.peek_line();
14324 if let Token::Ident(ref k) = self.peek().clone() {
14325 if matches!(self.peek_at(1), Token::RBrace) && !Self::is_underscore_topic_slot(k) {
14326 let s = k.clone();
14327 self.advance();
14328 return Ok(Expr {
14329 kind: ExprKind::String(s),
14330 line,
14331 });
14332 }
14333 }
14334 if matches!(self.peek_at(1), Token::RBrace) {
14335 if let Some(s) = Self::operator_keyword_to_ident_str(self.peek()) {
14336 self.advance();
14337 return Ok(Expr {
14338 kind: ExprKind::String(s.to_string()),
14339 line,
14340 });
14341 }
14342 }
14343 self.parse_expression()
14344 }
14345
14346 #[inline]
14348 fn peek_is_glob_par_progress_kw(&self) -> bool {
14349 matches!(self.peek(), Token::Ident(ref kw) if kw == "progress")
14350 && matches!(self.peek_at(1), Token::FatArrow)
14351 }
14352
14353 fn parse_pattern_list_until_rparen_or_progress(&mut self) -> PerlResult<Vec<Expr>> {
14355 let mut args = Vec::new();
14356 loop {
14357 if matches!(self.peek(), Token::RParen | Token::Eof) {
14358 break;
14359 }
14360 if self.peek_is_glob_par_progress_kw() {
14361 break;
14362 }
14363 args.push(self.parse_assign_expr()?);
14364 match self.peek() {
14365 Token::RParen => break,
14366 Token::Comma => {
14367 self.advance();
14368 if matches!(self.peek(), Token::RParen) {
14369 break;
14370 }
14371 if self.peek_is_glob_par_progress_kw() {
14372 break;
14373 }
14374 }
14375 _ => {
14376 return Err(self.syntax_err(
14377 "expected `,`, `)`, or `progress =>` after argument in `glob_par` / `par_sed`",
14378 self.peek_line(),
14379 ));
14380 }
14381 }
14382 }
14383 Ok(args)
14384 }
14385
14386 fn parse_pattern_list_glob_par_bare(&mut self) -> PerlResult<Vec<Expr>> {
14388 let mut args = Vec::new();
14389 loop {
14390 if matches!(
14391 self.peek(),
14392 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof
14393 ) {
14394 break;
14395 }
14396 if self.peek_is_postfix_stmt_modifier_keyword() {
14397 break;
14398 }
14399 if self.peek_is_glob_par_progress_kw() {
14400 break;
14401 }
14402 args.push(self.parse_assign_expr()?);
14403 if !self.eat(&Token::Comma) {
14404 break;
14405 }
14406 if self.peek_is_glob_par_progress_kw() {
14407 break;
14408 }
14409 }
14410 Ok(args)
14411 }
14412
14413 fn parse_glob_par_or_par_sed_args(&mut self) -> PerlResult<(Vec<Expr>, Option<Box<Expr>>)> {
14415 if matches!(self.peek(), Token::LParen) {
14416 self.advance();
14417 let args = self.parse_pattern_list_until_rparen_or_progress()?;
14418 let progress = if self.peek_is_glob_par_progress_kw() {
14419 self.advance();
14420 self.expect(&Token::FatArrow)?;
14421 Some(Box::new(self.parse_assign_expr()?))
14422 } else {
14423 None
14424 };
14425 self.expect(&Token::RParen)?;
14426 Ok((args, progress))
14427 } else {
14428 let args = self.parse_pattern_list_glob_par_bare()?;
14429 let progress = if self.peek_is_glob_par_progress_kw() {
14431 self.advance();
14432 self.expect(&Token::FatArrow)?;
14433 Some(Box::new(self.parse_assign_expr()?))
14434 } else {
14435 None
14436 };
14437 Ok((args, progress))
14438 }
14439 }
14440
14441 pub(crate) fn parse_arg_list(&mut self) -> PerlResult<Vec<Expr>> {
14442 let mut args = Vec::new();
14443 let saved_no_pf = self.no_pipe_forward_depth;
14447 self.no_pipe_forward_depth = 0;
14448 while !matches!(
14449 self.peek(),
14450 Token::RParen | Token::RBracket | Token::RBrace | Token::Eof
14451 ) {
14452 let arg = match self.parse_assign_expr() {
14453 Ok(e) => e,
14454 Err(err) => {
14455 self.no_pipe_forward_depth = saved_no_pf;
14456 return Err(err);
14457 }
14458 };
14459 args.push(arg);
14460 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
14461 break;
14462 }
14463 }
14464 self.no_pipe_forward_depth = saved_no_pf;
14465 Ok(args)
14466 }
14467
14468 pub(crate) fn parse_slice_arg_list(&mut self, is_hash: bool) -> PerlResult<Vec<Expr>> {
14477 let mut args = Vec::new();
14478 let saved_no_pf = self.no_pipe_forward_depth;
14479 self.no_pipe_forward_depth = 0;
14480 while !matches!(
14481 self.peek(),
14482 Token::RParen | Token::RBracket | Token::RBrace | Token::Eof
14483 ) {
14484 let arg = match self.parse_slice_arg(is_hash) {
14485 Ok(e) => e,
14486 Err(err) => {
14487 self.no_pipe_forward_depth = saved_no_pf;
14488 return Err(err);
14489 }
14490 };
14491 args.push(arg);
14492 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
14493 break;
14494 }
14495 }
14496 self.no_pipe_forward_depth = saved_no_pf;
14497 Ok(args)
14498 }
14499
14500 fn parse_slice_arg(&mut self, is_hash: bool) -> PerlResult<Expr> {
14502 let line = self.peek_line();
14503
14504 if matches!(self.peek(), Token::Colon) {
14506 self.advance();
14507 return self.finish_slice_range(None, false, is_hash, line);
14508 }
14509 if matches!(self.peek(), Token::PackageSep) {
14510 self.advance();
14511 return self.finish_slice_range(None, true, is_hash, line);
14512 }
14513
14514 self.suppress_colon_range = self.suppress_colon_range.saturating_add(1);
14517 let result = self.parse_slice_endpoint(is_hash);
14518 self.suppress_colon_range = self.suppress_colon_range.saturating_sub(1);
14519 let from_expr = result?;
14520
14521 if matches!(self.peek(), Token::Colon) {
14523 self.advance();
14524 return self.finish_slice_range(Some(Box::new(from_expr)), false, is_hash, line);
14525 }
14526 if matches!(self.peek(), Token::PackageSep) {
14527 self.advance();
14528 return self.finish_slice_range(Some(Box::new(from_expr)), true, is_hash, line);
14529 }
14530
14531 Ok(from_expr)
14532 }
14533
14534 fn finish_slice_range(
14541 &mut self,
14542 from: Option<Box<Expr>>,
14543 double: bool,
14544 is_hash: bool,
14545 line: usize,
14546 ) -> PerlResult<Expr> {
14547 let (to, step) = if double {
14548 let step_v = self.parse_slice_optional_endpoint(is_hash)?;
14550 (None, step_v)
14551 } else {
14552 let to_v = self.parse_slice_optional_endpoint(is_hash)?;
14554 let step_v = if matches!(self.peek(), Token::Colon) {
14555 self.advance();
14556 self.parse_slice_optional_endpoint(is_hash)?
14557 } else if matches!(self.peek(), Token::PackageSep) {
14558 return Err(
14559 self.syntax_err("Unexpected `::` after slice TO endpoint".to_string(), line)
14560 );
14561 } else {
14562 None
14563 };
14564 (to_v, step_v)
14565 };
14566
14567 if let (Some(f), Some(t)) = (from.as_ref(), to.as_ref()) {
14570 return Ok(Expr {
14571 kind: ExprKind::Range {
14572 from: f.clone(),
14573 to: t.clone(),
14574 exclusive: false,
14575 step,
14576 },
14577 line,
14578 });
14579 }
14580
14581 Ok(Expr {
14582 kind: ExprKind::SliceRange { from, to, step },
14583 line,
14584 })
14585 }
14586
14587 fn parse_slice_optional_endpoint(&mut self, is_hash: bool) -> PerlResult<Option<Box<Expr>>> {
14590 if matches!(
14591 self.peek(),
14592 Token::Colon
14593 | Token::PackageSep
14594 | Token::Comma
14595 | Token::RBracket
14596 | Token::RBrace
14597 | Token::Eof
14598 ) {
14599 return Ok(None);
14600 }
14601 self.suppress_colon_range = self.suppress_colon_range.saturating_add(1);
14602 let r = self.parse_slice_endpoint(is_hash);
14603 self.suppress_colon_range = self.suppress_colon_range.saturating_sub(1);
14604 Ok(Some(Box::new(r?)))
14605 }
14606
14607 fn parse_slice_endpoint(&mut self, is_hash: bool) -> PerlResult<Expr> {
14611 if is_hash {
14612 if let Token::Ident(name) = self.peek().clone() {
14613 if matches!(
14614 self.peek_at(1),
14615 Token::Colon
14616 | Token::PackageSep
14617 | Token::Comma
14618 | Token::RBracket
14619 | Token::RBrace
14620 ) {
14621 let line = self.peek_line();
14622 self.advance();
14623 return Ok(Expr {
14624 kind: ExprKind::String(name),
14625 line,
14626 });
14627 }
14628 }
14629 }
14630 self.parse_assign_expr()
14631 }
14632
14633 fn parse_method_arg_list_no_paren(&mut self) -> PerlResult<Vec<Expr>> {
14637 let mut args = Vec::new();
14638 let call_line = self.prev_line();
14639 loop {
14640 if args.is_empty() && matches!(self.peek(), Token::LBrace) {
14643 break;
14644 }
14645 if matches!(
14646 self.peek(),
14647 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof | Token::PipeForward
14648 ) {
14649 break;
14650 }
14651 if let Token::Ident(ref kw) = self.peek().clone() {
14652 if matches!(
14653 kw.as_str(),
14654 "if" | "unless" | "while" | "until" | "for" | "foreach"
14655 ) {
14656 break;
14657 }
14658 }
14659 if args.is_empty()
14662 && (self.peek_method_arg_infix_terminator() || matches!(self.peek(), Token::Comma))
14663 {
14664 break;
14665 }
14666 if args.is_empty() && self.peek_line() > call_line {
14669 break;
14670 }
14671 args.push(self.parse_assign_expr()?);
14672 if !self.eat(&Token::Comma) {
14673 break;
14674 }
14675 }
14676 Ok(args)
14677 }
14678
14679 fn peek_method_arg_infix_terminator(&self) -> bool {
14682 matches!(
14683 self.peek(),
14684 Token::Plus
14685 | Token::Minus
14686 | Token::Star
14687 | Token::Slash
14688 | Token::Percent
14689 | Token::Power
14690 | Token::Dot
14691 | Token::X
14692 | Token::NumEq
14693 | Token::NumNe
14694 | Token::NumLt
14695 | Token::NumGt
14696 | Token::NumLe
14697 | Token::NumGe
14698 | Token::Spaceship
14699 | Token::StrEq
14700 | Token::StrNe
14701 | Token::StrLt
14702 | Token::StrGt
14703 | Token::StrLe
14704 | Token::StrGe
14705 | Token::StrCmp
14706 | Token::LogAnd
14707 | Token::LogOr
14708 | Token::LogAndWord
14709 | Token::LogOrWord
14710 | Token::DefinedOr
14711 | Token::BitAnd
14712 | Token::BitOr
14713 | Token::BitXor
14714 | Token::ShiftLeft
14715 | Token::ShiftRight
14716 | Token::Range
14717 | Token::RangeExclusive
14718 | Token::BindMatch
14719 | Token::BindNotMatch
14720 | Token::Arrow
14721 | Token::Question
14723 | Token::Colon
14724 | Token::Assign
14726 | Token::PlusAssign
14727 | Token::MinusAssign
14728 | Token::MulAssign
14729 | Token::DivAssign
14730 | Token::ModAssign
14731 | Token::PowAssign
14732 | Token::DotAssign
14733 | Token::AndAssign
14734 | Token::OrAssign
14735 | Token::XorAssign
14736 | Token::DefinedOrAssign
14737 | Token::ShiftLeftAssign
14738 | Token::ShiftRightAssign
14739 | Token::BitAndAssign
14740 | Token::BitOrAssign
14741 )
14742 }
14743
14744 fn parse_list_until_terminator(&mut self) -> PerlResult<Vec<Expr>> {
14745 let mut args = Vec::new();
14746 let call_line = self.prev_line();
14751 loop {
14752 if matches!(
14753 self.peek(),
14754 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof | Token::PipeForward
14755 ) {
14756 break;
14757 }
14758 if let Token::Ident(ref kw) = self.peek().clone() {
14760 if matches!(
14761 kw.as_str(),
14762 "if" | "unless" | "while" | "until" | "for" | "foreach"
14763 ) {
14764 break;
14765 }
14766 }
14767 if args.is_empty() && self.peek_line() > call_line {
14774 break;
14775 }
14776 args.push(self.parse_assign_expr_stop_at_pipe()?);
14779 if !self.eat(&Token::Comma) {
14780 break;
14781 }
14782 }
14783 Ok(args)
14784 }
14785
14786 fn parse_forced_hashref_body(&mut self, line: usize) -> PerlResult<Expr> {
14793 let saved = self.pos;
14794 if let Ok(pairs) = self.try_parse_hash_ref() {
14795 return Ok(Expr {
14796 kind: ExprKind::HashRef(pairs),
14797 line,
14798 });
14799 }
14800 self.pos = saved;
14802 if matches!(self.peek(), Token::RBrace) {
14803 self.advance();
14804 return Ok(Expr {
14805 kind: ExprKind::HashRef(vec![]),
14806 line,
14807 });
14808 }
14809 let inner = self.parse_expression()?;
14813 self.expect(&Token::RBrace)?;
14814 let sentinel_key = Expr {
14815 kind: ExprKind::String("__HASH_SPREAD__".into()),
14816 line,
14817 };
14818 Ok(Expr {
14819 kind: ExprKind::HashRef(vec![(sentinel_key, inner)]),
14820 line,
14821 })
14822 }
14823
14824 fn try_parse_hash_ref(&mut self) -> PerlResult<Vec<(Expr, Expr)>> {
14825 let mut pairs = Vec::new();
14826 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
14827 let line = self.peek_line();
14832 let key = if let Token::Ident(ref name) = self.peek().clone() {
14833 if matches!(self.peek_at(1), Token::FatArrow)
14834 && !Self::is_underscore_topic_slot(name)
14835 {
14836 self.advance();
14837 Expr {
14838 kind: ExprKind::String(name.clone()),
14839 line,
14840 }
14841 } else {
14842 self.parse_assign_expr()?
14843 }
14844 } else {
14845 self.parse_assign_expr()?
14846 };
14847 if matches!(self.peek(), Token::RBrace | Token::Comma)
14851 && matches!(
14852 key.kind,
14853 ExprKind::HashVar(_)
14854 | ExprKind::Deref {
14855 kind: Sigil::Hash,
14856 ..
14857 }
14858 )
14859 {
14860 let sentinel_key = Expr {
14864 kind: ExprKind::String("__HASH_SPREAD__".into()),
14865 line,
14866 };
14867 pairs.push((sentinel_key, key));
14868 self.eat(&Token::Comma);
14869 continue;
14870 }
14871 if self.eat(&Token::FatArrow) || self.eat(&Token::Comma) {
14873 let val = self.parse_assign_expr()?;
14874 pairs.push((key, val));
14875 self.eat(&Token::Comma);
14876 } else {
14877 return Err(self.syntax_err("Expected => or , in hash ref", key.line));
14878 }
14879 }
14880 self.expect(&Token::RBrace)?;
14881 Ok(pairs)
14882 }
14883
14884 fn parse_hashref_pairs_until(&mut self, term: &Token) -> PerlResult<Vec<(Expr, Expr)>> {
14889 let mut pairs = Vec::new();
14890 while !matches!(&self.peek(), t if std::mem::discriminant(*t) == std::mem::discriminant(term))
14891 && !matches!(self.peek(), Token::Eof)
14892 {
14893 let line = self.peek_line();
14894 let key = if let Token::Ident(ref name) = self.peek().clone() {
14895 if matches!(self.peek_at(1), Token::FatArrow)
14896 && !Self::is_underscore_topic_slot(name)
14897 {
14898 self.advance();
14899 Expr {
14900 kind: ExprKind::String(name.clone()),
14901 line,
14902 }
14903 } else {
14904 self.parse_assign_expr()?
14905 }
14906 } else {
14907 self.parse_assign_expr()?
14908 };
14909 if self.eat(&Token::FatArrow) || self.eat(&Token::Comma) {
14910 let val = self.parse_assign_expr()?;
14911 pairs.push((key, val));
14912 self.eat(&Token::Comma);
14913 } else {
14914 return Err(self.syntax_err("Expected => or , in hash ref", key.line));
14915 }
14916 }
14917 Ok(pairs)
14918 }
14919
14920 fn interp_chain_subscripts(
14926 &self,
14927 chars: &[char],
14928 i: &mut usize,
14929 mut base: Expr,
14930 line: usize,
14931 ) -> Expr {
14932 loop {
14933 let (after, requires_subscript) =
14935 if *i + 1 < chars.len() && chars[*i] == '-' && chars[*i + 1] == '>' {
14936 (*i + 2, true)
14937 } else {
14938 (*i, false)
14939 };
14940 if after >= chars.len() {
14941 break;
14942 }
14943 match chars[after] {
14944 '[' => {
14945 *i = after + 1;
14946 let mut idx_str = String::new();
14947 while *i < chars.len() && chars[*i] != ']' {
14948 idx_str.push(chars[*i]);
14949 *i += 1;
14950 }
14951 if *i < chars.len() {
14952 *i += 1;
14953 }
14954 let idx_expr = if let Some(rest) = idx_str.strip_prefix('$') {
14955 Expr {
14956 kind: ExprKind::ScalarVar(rest.to_string()),
14957 line,
14958 }
14959 } else if let Ok(n) = idx_str.parse::<i64>() {
14960 Expr {
14961 kind: ExprKind::Integer(n),
14962 line,
14963 }
14964 } else {
14965 Expr {
14966 kind: ExprKind::String(idx_str),
14967 line,
14968 }
14969 };
14970 base = Expr {
14971 kind: ExprKind::ArrowDeref {
14972 expr: Box::new(base),
14973 index: Box::new(idx_expr),
14974 kind: DerefKind::Array,
14975 },
14976 line,
14977 };
14978 }
14979 '{' => {
14980 *i = after + 1;
14981 let mut key = String::new();
14982 let mut depth = 1usize;
14983 while *i < chars.len() && depth > 0 {
14984 if chars[*i] == '{' {
14985 depth += 1;
14986 } else if chars[*i] == '}' {
14987 depth -= 1;
14988 if depth == 0 {
14989 break;
14990 }
14991 }
14992 key.push(chars[*i]);
14993 *i += 1;
14994 }
14995 if *i < chars.len() {
14996 *i += 1;
14997 }
14998 let key_expr = if let Some(rest) = key.strip_prefix('$') {
14999 Expr {
15000 kind: ExprKind::ScalarVar(rest.to_string()),
15001 line,
15002 }
15003 } else {
15004 Expr {
15005 kind: ExprKind::String(key),
15006 line,
15007 }
15008 };
15009 base = Expr {
15010 kind: ExprKind::ArrowDeref {
15011 expr: Box::new(base),
15012 index: Box::new(key_expr),
15013 kind: DerefKind::Hash,
15014 },
15015 line,
15016 };
15017 }
15018 _ => {
15019 if requires_subscript {
15020 }
15022 break;
15023 }
15024 }
15025 }
15026 base
15027 }
15028
15029 fn no_interop_check_scalar_var_name(&self, name: &str, line: usize) -> PerlResult<()> {
15033 if crate::no_interop_mode() && (name == "a" || name == "b") {
15034 return Err(self.syntax_err(
15035 format!(
15036 "stryke uses `$_0` / `$_1` instead of `${}` (--no-interop is active)",
15037 name
15038 ),
15039 line,
15040 ));
15041 }
15042 Ok(())
15043 }
15044
15045 fn parse_interpolated_string(&self, s: &str, line: usize) -> PerlResult<Expr> {
15046 let mut parts = Vec::new();
15048 let mut literal = String::new();
15049 let chars: Vec<char> = s.chars().collect();
15050 let mut i = 0;
15051
15052 'istr: while i < chars.len() {
15053 if chars[i] == LITERAL_DOLLAR_IN_DQUOTE {
15054 literal.push('$');
15055 i += 1;
15056 continue;
15057 }
15058 if chars[i] == LITERAL_AT_IN_DQUOTE {
15059 literal.push('@');
15060 i += 1;
15061 continue;
15062 }
15063 if chars[i] == '\\' && i + 1 < chars.len() && chars[i + 1] == '$' {
15065 literal.push('\\');
15066 i += 1;
15067 }
15069 if chars[i] == '$' && i + 1 < chars.len() {
15070 if !literal.is_empty() {
15071 parts.push(StringPart::Literal(std::mem::take(&mut literal)));
15072 }
15073 i += 1; while i < chars.len() && chars[i].is_whitespace() {
15076 i += 1;
15077 }
15078 if i >= chars.len() {
15079 return Err(self.syntax_err("Final $ should be \\$ or $name", line));
15080 }
15081 if chars[i] == '#' {
15083 i += 1;
15084 let mut sname = String::from("#");
15085 while i < chars.len()
15086 && (chars[i].is_alphanumeric() || chars[i] == '_' || chars[i] == ':')
15087 {
15088 sname.push(chars[i]);
15089 i += 1;
15090 }
15091 while i + 1 < chars.len() && chars[i] == ':' && chars[i + 1] == ':' {
15092 sname.push_str("::");
15093 i += 2;
15094 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
15095 sname.push(chars[i]);
15096 i += 1;
15097 }
15098 }
15099 self.no_interop_check_scalar_var_name(&sname, line)?;
15100 parts.push(StringPart::ScalarVar(sname));
15101 continue;
15102 }
15103 if chars[i] == '$' {
15107 let next_c = chars.get(i + 1).copied();
15108 let is_pid = match next_c {
15109 None => true,
15110 Some(c)
15111 if !c.is_ascii_digit() && !matches!(c, 'A'..='Z' | 'a'..='z' | '_') =>
15112 {
15113 true
15114 }
15115 _ => false,
15116 };
15117 if is_pid {
15118 parts.push(StringPart::ScalarVar("$$".to_string()));
15119 i += 1; continue;
15121 }
15122 i += 1; }
15124 if chars[i] == '{' {
15125 i += 1;
15131 let mut inner = String::new();
15132 let mut depth = 1usize;
15133 while i < chars.len() && depth > 0 {
15134 match chars[i] {
15135 '{' => depth += 1,
15136 '}' => {
15137 depth -= 1;
15138 if depth == 0 {
15139 break;
15140 }
15141 }
15142 _ => {}
15143 }
15144 inner.push(chars[i]);
15145 i += 1;
15146 }
15147 if i < chars.len() {
15148 i += 1; }
15150
15151 let trimmed = inner.trim();
15155 let is_expr = trimmed.starts_with('$')
15156 || trimmed.starts_with('\\')
15157 || trimmed.starts_with('@') || trimmed.starts_with('%') || trimmed.contains(['(', '+', '-', '*', '/', '.', '?', '&', '|']);
15160 let mut base: Expr = if is_expr {
15161 match parse_expression_from_str(trimmed, "<interp>") {
15165 Ok(e) => Expr {
15166 kind: ExprKind::Deref {
15167 expr: Box::new(e),
15168 kind: Sigil::Scalar,
15169 },
15170 line,
15171 },
15172 Err(_) => Expr {
15173 kind: ExprKind::ScalarVar(inner.clone()),
15174 line,
15175 },
15176 }
15177 } else {
15178 self.no_interop_check_scalar_var_name(&inner, line)?;
15180 Expr {
15181 kind: ExprKind::ScalarVar(inner),
15182 line,
15183 }
15184 };
15185
15186 base = self.interp_chain_subscripts(&chars, &mut i, base, line);
15190 parts.push(StringPart::Expr(base));
15191 } else if chars[i] == '^' {
15192 let mut name = String::from("^");
15194 i += 1;
15195 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
15196 name.push(chars[i]);
15197 i += 1;
15198 }
15199 if i < chars.len() && chars[i] == '{' {
15200 i += 1; let mut key = String::new();
15202 let mut depth = 1;
15203 while i < chars.len() && depth > 0 {
15204 if chars[i] == '{' {
15205 depth += 1;
15206 } else if chars[i] == '}' {
15207 depth -= 1;
15208 if depth == 0 {
15209 break;
15210 }
15211 }
15212 key.push(chars[i]);
15213 i += 1;
15214 }
15215 if i < chars.len() {
15216 i += 1;
15217 }
15218 let key_expr = if let Some(rest) = key.strip_prefix('$') {
15219 Expr {
15220 kind: ExprKind::ScalarVar(rest.to_string()),
15221 line,
15222 }
15223 } else {
15224 Expr {
15225 kind: ExprKind::String(key),
15226 line,
15227 }
15228 };
15229 parts.push(StringPart::Expr(Expr {
15230 kind: ExprKind::HashElement {
15231 hash: name,
15232 key: Box::new(key_expr),
15233 },
15234 line,
15235 }));
15236 } else if i < chars.len() && chars[i] == '[' {
15237 i += 1;
15238 let mut idx_str = String::new();
15239 while i < chars.len() && chars[i] != ']' {
15240 idx_str.push(chars[i]);
15241 i += 1;
15242 }
15243 if i < chars.len() {
15244 i += 1;
15245 }
15246 let idx_expr = if let Some(rest) = idx_str.strip_prefix('$') {
15247 Expr {
15248 kind: ExprKind::ScalarVar(rest.to_string()),
15249 line,
15250 }
15251 } else if let Ok(n) = idx_str.parse::<i64>() {
15252 Expr {
15253 kind: ExprKind::Integer(n),
15254 line,
15255 }
15256 } else {
15257 Expr {
15258 kind: ExprKind::String(idx_str),
15259 line,
15260 }
15261 };
15262 parts.push(StringPart::Expr(Expr {
15263 kind: ExprKind::ArrayElement {
15264 array: name,
15265 index: Box::new(idx_expr),
15266 },
15267 line,
15268 }));
15269 } else {
15270 self.no_interop_check_scalar_var_name(&name, line)?;
15271 parts.push(StringPart::ScalarVar(name));
15272 }
15273 } else if chars[i].is_alphabetic() || chars[i] == '_' {
15274 let mut name = String::new();
15275 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
15276 name.push(chars[i]);
15277 i += 1;
15278 }
15279 if name == "_" {
15281 while i < chars.len() && chars[i] == '<' {
15282 name.push('<');
15283 i += 1;
15284 }
15285 }
15286 self.no_interop_check_scalar_var_name(&name, line)?;
15291 let mut base = if i < chars.len() && chars[i] == '{' {
15296 i += 1; let mut key = String::new();
15299 let mut depth = 1;
15300 while i < chars.len() && depth > 0 {
15301 if chars[i] == '{' {
15302 depth += 1;
15303 } else if chars[i] == '}' {
15304 depth -= 1;
15305 if depth == 0 {
15306 break;
15307 }
15308 }
15309 key.push(chars[i]);
15310 i += 1;
15311 }
15312 if i < chars.len() {
15313 i += 1;
15314 } let key_expr = if let Some(rest) = key.strip_prefix('$') {
15316 Expr {
15317 kind: ExprKind::ScalarVar(rest.to_string()),
15318 line,
15319 }
15320 } else {
15321 Expr {
15322 kind: ExprKind::String(key),
15323 line,
15324 }
15325 };
15326 Expr {
15327 kind: ExprKind::HashElement {
15328 hash: name,
15329 key: Box::new(key_expr),
15330 },
15331 line,
15332 }
15333 } else if i < chars.len() && chars[i] == '[' {
15334 i += 1;
15336 let mut idx_str = String::new();
15337 while i < chars.len() && chars[i] != ']' {
15338 idx_str.push(chars[i]);
15339 i += 1;
15340 }
15341 if i < chars.len() {
15342 i += 1;
15343 }
15344 let idx_expr = if let Some(rest) = idx_str.strip_prefix('$') {
15345 Expr {
15346 kind: ExprKind::ScalarVar(rest.to_string()),
15347 line,
15348 }
15349 } else if let Ok(n) = idx_str.parse::<i64>() {
15350 Expr {
15351 kind: ExprKind::Integer(n),
15352 line,
15353 }
15354 } else {
15355 Expr {
15356 kind: ExprKind::String(idx_str),
15357 line,
15358 }
15359 };
15360 Expr {
15361 kind: ExprKind::ArrayElement {
15362 array: name,
15363 index: Box::new(idx_expr),
15364 },
15365 line,
15366 }
15367 } else {
15368 Expr {
15370 kind: ExprKind::ScalarVar(name),
15371 line,
15372 }
15373 };
15374
15375 base = self.interp_chain_subscripts(&chars, &mut i, base, line);
15379 parts.push(StringPart::Expr(base));
15380 } else if chars[i].is_ascii_digit() {
15381 if chars[i] == '0' {
15383 i += 1;
15384 if i < chars.len() && chars[i].is_ascii_digit() {
15385 return Err(self.syntax_err(
15386 "Numeric variables with more than one digit may not start with '0'",
15387 line,
15388 ));
15389 }
15390 parts.push(StringPart::ScalarVar("0".into()));
15391 } else {
15392 let start = i;
15393 while i < chars.len() && chars[i].is_ascii_digit() {
15394 i += 1;
15395 }
15396 parts.push(StringPart::ScalarVar(chars[start..i].iter().collect()));
15397 }
15398 } else {
15399 let c = chars[i];
15400 let probe = c.to_string();
15401 if VMHelper::is_special_scalar_name_for_get(&probe)
15402 || matches!(c, '\'' | '`')
15403 {
15404 i += 1;
15405 if i < chars.len() && chars[i] == '{' {
15407 i += 1; let mut key = String::new();
15409 let mut depth = 1;
15410 while i < chars.len() && depth > 0 {
15411 if chars[i] == '{' {
15412 depth += 1;
15413 } else if chars[i] == '}' {
15414 depth -= 1;
15415 if depth == 0 {
15416 break;
15417 }
15418 }
15419 key.push(chars[i]);
15420 i += 1;
15421 }
15422 if i < chars.len() {
15423 i += 1;
15424 } let key_expr = if let Some(rest) = key.strip_prefix('$') {
15426 Expr {
15427 kind: ExprKind::ScalarVar(rest.to_string()),
15428 line,
15429 }
15430 } else {
15431 Expr {
15432 kind: ExprKind::String(key),
15433 line,
15434 }
15435 };
15436 let mut base = Expr {
15437 kind: ExprKind::HashElement {
15438 hash: probe,
15439 key: Box::new(key_expr),
15440 },
15441 line,
15442 };
15443 base = self.interp_chain_subscripts(&chars, &mut i, base, line);
15444 parts.push(StringPart::Expr(base));
15445 } else {
15446 let mut base = Expr {
15448 kind: ExprKind::ScalarVar(probe),
15449 line,
15450 };
15451 base = self.interp_chain_subscripts(&chars, &mut i, base, line);
15452 if matches!(base.kind, ExprKind::ScalarVar(_)) {
15453 if let ExprKind::ScalarVar(name) = base.kind {
15455 self.no_interop_check_scalar_var_name(&name, line)?;
15456 parts.push(StringPart::ScalarVar(name));
15457 }
15458 } else {
15459 parts.push(StringPart::Expr(base));
15460 }
15461 }
15462 } else {
15463 literal.push('$');
15464 literal.push(c);
15465 i += 1;
15466 }
15467 }
15468 } else if chars[i] == '@' && i + 1 < chars.len() {
15469 let next = chars[i + 1];
15470 if next == '$' {
15472 if !literal.is_empty() {
15473 parts.push(StringPart::Literal(std::mem::take(&mut literal)));
15474 }
15475 i += 1; debug_assert_eq!(chars[i], '$');
15477 i += 1; while i < chars.len() && chars[i].is_whitespace() {
15479 i += 1;
15480 }
15481 if i >= chars.len() {
15482 return Err(self.syntax_err(
15483 "Expected variable or block after `@$` in double-quoted string",
15484 line,
15485 ));
15486 }
15487 let inner_expr = if chars[i] == '{' {
15488 i += 1;
15489 let start = i;
15490 let mut depth = 1usize;
15491 while i < chars.len() && depth > 0 {
15492 match chars[i] {
15493 '{' => depth += 1,
15494 '}' => {
15495 depth -= 1;
15496 if depth == 0 {
15497 break;
15498 }
15499 }
15500 _ => {}
15501 }
15502 i += 1;
15503 }
15504 if depth != 0 {
15505 return Err(self.syntax_err(
15506 "Unterminated `${ ... }` after `@` in double-quoted string",
15507 line,
15508 ));
15509 }
15510 let inner: String = chars[start..i].iter().collect();
15511 i += 1; parse_expression_from_str(inner.trim(), "-e")?
15513 } else {
15514 let mut name = String::new();
15515 if chars[i] == '^' {
15516 name.push('^');
15517 i += 1;
15518 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_')
15519 {
15520 name.push(chars[i]);
15521 i += 1;
15522 }
15523 } else {
15524 while i < chars.len()
15525 && (chars[i].is_alphanumeric()
15526 || chars[i] == '_'
15527 || chars[i] == ':')
15528 {
15529 name.push(chars[i]);
15530 i += 1;
15531 }
15532 while i + 1 < chars.len() && chars[i] == ':' && chars[i + 1] == ':' {
15533 name.push_str("::");
15534 i += 2;
15535 while i < chars.len()
15536 && (chars[i].is_alphanumeric() || chars[i] == '_')
15537 {
15538 name.push(chars[i]);
15539 i += 1;
15540 }
15541 }
15542 }
15543 if name.is_empty() {
15544 return Err(self.syntax_err(
15545 "Expected identifier after `@$` in double-quoted string",
15546 line,
15547 ));
15548 }
15549 Expr {
15550 kind: ExprKind::ScalarVar(name),
15551 line,
15552 }
15553 };
15554 parts.push(StringPart::Expr(Expr {
15555 kind: ExprKind::Deref {
15556 expr: Box::new(inner_expr),
15557 kind: Sigil::Array,
15558 },
15559 line,
15560 }));
15561 continue 'istr;
15562 }
15563 if next == '{' {
15564 if !literal.is_empty() {
15565 parts.push(StringPart::Literal(std::mem::take(&mut literal)));
15566 }
15567 i += 2; let start = i;
15569 let mut depth = 1usize;
15570 while i < chars.len() && depth > 0 {
15571 match chars[i] {
15572 '{' => depth += 1,
15573 '}' => {
15574 depth -= 1;
15575 if depth == 0 {
15576 break;
15577 }
15578 }
15579 _ => {}
15580 }
15581 i += 1;
15582 }
15583 if depth != 0 {
15584 return Err(
15585 self.syntax_err("Unterminated @{ ... } in double-quoted string", line)
15586 );
15587 }
15588 let inner: String = chars[start..i].iter().collect();
15589 i += 1; let inner_expr = parse_expression_from_str(inner.trim(), "-e")?;
15591 parts.push(StringPart::Expr(Expr {
15592 kind: ExprKind::Deref {
15593 expr: Box::new(inner_expr),
15594 kind: Sigil::Array,
15595 },
15596 line,
15597 }));
15598 continue 'istr;
15599 }
15600 if !(next.is_alphabetic() || next == '_' || next == '+' || next == '-') {
15601 literal.push(chars[i]);
15602 i += 1;
15603 } else {
15604 if !literal.is_empty() {
15605 parts.push(StringPart::Literal(std::mem::take(&mut literal)));
15606 }
15607 i += 1;
15608 let mut name = String::new();
15609 if i < chars.len() && (chars[i] == '+' || chars[i] == '-') {
15610 name.push(chars[i]);
15611 i += 1;
15612 } else {
15613 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
15614 name.push(chars[i]);
15615 i += 1;
15616 }
15617 while i + 1 < chars.len() && chars[i] == ':' && chars[i + 1] == ':' {
15618 name.push_str("::");
15619 i += 2;
15620 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_')
15621 {
15622 name.push(chars[i]);
15623 i += 1;
15624 }
15625 }
15626 }
15627 if i < chars.len() && chars[i] == '[' {
15628 i += 1;
15629 let start_inner = i;
15630 let mut depth = 1usize;
15631 while i < chars.len() && depth > 0 {
15632 match chars[i] {
15633 '[' => depth += 1,
15634 ']' => depth -= 1,
15635 _ => {}
15636 }
15637 if depth == 0 {
15638 let inner: String = chars[start_inner..i].iter().collect();
15639 i += 1; let indices = parse_slice_indices_from_str(inner.trim(), "-e")?;
15641 parts.push(StringPart::Expr(Expr {
15642 kind: ExprKind::ArraySlice {
15643 array: name.clone(),
15644 indices,
15645 },
15646 line,
15647 }));
15648 continue 'istr;
15649 }
15650 i += 1;
15651 }
15652 return Err(self.syntax_err(
15653 "Unterminated [ in array slice inside quoted string",
15654 line,
15655 ));
15656 }
15657 parts.push(StringPart::ArrayVar(name));
15658 }
15659 } else if chars[i] == '#'
15660 && i + 1 < chars.len()
15661 && chars[i + 1] == '{'
15662 && !crate::compat_mode()
15663 {
15664 if !literal.is_empty() {
15666 parts.push(StringPart::Literal(std::mem::take(&mut literal)));
15667 }
15668 i += 2; let mut inner = String::new();
15670 let mut depth = 1usize;
15671 while i < chars.len() && depth > 0 {
15672 match chars[i] {
15673 '{' => depth += 1,
15674 '}' => {
15675 depth -= 1;
15676 if depth == 0 {
15677 break;
15678 }
15679 }
15680 _ => {}
15681 }
15682 inner.push(chars[i]);
15683 i += 1;
15684 }
15685 if i < chars.len() {
15686 i += 1; }
15688 let expr = parse_block_from_str(inner.trim(), "-e", line)?;
15689 parts.push(StringPart::Expr(expr));
15690 } else {
15691 literal.push(chars[i]);
15692 i += 1;
15693 }
15694 }
15695 if !literal.is_empty() {
15696 parts.push(StringPart::Literal(literal));
15697 }
15698
15699 if parts.len() == 1 {
15700 if let StringPart::Literal(s) = &parts[0] {
15701 return Ok(Expr {
15702 kind: ExprKind::String(s.clone()),
15703 line,
15704 });
15705 }
15706 }
15707 if parts.is_empty() {
15708 return Ok(Expr {
15709 kind: ExprKind::String(String::new()),
15710 line,
15711 });
15712 }
15713
15714 Ok(Expr {
15715 kind: ExprKind::InterpolatedString(parts),
15716 line,
15717 })
15718 }
15719
15720 fn expr_to_overload_key(&self, e: &Expr) -> PerlResult<String> {
15721 match &e.kind {
15722 ExprKind::String(s) => Ok(s.clone()),
15723 _ => Err(self.syntax_err(
15724 "overload key must be a string literal (e.g. '\"\"' or '+')",
15725 e.line,
15726 )),
15727 }
15728 }
15729
15730 fn expr_to_overload_sub(&mut self, e: &Expr) -> PerlResult<String> {
15731 match &e.kind {
15732 ExprKind::String(s) => Ok(s.clone()),
15733 ExprKind::Integer(n) => Ok(n.to_string()),
15734 ExprKind::SubroutineRef(s) | ExprKind::SubroutineCodeRef(s) => Ok(s.clone()),
15735 ExprKind::CodeRef { params, body } => {
15739 let id = self.next_overload_anon_id;
15740 self.next_overload_anon_id = self.next_overload_anon_id.saturating_add(1);
15741 let name = format!("__overload_anon_{}", id);
15742 self.pending_synthetic_subs.push(Statement {
15743 label: None,
15744 kind: StmtKind::SubDecl {
15745 name: name.clone(),
15746 params: params.clone(),
15747 body: body.clone(),
15748 prototype: None,
15749 },
15750 line: e.line,
15751 });
15752 Ok(name)
15753 }
15754 _ => Err(self.syntax_err(
15755 "overload handler must be a string literal, number (e.g. fallback => 1), or \\&subname (method in current package)",
15756 e.line,
15757 )),
15758 }
15759 }
15760}
15761
15762fn merge_expr_list(parts: Vec<Expr>) -> Expr {
15763 if parts.len() == 1 {
15764 parts.into_iter().next().unwrap()
15765 } else {
15766 let line = parts.first().map(|e| e.line).unwrap_or(0);
15767 Expr {
15768 kind: ExprKind::List(parts),
15769 line,
15770 }
15771 }
15772}
15773
15774pub fn parse_expression_from_str(s: &str, file: &str) -> PerlResult<Expr> {
15776 let mut lexer = Lexer::new_with_file(s, file);
15777 let tokens = lexer.tokenize()?;
15778 let mut parser = Parser::new_with_file(tokens, file);
15779 let e = parser.parse_expression()?;
15780 if !parser.at_eof() {
15781 return Err(parser.syntax_err(
15782 "Extra tokens in embedded string expression",
15783 parser.peek_line(),
15784 ));
15785 }
15786 Ok(e)
15787}
15788
15789pub fn parse_block_from_str(s: &str, file: &str, line: usize) -> PerlResult<Expr> {
15791 let mut lexer = Lexer::new_with_file(s, file);
15792 let tokens = lexer.tokenize()?;
15793 let mut parser = Parser::new_with_file(tokens, file);
15794 let stmts = parser.parse_statements()?;
15795 let inner_line = stmts.first().map(|st| st.line).unwrap_or(line);
15796 let inner = Expr {
15797 kind: ExprKind::CodeRef {
15798 params: vec![],
15799 body: stmts,
15800 },
15801 line: inner_line,
15802 };
15803 Ok(Expr {
15804 kind: ExprKind::Do(Box::new(inner)),
15805 line,
15806 })
15807}
15808
15809pub fn parse_slice_indices_from_str(s: &str, file: &str) -> PerlResult<Vec<Expr>> {
15812 let mut lexer = Lexer::new_with_file(s, file);
15813 let tokens = lexer.tokenize()?;
15814 let mut parser = Parser::new_with_file(tokens, file);
15815 parser.parse_arg_list()
15816}
15817
15818pub fn parse_format_value_line(line: &str) -> PerlResult<Vec<Expr>> {
15819 let trimmed = line.trim();
15820 if trimmed.is_empty() {
15821 return Ok(vec![]);
15822 }
15823 let mut lexer = Lexer::new(trimmed);
15824 let tokens = lexer.tokenize()?;
15825 let mut parser = Parser::new(tokens);
15826 let mut exprs = Vec::new();
15827 loop {
15828 if parser.at_eof() {
15829 break;
15830 }
15831 exprs.push(parser.parse_assign_expr()?);
15833 if parser.eat(&Token::Comma) {
15834 continue;
15835 }
15836 if !parser.at_eof() {
15837 return Err(parser.syntax_err("Extra tokens in format value line", parser.peek_line()));
15838 }
15839 break;
15840 }
15841 Ok(exprs)
15842}
15843
15844#[cfg(test)]
15845mod tests {
15846 use super::*;
15847
15848 fn parse_ok(code: &str) -> Program {
15849 let mut lexer = Lexer::new(code);
15850 let tokens = lexer.tokenize().expect("tokenize");
15851 let mut parser = Parser::new(tokens);
15852 parser.parse_program().expect("parse")
15853 }
15854
15855 fn parse_err(code: &str) -> String {
15856 let mut lexer = Lexer::new(code);
15857 let tokens = match lexer.tokenize() {
15858 Ok(t) => t,
15859 Err(e) => return e.message,
15860 };
15861 let mut parser = Parser::new(tokens);
15862 parser.parse_program().unwrap_err().message
15863 }
15864
15865 #[test]
15866 fn parse_empty_program() {
15867 let p = parse_ok("");
15868 assert!(p.statements.is_empty());
15869 }
15870
15871 #[test]
15872 fn parse_semicolons_only() {
15873 let p = parse_ok(";;");
15874 assert!(p.statements.len() <= 3);
15875 }
15876
15877 #[test]
15878 fn parse_simple_scalar_assignment() {
15879 let p = parse_ok("$x = 1");
15880 assert_eq!(p.statements.len(), 1);
15881 }
15882
15883 #[test]
15884 fn parse_simple_array_assignment() {
15885 let p = parse_ok("@arr = (1, 2, 3)");
15886 assert_eq!(p.statements.len(), 1);
15887 }
15888
15889 #[test]
15890 fn parse_simple_hash_assignment() {
15891 let p = parse_ok("%h = (a => 1, b => 2)");
15892 assert_eq!(p.statements.len(), 1);
15893 }
15894
15895 #[test]
15896 fn parse_subroutine_decl() {
15897 let p = parse_ok("fn foo { 1 }");
15898 assert_eq!(p.statements.len(), 1);
15899 match &p.statements[0].kind {
15900 StmtKind::SubDecl { name, .. } => assert_eq!(name, "foo"),
15901 _ => panic!("expected SubDecl"),
15902 }
15903 }
15904
15905 #[test]
15906 fn parse_subroutine_with_prototype() {
15907 let p = parse_ok("fn foo ($$) { 1 }");
15908 assert_eq!(p.statements.len(), 1);
15909 match &p.statements[0].kind {
15910 StmtKind::SubDecl { prototype, .. } => {
15911 assert!(prototype.is_some());
15912 }
15913 _ => panic!("expected SubDecl"),
15914 }
15915 }
15916
15917 #[test]
15918 fn parse_anonymous_fn() {
15919 let p = parse_ok("my $f = fn { 1 }");
15920 assert_eq!(p.statements.len(), 1);
15921 }
15922
15923 #[test]
15924 fn parse_if_statement() {
15925 let p = parse_ok("if (1) { 2 }");
15926 assert_eq!(p.statements.len(), 1);
15927 matches!(&p.statements[0].kind, StmtKind::If { .. });
15928 }
15929
15930 #[test]
15931 fn parse_if_elsif_else() {
15932 let p = parse_ok("if (0) { 1 } elsif (1) { 2 } else { 3 }");
15933 assert_eq!(p.statements.len(), 1);
15934 }
15935
15936 #[test]
15937 fn parse_unless_statement() {
15938 let p = parse_ok("unless (0) { 1 }");
15939 assert_eq!(p.statements.len(), 1);
15940 }
15941
15942 #[test]
15943 fn parse_while_loop() {
15944 let p = parse_ok("while ($x) { $x-- }");
15945 assert_eq!(p.statements.len(), 1);
15946 }
15947
15948 #[test]
15949 fn parse_until_loop() {
15950 let p = parse_ok("until ($x) { $x++ }");
15951 assert_eq!(p.statements.len(), 1);
15952 }
15953
15954 #[test]
15955 fn parse_for_c_style() {
15956 let p = parse_ok("for (my $i=0; $i<10; $i++) { 1 }");
15957 assert_eq!(p.statements.len(), 1);
15958 }
15959
15960 #[test]
15961 fn parse_foreach_loop() {
15962 let p = parse_ok("foreach my $x (@arr) { 1 }");
15963 assert_eq!(p.statements.len(), 1);
15964 }
15965
15966 #[test]
15967 fn parse_loop_with_label() {
15968 let p = parse_ok("OUTER: for my $i (1..10) { last OUTER }");
15969 assert_eq!(p.statements.len(), 1);
15970 assert_eq!(p.statements[0].label.as_deref(), Some("OUTER"));
15971 }
15972
15973 #[test]
15974 fn parse_begin_block() {
15975 let p = parse_ok("BEGIN { 1 }");
15976 assert_eq!(p.statements.len(), 1);
15977 matches!(&p.statements[0].kind, StmtKind::Begin(_));
15978 }
15979
15980 #[test]
15981 fn parse_end_block() {
15982 let p = parse_ok("END { 1 }");
15983 assert_eq!(p.statements.len(), 1);
15984 matches!(&p.statements[0].kind, StmtKind::End(_));
15985 }
15986
15987 #[test]
15988 fn parse_package_statement() {
15989 let p = parse_ok("package Foo::Bar");
15990 assert_eq!(p.statements.len(), 1);
15991 match &p.statements[0].kind {
15992 StmtKind::Package { name } => assert_eq!(name, "Foo::Bar"),
15993 _ => panic!("expected Package"),
15994 }
15995 }
15996
15997 #[test]
15998 fn parse_use_statement() {
15999 let p = parse_ok("use strict");
16000 assert_eq!(p.statements.len(), 1);
16001 }
16002
16003 #[test]
16004 fn parse_no_statement() {
16005 let p = parse_ok("no warnings");
16006 assert_eq!(p.statements.len(), 1);
16007 }
16008
16009 #[test]
16010 fn parse_require_bareword() {
16011 let p = parse_ok("require Foo::Bar");
16012 assert_eq!(p.statements.len(), 1);
16013 }
16014
16015 #[test]
16016 fn parse_require_string() {
16017 let p = parse_ok(r#"require "foo.pl""#);
16018 assert_eq!(p.statements.len(), 1);
16019 }
16020
16021 #[test]
16022 fn parse_eval_block() {
16023 let p = parse_ok("eval { 1 }");
16024 assert_eq!(p.statements.len(), 1);
16025 }
16026
16027 #[test]
16028 fn parse_eval_string() {
16029 let p = parse_ok(r#"eval "1 + 2""#);
16030 assert_eq!(p.statements.len(), 1);
16031 }
16032
16033 #[test]
16034 fn parse_qw_word_list() {
16035 let p = parse_ok("my @a = qw(foo bar baz)");
16036 assert_eq!(p.statements.len(), 1);
16037 }
16038
16039 #[test]
16040 fn parse_q_string() {
16041 let p = parse_ok("my $s = q{hello}");
16042 assert_eq!(p.statements.len(), 1);
16043 }
16044
16045 #[test]
16046 fn parse_qq_string() {
16047 let p = parse_ok(r#"my $s = qq(hello $x)"#);
16048 assert_eq!(p.statements.len(), 1);
16049 }
16050
16051 #[test]
16052 fn parse_regex_match() {
16053 let p = parse_ok(r#"$x =~ /foo/"#);
16054 assert_eq!(p.statements.len(), 1);
16055 }
16056
16057 #[test]
16058 fn parse_regex_substitution() {
16059 let p = parse_ok(r#"$x =~ s/foo/bar/g"#);
16060 assert_eq!(p.statements.len(), 1);
16061 }
16062
16063 #[test]
16064 fn parse_transliterate() {
16065 let p = parse_ok(r#"$x =~ tr/a-z/A-Z/"#);
16066 assert_eq!(p.statements.len(), 1);
16067 }
16068
16069 #[test]
16070 fn parse_ternary_operator() {
16071 let p = parse_ok("my $x = $a ? 1 : 2");
16072 assert_eq!(p.statements.len(), 1);
16073 }
16074
16075 #[test]
16076 fn parse_arrow_method_call() {
16077 let p = parse_ok("$obj->method()");
16078 assert_eq!(p.statements.len(), 1);
16079 }
16080
16081 #[test]
16082 fn parse_arrow_deref_hash() {
16083 let p = parse_ok("$r->{key}");
16084 assert_eq!(p.statements.len(), 1);
16085 }
16086
16087 #[test]
16088 fn parse_arrow_deref_array() {
16089 let p = parse_ok("$r->[0]");
16090 assert_eq!(p.statements.len(), 1);
16091 }
16092
16093 #[test]
16094 fn parse_chained_arrow_deref() {
16095 let p = parse_ok("$r->{a}[0]{b}");
16096 assert_eq!(p.statements.len(), 1);
16097 }
16098
16099 #[test]
16100 fn parse_my_multiple_vars() {
16101 let p = parse_ok("my ($a, $b, $c) = (1, 2, 3)");
16102 assert_eq!(p.statements.len(), 1);
16103 }
16104
16105 #[test]
16106 fn parse_our_scalar() {
16107 let p = parse_ok("our $VERSION = '1.0'");
16108 assert_eq!(p.statements.len(), 1);
16109 }
16110
16111 #[test]
16112 fn parse_local_scalar() {
16113 let p = parse_ok("local $/ = undef");
16114 assert_eq!(p.statements.len(), 1);
16115 }
16116
16117 #[test]
16118 fn parse_state_variable() {
16119 let p = parse_ok("fn Test::counter { state $n = 0; $n++ }");
16120 assert_eq!(p.statements.len(), 1);
16121 }
16122
16123 #[test]
16124 fn parse_postfix_if() {
16125 let p = parse_ok("print 1 if $x");
16126 assert_eq!(p.statements.len(), 1);
16127 }
16128
16129 #[test]
16130 fn parse_postfix_unless() {
16131 let p = parse_ok("die 'error' unless $ok");
16132 assert_eq!(p.statements.len(), 1);
16133 }
16134
16135 #[test]
16136 fn parse_postfix_while() {
16137 let p = parse_ok("$x++ while $x < 10");
16138 assert_eq!(p.statements.len(), 1);
16139 }
16140
16141 #[test]
16142 fn parse_postfix_for() {
16143 let p = parse_ok("print for @arr");
16144 assert_eq!(p.statements.len(), 1);
16145 }
16146
16147 #[test]
16148 fn parse_last_next_redo() {
16149 let p = parse_ok("for (@a) { next if $_ < 0; last if $_ > 10 }");
16150 assert_eq!(p.statements.len(), 1);
16151 }
16152
16153 #[test]
16154 fn parse_return_statement() {
16155 let p = parse_ok("fn foo { return 42 }");
16156 assert_eq!(p.statements.len(), 1);
16157 }
16158
16159 #[test]
16160 fn parse_wantarray() {
16161 let p = parse_ok("fn foo { wantarray ? @a : $a }");
16162 assert_eq!(p.statements.len(), 1);
16163 }
16164
16165 #[test]
16166 fn parse_caller_builtin() {
16167 let p = parse_ok("my @c = caller");
16168 assert_eq!(p.statements.len(), 1);
16169 }
16170
16171 #[test]
16172 fn parse_ref_to_array() {
16173 let p = parse_ok("my $r = \\@arr");
16174 assert_eq!(p.statements.len(), 1);
16175 }
16176
16177 #[test]
16178 fn parse_ref_to_hash() {
16179 let p = parse_ok("my $r = \\%hash");
16180 assert_eq!(p.statements.len(), 1);
16181 }
16182
16183 #[test]
16184 fn parse_ref_to_scalar() {
16185 let p = parse_ok("my $r = \\$x");
16186 assert_eq!(p.statements.len(), 1);
16187 }
16188
16189 #[test]
16190 fn parse_deref_scalar() {
16191 let p = parse_ok("my $v = $$r");
16192 assert_eq!(p.statements.len(), 1);
16193 }
16194
16195 #[test]
16196 fn parse_deref_array() {
16197 let p = parse_ok("my @a = @$r");
16198 assert_eq!(p.statements.len(), 1);
16199 }
16200
16201 #[test]
16202 fn parse_deref_hash() {
16203 let p = parse_ok("my %h = %$r");
16204 assert_eq!(p.statements.len(), 1);
16205 }
16206
16207 #[test]
16208 fn parse_blessed_ref() {
16209 let p = parse_ok("bless $r, 'Foo'");
16210 assert_eq!(p.statements.len(), 1);
16211 }
16212
16213 #[test]
16214 fn parse_heredoc_basic() {
16215 let p = parse_ok("my $s = <<END;\nfoo\nEND");
16216 assert_eq!(p.statements.len(), 1);
16217 }
16218
16219 #[test]
16220 fn parse_heredoc_quoted() {
16221 let p = parse_ok("my $s = <<'END';\nfoo\nEND");
16222 assert_eq!(p.statements.len(), 1);
16223 }
16224
16225 #[test]
16226 fn parse_do_block() {
16227 let p = parse_ok("my $x = do { 1 + 2 }");
16228 assert_eq!(p.statements.len(), 1);
16229 }
16230
16231 #[test]
16232 fn parse_do_file() {
16233 let p = parse_ok(r#"do "foo.pl""#);
16234 assert_eq!(p.statements.len(), 1);
16235 }
16236
16237 #[test]
16238 fn parse_map_expression() {
16239 let p = parse_ok("my @b = map { $_ * 2 } @a");
16240 assert_eq!(p.statements.len(), 1);
16241 }
16242
16243 #[test]
16244 fn parse_grep_expression() {
16245 let p = parse_ok("my @b = grep { $_ > 0 } @a");
16246 assert_eq!(p.statements.len(), 1);
16247 }
16248
16249 #[test]
16250 fn parse_sort_expression() {
16251 let p = parse_ok("my @b = sort { $a <=> $b } @a");
16252 assert_eq!(p.statements.len(), 1);
16253 }
16254
16255 #[test]
16256 fn parse_pipe_forward() {
16257 let p = parse_ok("@a |> map { $_ * 2 }");
16258 assert_eq!(p.statements.len(), 1);
16259 }
16260
16261 #[test]
16262 fn parse_expression_from_str_simple() {
16263 let e = parse_expression_from_str("$x + 1", "-e").unwrap();
16264 assert!(matches!(e.kind, ExprKind::BinOp { .. }));
16265 }
16266
16267 #[test]
16268 fn parse_expression_from_str_extra_tokens_error() {
16269 let err = parse_expression_from_str("$x; $y", "-e").unwrap_err();
16270 assert!(err.message.contains("Extra tokens"));
16271 }
16272
16273 #[test]
16274 fn parse_slice_indices_from_str_basic() {
16275 let indices = parse_slice_indices_from_str("0, 1, 2", "-e").unwrap();
16276 assert_eq!(indices.len(), 3);
16277 }
16278
16279 #[test]
16280 fn parse_format_value_line_empty() {
16281 let exprs = parse_format_value_line("").unwrap();
16282 assert!(exprs.is_empty());
16283 }
16284
16285 #[test]
16286 fn parse_format_value_line_single() {
16287 let exprs = parse_format_value_line("$x").unwrap();
16288 assert_eq!(exprs.len(), 1);
16289 }
16290
16291 #[test]
16292 fn parse_format_value_line_multiple() {
16293 let exprs = parse_format_value_line("$a, $b, $c").unwrap();
16294 assert_eq!(exprs.len(), 3);
16295 }
16296
16297 #[test]
16298 fn parse_unclosed_brace_error() {
16299 let err = parse_err("fn foo {");
16300 assert!(!err.is_empty());
16301 }
16302
16303 #[test]
16304 fn parse_unclosed_paren_error() {
16305 let err = parse_err("print (1, 2");
16306 assert!(!err.is_empty());
16307 }
16308
16309 #[test]
16310 fn parse_invalid_statement_error() {
16311 let err = parse_err("???");
16312 assert!(!err.is_empty());
16313 }
16314
16315 #[test]
16316 fn merge_expr_list_single() {
16317 let e = Expr {
16318 kind: ExprKind::Integer(1),
16319 line: 1,
16320 };
16321 let merged = merge_expr_list(vec![e.clone()]);
16322 matches!(merged.kind, ExprKind::Integer(1));
16323 }
16324
16325 #[test]
16326 fn merge_expr_list_multiple() {
16327 let e1 = Expr {
16328 kind: ExprKind::Integer(1),
16329 line: 1,
16330 };
16331 let e2 = Expr {
16332 kind: ExprKind::Integer(2),
16333 line: 1,
16334 };
16335 let merged = merge_expr_list(vec![e1, e2]);
16336 matches!(merged.kind, ExprKind::List(_));
16337 }
16338}