1use crate::ast::*;
2use crate::error::{ErrorKind, PerlError, PerlResult};
3use crate::interpreter::Interpreter;
4use crate::lexer::{Lexer, LITERAL_DOLLAR_IN_DQUOTE};
5use crate::token::Token;
6
7fn postfix_lbracket_is_arrow_container(expr: &Expr) -> bool {
10 matches!(
11 expr.kind,
12 ExprKind::ArrayElement { .. }
13 | ExprKind::HashElement { .. }
14 | ExprKind::ArrowDeref { .. }
15 | ExprKind::Deref {
16 kind: Sigil::Scalar,
17 ..
18 }
19 )
20}
21
22fn destructure_stmt_from_var_decls(keyword: &str, decls: Vec<VarDecl>, line: usize) -> Statement {
23 let kind = match keyword {
24 "my" => StmtKind::My(decls),
25 "mysync" => StmtKind::MySync(decls),
26 "our" => StmtKind::Our(decls),
27 "local" => StmtKind::Local(decls),
28 "state" => StmtKind::State(decls),
29 _ => unreachable!("parse_my_our_local keyword"),
30 };
31 Statement {
32 label: None,
33 kind,
34 line,
35 }
36}
37
38fn destructure_stmt_die_string(line: usize, msg: &str) -> Statement {
39 Statement {
40 label: None,
41 kind: StmtKind::Expression(Expr {
42 kind: ExprKind::Die(vec![Expr {
43 kind: ExprKind::String(msg.to_string()),
44 line,
45 }]),
46 line,
47 }),
48 line,
49 }
50}
51
52fn destructure_stmt_unless_die(line: usize, cond: Expr, msg: &str) -> Statement {
53 Statement {
54 label: None,
55 kind: StmtKind::Unless {
56 condition: cond,
57 body: vec![destructure_stmt_die_string(line, msg)],
58 else_block: None,
59 },
60 line,
61 }
62}
63
64fn destructure_expr_scalar_tmp(name: &str, line: usize) -> Expr {
65 Expr {
66 kind: ExprKind::ScalarVar(name.to_string()),
67 line,
68 }
69}
70
71fn destructure_expr_array_len(tmp: &str, line: usize) -> Expr {
72 Expr {
73 kind: ExprKind::Deref {
74 expr: Box::new(destructure_expr_scalar_tmp(tmp, line)),
75 kind: Sigil::Array,
76 },
77 line,
78 }
79}
80
81pub struct Parser {
82 tokens: Vec<(Token, usize)>,
83 pos: usize,
84 next_rate_limit_slot: u32,
86 suppress_indirect_paren_call: u32,
89 pipe_rhs_depth: u32,
95 no_pipe_forward_depth: u32,
103 suppress_scalar_hash_brace: u32,
106 next_desugar_tmp: u32,
108 error_file: String,
110 declared_subs: std::collections::HashSet<String>,
112 suppress_parenless_call: u32,
116 suppress_slash_as_div: u32,
119 pub suppress_m_regex: u32,
122 suppress_colon_range: u32,
126 thread_last_mode: bool,
129 pub parsing_module: bool,
132}
133
134impl Parser {
135 pub fn new(tokens: Vec<(Token, usize)>) -> Self {
136 Self::new_with_file(tokens, "-e")
137 }
138
139 pub fn new_with_file(tokens: Vec<(Token, usize)>, file: impl Into<String>) -> Self {
140 Self {
141 tokens,
142 pos: 0,
143 next_rate_limit_slot: 0,
144 suppress_indirect_paren_call: 0,
145 pipe_rhs_depth: 0,
146 no_pipe_forward_depth: 0,
147 suppress_scalar_hash_brace: 0,
148 next_desugar_tmp: 0,
149 error_file: file.into(),
150 declared_subs: std::collections::HashSet::new(),
151 suppress_parenless_call: 0,
152 suppress_slash_as_div: 0,
153 suppress_m_regex: 0,
154 suppress_colon_range: 0,
155 thread_last_mode: false,
156 parsing_module: false,
157 }
158 }
159
160 fn alloc_desugar_tmp(&mut self) -> u32 {
161 let n = self.next_desugar_tmp;
162 self.next_desugar_tmp = self.next_desugar_tmp.saturating_add(1);
163 n
164 }
165
166 #[inline]
170 fn in_pipe_rhs(&self) -> bool {
171 self.pipe_rhs_depth > 0
172 }
173
174 fn pipe_supplies_slurped_list_operand(&self) -> bool {
177 self.in_pipe_rhs()
178 && (matches!(
179 self.peek(),
180 Token::Semicolon
181 | Token::RBrace
182 | Token::RParen
183 | Token::Eof
184 | Token::Comma
185 | Token::PipeForward
186 ) || self.peek_line() > self.prev_line())
187 }
188
189 #[inline]
194 fn pipe_placeholder_list(&self, line: usize) -> Expr {
195 Expr {
196 kind: ExprKind::List(vec![]),
197 line,
198 }
199 }
200
201 fn is_block_then_list_pipe_builtin(name: &str) -> bool {
206 matches!(
207 name,
208 "pfirst"
209 | "pany"
210 | "any"
211 | "all"
212 | "none"
213 | "first"
214 | "take_while"
215 | "drop_while"
216 | "skip_while"
217 | "reject"
218 | "tap"
219 | "peek"
220 | "group_by"
221 | "chunk_by"
222 | "partition"
223 | "min_by"
224 | "max_by"
225 | "zip_with"
226 | "count_by"
227 )
228 }
229
230 fn lift_bareword_to_topic_call(expr: Expr) -> Expr {
241 let line = expr.line;
242 let topic = || Expr {
243 kind: ExprKind::ScalarVar("_".into()),
244 line,
245 };
246 match expr.kind {
247 ExprKind::Bareword(ref name) => Expr {
248 kind: ExprKind::FuncCall {
249 name: name.clone(),
250 args: vec![topic()],
251 },
252 line,
253 },
254 ExprKind::Unlink(ref args) if args.is_empty() => Expr {
256 kind: ExprKind::Unlink(vec![topic()]),
257 line,
258 },
259 ExprKind::Chmod(ref args) if args.is_empty() => Expr {
260 kind: ExprKind::Chmod(vec![topic()]),
261 line,
262 },
263 ExprKind::Stat(_) => expr,
265 ExprKind::Lstat(_) => expr,
266 ExprKind::Readlink(_) => expr,
267 ExprKind::Rev(ref inner) => {
269 if matches!(inner.kind, ExprKind::List(ref v) if v.is_empty()) {
270 Expr {
271 kind: ExprKind::Rev(Box::new(topic())),
272 line,
273 }
274 } else {
275 expr
276 }
277 }
278 _ => expr,
279 }
280 }
281
282 fn parse_assign_expr_stop_at_pipe(&mut self) -> PerlResult<Expr> {
290 self.no_pipe_forward_depth = self.no_pipe_forward_depth.saturating_add(1);
291 let r = self.parse_assign_expr();
292 self.no_pipe_forward_depth = self.no_pipe_forward_depth.saturating_sub(1);
293 r
294 }
295
296 fn syntax_err(&self, message: impl Into<String>, line: usize) -> PerlError {
297 PerlError::new(ErrorKind::Syntax, message, line, self.error_file.clone())
298 }
299
300 fn alloc_rate_limit_slot(&mut self) -> u32 {
301 let s = self.next_rate_limit_slot;
302 self.next_rate_limit_slot = self.next_rate_limit_slot.saturating_add(1);
303 s
304 }
305
306 fn peek(&self) -> &Token {
307 self.tokens
308 .get(self.pos)
309 .map(|(t, _)| t)
310 .unwrap_or(&Token::Eof)
311 }
312
313 fn peek_line(&self) -> usize {
314 self.tokens.get(self.pos).map(|(_, l)| *l).unwrap_or(0)
315 }
316
317 fn peek_at(&self, offset: usize) -> &Token {
318 self.tokens
319 .get(self.pos + offset)
320 .map(|(t, _)| t)
321 .unwrap_or(&Token::Eof)
322 }
323
324 fn advance(&mut self) -> (Token, usize) {
325 let tok = self
326 .tokens
327 .get(self.pos)
328 .cloned()
329 .unwrap_or((Token::Eof, 0));
330 self.pos += 1;
331 tok
332 }
333
334 fn prev_line(&self) -> usize {
336 if self.pos > 0 {
337 self.tokens.get(self.pos - 1).map(|(_, l)| *l).unwrap_or(0)
338 } else {
339 0
340 }
341 }
342
343 fn looks_like_hashref(&self) -> bool {
352 debug_assert!(matches!(self.peek(), Token::LBrace));
353 let tok1 = self.peek_at(1);
354 let tok2 = self.peek_at(2);
355 match tok1 {
356 Token::RBrace => true,
357 Token::Ident(_)
358 | Token::SingleString(_)
359 | Token::DoubleString(_)
360 | Token::ScalarVar(_)
361 | Token::Integer(_) => matches!(tok2, Token::FatArrow),
362 Token::HashVar(_) => matches!(tok2, Token::RBrace | Token::Comma),
363 _ => false,
364 }
365 }
366
367 fn expect(&mut self, expected: &Token) -> PerlResult<usize> {
368 let (tok, line) = self.advance();
369 if std::mem::discriminant(&tok) == std::mem::discriminant(expected) {
370 Ok(line)
371 } else {
372 Err(self.syntax_err(format!("Expected {:?}, got {:?}", expected, tok), line))
373 }
374 }
375
376 fn eat(&mut self, expected: &Token) -> bool {
377 if std::mem::discriminant(self.peek()) == std::mem::discriminant(expected) {
378 self.advance();
379 true
380 } else {
381 false
382 }
383 }
384
385 fn at_eof(&self) -> bool {
386 matches!(self.peek(), Token::Eof)
387 }
388
389 fn filetest_allows_implicit_topic(tok: &Token) -> bool {
391 matches!(
392 tok,
393 Token::RParen
394 | Token::Semicolon
395 | Token::Comma
396 | Token::RBrace
397 | Token::Eof
398 | Token::LogAnd
399 | Token::LogOr
400 | Token::LogAndWord
401 | Token::LogOrWord
402 | Token::PipeForward
403 )
404 }
405
406 fn next_is_new_stmt_keyword(&self, stmt_line: usize) -> bool {
410 if crate::compat_mode() {
412 return false;
413 }
414 if self.peek_line() == stmt_line {
415 return false;
416 }
417 matches!(
418 self.peek(),
419 Token::Ident(ref kw) if matches!(kw.as_str(),
420 "use" | "no" | "my" | "our" | "local" | "sub" | "struct" | "enum"
421 | "if" | "unless" | "while" | "until" | "for" | "foreach"
422 | "return" | "last" | "next" | "redo" | "package" | "require"
423 | "BEGIN" | "END" | "UNITCHECK" | "frozen" | "const" | "typed"
424 )
425 )
426 }
427
428 fn next_is_new_statement_start(&self, stmt_line: usize) -> bool {
432 if crate::compat_mode() {
433 return false;
434 }
435 if self.peek_line() == stmt_line {
436 return false;
437 }
438 matches!(
439 self.peek(),
440 Token::ScalarVar(_)
441 | Token::DerefScalarVar(_)
442 | Token::ArrayVar(_)
443 | Token::HashVar(_)
444 | Token::LBrace
445 ) || self.next_is_new_stmt_keyword(stmt_line)
446 }
447
448 pub fn parse_program(&mut self) -> PerlResult<Program> {
451 let statements = self.parse_statements()?;
452 Ok(Program { statements })
453 }
454
455 pub fn parse_statements(&mut self) -> PerlResult<Vec<Statement>> {
457 let mut statements = Vec::new();
458 while !self.at_eof() {
459 if matches!(self.peek(), Token::Semicolon) {
460 let line = self.peek_line();
461 self.advance();
462 statements.push(Statement {
463 label: None,
464 kind: StmtKind::Empty,
465 line,
466 });
467 continue;
468 }
469 statements.push(self.parse_statement()?);
470 }
471 Ok(statements)
472 }
473
474 fn parse_statement(&mut self) -> PerlResult<Statement> {
477 let line = self.peek_line();
478
479 let label = match self.peek().clone() {
482 Token::Ident(_) => {
483 if matches!(self.peek_at(1), Token::Colon)
484 && !matches!(self.peek_at(2), Token::Colon)
485 {
486 let (tok, _) = self.advance();
487 let l = match tok {
488 Token::Ident(l) => l,
489 _ => unreachable!(),
490 };
491 self.advance(); Some(l)
493 } else {
494 None
495 }
496 }
497 _ => None,
498 };
499
500 let mut stmt = match self.peek().clone() {
501 Token::FormatDecl { .. } => {
502 let tok_line = self.peek_line();
503 let (tok, _) = self.advance();
504 match tok {
505 Token::FormatDecl { name, lines } => Statement {
506 label: label.clone(),
507 kind: StmtKind::FormatDecl { name, lines },
508 line: tok_line,
509 },
510 _ => unreachable!(),
511 }
512 }
513 Token::Ident(ref kw) => match kw.as_str() {
514 "if" => self.parse_if()?,
515 "unless" => self.parse_unless()?,
516 "while" => {
517 let mut s = self.parse_while()?;
518 if let StmtKind::While {
519 label: ref mut lbl, ..
520 } = s.kind
521 {
522 *lbl = label.clone();
523 }
524 s
525 }
526 "until" => {
527 let mut s = self.parse_until()?;
528 if let StmtKind::Until {
529 label: ref mut lbl, ..
530 } = s.kind
531 {
532 *lbl = label.clone();
533 }
534 s
535 }
536 "for" => {
537 let mut s = self.parse_for_or_foreach()?;
538 match s.kind {
539 StmtKind::For {
540 label: ref mut lbl, ..
541 }
542 | StmtKind::Foreach {
543 label: ref mut lbl, ..
544 } => *lbl = label.clone(),
545 _ => {}
546 }
547 s
548 }
549 "foreach" => {
550 let mut s = self.parse_foreach()?;
551 if let StmtKind::Foreach {
552 label: ref mut lbl, ..
553 } = s.kind
554 {
555 *lbl = label.clone();
556 }
557 s
558 }
559 "sub" => {
560 if crate::no_interop_mode() {
561 return Err(self.syntax_err(
562 "stryke uses `fn` instead of `sub` (--no-interop is active)",
563 self.peek_line(),
564 ));
565 }
566 self.parse_sub_decl(true)?
567 }
568 "fn" => self.parse_sub_decl(false)?,
569 "struct" => {
570 if crate::compat_mode() {
571 return Err(self.syntax_err(
572 "`struct` is a stryke extension (disabled by --compat)",
573 self.peek_line(),
574 ));
575 }
576 self.parse_struct_decl()?
577 }
578 "enum" => {
579 if crate::compat_mode() {
580 return Err(self.syntax_err(
581 "`enum` is a stryke extension (disabled by --compat)",
582 self.peek_line(),
583 ));
584 }
585 self.parse_enum_decl()?
586 }
587 "class" => {
588 if crate::compat_mode() {
589 return Err(self.syntax_err(
591 "Perl 5.38 `class` syntax not yet implemented in --compat mode",
592 self.peek_line(),
593 ));
594 }
595 self.parse_class_decl(false, false)?
596 }
597 "abstract" => {
598 self.advance(); if !matches!(self.peek(), Token::Ident(ref s) if s == "class") {
600 return Err(self.syntax_err(
601 "`abstract` must be followed by `class`",
602 self.peek_line(),
603 ));
604 }
605 self.parse_class_decl(true, false)?
606 }
607 "final" => {
608 self.advance(); if !matches!(self.peek(), Token::Ident(ref s) if s == "class") {
610 return Err(self
611 .syntax_err("`final` must be followed by `class`", self.peek_line()));
612 }
613 self.parse_class_decl(false, true)?
614 }
615 "trait" => {
616 if crate::compat_mode() {
617 return Err(self.syntax_err(
618 "`trait` is a stryke extension (disabled by --compat)",
619 self.peek_line(),
620 ));
621 }
622 self.parse_trait_decl()?
623 }
624 "my" => self.parse_my_our_local("my", false)?,
625 "state" => self.parse_my_our_local("state", false)?,
626 "mysync" => {
627 if crate::compat_mode() {
628 return Err(self.syntax_err(
629 "`mysync` is a stryke extension (disabled by --compat)",
630 self.peek_line(),
631 ));
632 }
633 self.parse_my_our_local("mysync", false)?
634 }
635 "frozen" | "const" => {
636 let leading = kw.as_str().to_string();
637 if crate::compat_mode() {
638 return Err(self.syntax_err(
639 format!("`{leading}` is a stryke extension (disabled by --compat)"),
640 self.peek_line(),
641 ));
642 }
643 self.advance(); if let Token::Ident(ref kw) = self.peek().clone() {
649 if kw == "my" {
650 let mut stmt = self.parse_my_our_local("my", false)?;
651 if let StmtKind::My(ref mut decls) = stmt.kind {
652 for decl in decls.iter_mut() {
653 decl.frozen = true;
654 }
655 }
656 stmt
657 } else {
658 return Err(self.syntax_err(
659 format!("Expected 'my' after '{leading}'"),
660 self.peek_line(),
661 ));
662 }
663 } else {
664 return Err(self.syntax_err(
665 format!("Expected 'my' after '{leading}'"),
666 self.peek_line(),
667 ));
668 }
669 }
670 "typed" => {
671 if crate::compat_mode() {
672 return Err(self.syntax_err(
673 "`typed` is a stryke extension (disabled by --compat)",
674 self.peek_line(),
675 ));
676 }
677 self.advance();
678 if let Token::Ident(ref kw) = self.peek().clone() {
679 if kw == "my" {
680 self.parse_my_our_local("my", true)?
681 } else {
682 return Err(
683 self.syntax_err("Expected 'my' after 'typed'", self.peek_line())
684 );
685 }
686 } else {
687 return Err(
688 self.syntax_err("Expected 'my' after 'typed'", self.peek_line())
689 );
690 }
691 }
692 "our" => self.parse_my_our_local("our", false)?,
693 "local" => self.parse_my_our_local("local", false)?,
694 "package" => self.parse_package()?,
695 "use" => self.parse_use()?,
696 "no" => self.parse_no()?,
697 "return" => self.parse_return()?,
698 "last" => {
699 self.advance();
700 let lbl = if let Token::Ident(ref s) = self.peek() {
701 if s.chars().all(|c| c.is_uppercase() || c == '_') {
702 let (Token::Ident(l), _) = self.advance() else {
703 unreachable!()
704 };
705 Some(l)
706 } else {
707 None
708 }
709 } else {
710 None
711 };
712 let stmt = Statement {
713 label: None,
714 kind: StmtKind::Last(lbl.or(label.clone())),
715 line,
716 };
717 self.parse_stmt_postfix_modifier(stmt)?
718 }
719 "next" => {
720 self.advance();
721 let lbl = if let Token::Ident(ref s) = self.peek() {
722 if s.chars().all(|c| c.is_uppercase() || c == '_') {
723 let (Token::Ident(l), _) = self.advance() else {
724 unreachable!()
725 };
726 Some(l)
727 } else {
728 None
729 }
730 } else {
731 None
732 };
733 let stmt = Statement {
734 label: None,
735 kind: StmtKind::Next(lbl.or(label.clone())),
736 line,
737 };
738 self.parse_stmt_postfix_modifier(stmt)?
739 }
740 "redo" => {
741 self.advance();
742 self.eat(&Token::Semicolon);
743 Statement {
744 label: None,
745 kind: StmtKind::Redo(label.clone()),
746 line,
747 }
748 }
749 "BEGIN" => {
750 self.advance();
751 let block = self.parse_block()?;
752 Statement {
753 label: None,
754 kind: StmtKind::Begin(block),
755 line,
756 }
757 }
758 "END" => {
759 self.advance();
760 let block = self.parse_block()?;
761 Statement {
762 label: None,
763 kind: StmtKind::End(block),
764 line,
765 }
766 }
767 "UNITCHECK" => {
768 self.advance();
769 let block = self.parse_block()?;
770 Statement {
771 label: None,
772 kind: StmtKind::UnitCheck(block),
773 line,
774 }
775 }
776 "CHECK" => {
777 self.advance();
778 let block = self.parse_block()?;
779 Statement {
780 label: None,
781 kind: StmtKind::Check(block),
782 line,
783 }
784 }
785 "INIT" => {
786 self.advance();
787 let block = self.parse_block()?;
788 Statement {
789 label: None,
790 kind: StmtKind::Init(block),
791 line,
792 }
793 }
794 "goto" => {
795 self.advance();
796 let target = self.parse_expression()?;
797 let stmt = Statement {
798 label: None,
799 kind: StmtKind::Goto {
800 target: Box::new(target),
801 },
802 line,
803 };
804 self.parse_stmt_postfix_modifier(stmt)?
806 }
807 "continue" => {
808 self.advance();
809 let block = self.parse_block()?;
810 Statement {
811 label: None,
812 kind: StmtKind::Continue(block),
813 line,
814 }
815 }
816 "try" => self.parse_try_catch()?,
817 "defer" => self.parse_defer_stmt()?,
818 "tie" => self.parse_tie_stmt()?,
819 "given" => self.parse_given()?,
820 "when" => self.parse_when_stmt()?,
821 "default" => self.parse_default_stmt()?,
822 "eval_timeout" => self.parse_eval_timeout()?,
823 "do" => {
824 if matches!(self.peek_at(1), Token::LBrace) {
825 self.advance();
826 let body = self.parse_block()?;
827 if let Token::Ident(ref w) = self.peek().clone() {
828 if w == "while" {
829 self.advance();
830 self.expect(&Token::LParen)?;
831 let mut condition = self.parse_expression()?;
832 Self::mark_match_scalar_g_for_boolean_condition(&mut condition);
833 self.expect(&Token::RParen)?;
834 self.eat(&Token::Semicolon);
835 Statement {
836 label: label.clone(),
837 kind: StmtKind::DoWhile { body, condition },
838 line,
839 }
840 } else {
841 let inner_line = body.first().map(|s| s.line).unwrap_or(line);
842 let inner = Expr {
843 kind: ExprKind::CodeRef {
844 params: vec![],
845 body,
846 },
847 line: inner_line,
848 };
849 let expr = Expr {
850 kind: ExprKind::Do(Box::new(inner)),
851 line,
852 };
853 let stmt = Statement {
854 label: label.clone(),
855 kind: StmtKind::Expression(expr),
856 line,
857 };
858 self.parse_stmt_postfix_modifier(stmt)?
860 }
861 } else {
862 let inner_line = body.first().map(|s| s.line).unwrap_or(line);
863 let inner = Expr {
864 kind: ExprKind::CodeRef {
865 params: vec![],
866 body,
867 },
868 line: inner_line,
869 };
870 let expr = Expr {
871 kind: ExprKind::Do(Box::new(inner)),
872 line,
873 };
874 let stmt = Statement {
875 label: label.clone(),
876 kind: StmtKind::Expression(expr),
877 line,
878 };
879 self.parse_stmt_postfix_modifier(stmt)?
880 }
881 } else {
882 if let Some(expr) = self.try_parse_bareword_stmt_call() {
883 let stmt = self.maybe_postfix_modifier(expr)?;
884 self.parse_stmt_postfix_modifier(stmt)?
885 } else {
886 let expr = self.parse_expression()?;
887 let stmt = self.maybe_postfix_modifier(expr)?;
888 self.parse_stmt_postfix_modifier(stmt)?
889 }
890 }
891 }
892 _ => {
893 if let Some(expr) = self.try_parse_bareword_stmt_call() {
895 let stmt = self.maybe_postfix_modifier(expr)?;
896 self.parse_stmt_postfix_modifier(stmt)?
897 } else {
898 let expr = self.parse_expression()?;
899 let stmt = self.maybe_postfix_modifier(expr)?;
900 self.parse_stmt_postfix_modifier(stmt)?
901 }
902 }
903 },
904 Token::LBrace => {
905 if self.looks_like_hashref() {
908 let expr = self.parse_expression()?;
909 let stmt = self.maybe_postfix_modifier(expr)?;
910 self.parse_stmt_postfix_modifier(stmt)?
911 } else {
912 let block = self.parse_block()?;
913 let stmt = Statement {
914 label: None,
915 kind: StmtKind::Block(block),
916 line,
917 };
918 self.parse_stmt_postfix_modifier(stmt)?
920 }
921 }
922 _ => {
923 let expr = self.parse_expression()?;
924 let stmt = self.maybe_postfix_modifier(expr)?;
925 self.parse_stmt_postfix_modifier(stmt)?
926 }
927 };
928
929 stmt.label = label;
930 Ok(stmt)
931 }
932
933 fn parse_stmt_postfix_modifier(&mut self, stmt: Statement) -> PerlResult<Statement> {
935 let line = stmt.line;
936 if self.peek_line() > self.prev_line() {
941 self.eat(&Token::Semicolon);
942 return Ok(stmt);
943 }
944 if let Token::Ident(ref kw) = self.peek().clone() {
945 match kw.as_str() {
946 "if" => {
947 self.advance();
948 let mut cond = self.parse_expression()?;
949 Self::mark_match_scalar_g_for_boolean_condition(&mut cond);
950 self.eat(&Token::Semicolon);
951 return Ok(Statement {
952 label: None,
953 kind: StmtKind::If {
954 condition: cond,
955 body: vec![stmt],
956 elsifs: vec![],
957 else_block: None,
958 },
959 line,
960 });
961 }
962 "unless" => {
963 self.advance();
964 let mut cond = self.parse_expression()?;
965 Self::mark_match_scalar_g_for_boolean_condition(&mut cond);
966 self.eat(&Token::Semicolon);
967 return Ok(Statement {
968 label: None,
969 kind: StmtKind::Unless {
970 condition: cond,
971 body: vec![stmt],
972 else_block: None,
973 },
974 line,
975 });
976 }
977 "while" | "until" | "for" | "foreach" => {
978 if let Some(expr) = Self::stmt_into_postfix_body_expr(stmt) {
981 let out = self.maybe_postfix_modifier(expr)?;
982 self.eat(&Token::Semicolon);
983 return Ok(out);
984 }
985 return Err(self.syntax_err(
986 format!("postfix `{}` is not supported on this statement form", kw),
987 self.peek_line(),
988 ));
989 }
990 "pmap" | "pflat_map" | "pgrep" | "pfor" | "preduce" | "pcache" => {
992 let line = stmt.line;
993 let block = self.stmt_into_parallel_block(stmt)?;
994 let which = kw.as_str();
995 self.advance();
996 self.eat(&Token::Comma);
997 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
998 self.eat(&Token::Semicolon);
999 let list = Box::new(list);
1000 let progress = progress.map(Box::new);
1001 let kind = match which {
1002 "pmap" => ExprKind::PMapExpr {
1003 block,
1004 list,
1005 progress,
1006 flat_outputs: false,
1007 on_cluster: None,
1008 stream: false,
1009 },
1010 "pflat_map" => ExprKind::PMapExpr {
1011 block,
1012 list,
1013 progress,
1014 flat_outputs: true,
1015 on_cluster: None,
1016 stream: false,
1017 },
1018 "pgrep" => ExprKind::PGrepExpr {
1019 block,
1020 list,
1021 progress,
1022 stream: false,
1023 },
1024 "pfor" => ExprKind::PForExpr {
1025 block,
1026 list,
1027 progress,
1028 },
1029 "preduce" => ExprKind::PReduceExpr {
1030 block,
1031 list,
1032 progress,
1033 },
1034 "pcache" => ExprKind::PcacheExpr {
1035 block,
1036 list,
1037 progress,
1038 },
1039 _ => unreachable!(),
1040 };
1041 return Ok(Statement {
1042 label: None,
1043 kind: StmtKind::Expression(Expr { kind, line }),
1044 line,
1045 });
1046 }
1047 _ => {}
1048 }
1049 }
1050 self.eat(&Token::Semicolon);
1051 Ok(stmt)
1052 }
1053
1054 fn stmt_into_parallel_block(&self, stmt: Statement) -> PerlResult<Block> {
1057 let line = stmt.line;
1058 match stmt.kind {
1059 StmtKind::Block(block) => Ok(block),
1060 StmtKind::Expression(expr) => {
1061 if let ExprKind::Do(ref inner) = expr.kind {
1062 if let ExprKind::CodeRef { ref body, .. } = inner.kind {
1063 return Ok(body.clone());
1064 }
1065 }
1066 Ok(vec![Statement {
1067 label: None,
1068 kind: StmtKind::Expression(expr),
1069 line,
1070 }])
1071 }
1072 _ => Err(self.syntax_err(
1073 "postfix parallel op expects `do { }`, a bare `{ }` block, or an expression statement",
1074 line,
1075 )),
1076 }
1077 }
1078
1079 fn stmt_into_postfix_body_expr(stmt: Statement) -> Option<Expr> {
1082 match stmt.kind {
1083 StmtKind::Expression(expr) => Some(expr),
1084 StmtKind::Block(block) => {
1085 let line = stmt.line;
1086 let inner = Expr {
1087 kind: ExprKind::CodeRef {
1088 params: vec![],
1089 body: block,
1090 },
1091 line,
1092 };
1093 Some(Expr {
1094 kind: ExprKind::Do(Box::new(inner)),
1095 line,
1096 })
1097 }
1098 _ => None,
1099 }
1100 }
1101
1102 fn peek_is_postfix_stmt_modifier_keyword(&self) -> bool {
1105 matches!(
1106 self.peek(),
1107 Token::Ident(ref kw)
1108 if matches!(
1109 kw.as_str(),
1110 "if" | "unless" | "while" | "until" | "for" | "foreach"
1111 )
1112 )
1113 }
1114
1115 fn maybe_postfix_modifier(&mut self, expr: Expr) -> PerlResult<Statement> {
1116 let line = expr.line;
1117 if self.peek_line() > self.prev_line() {
1119 return Ok(Statement {
1120 label: None,
1121 kind: StmtKind::Expression(expr),
1122 line,
1123 });
1124 }
1125 match self.peek() {
1126 Token::Ident(ref kw) => match kw.as_str() {
1127 "if" => {
1128 self.advance();
1129 let cond = self.parse_expression()?;
1130 Ok(Statement {
1131 label: None,
1132 kind: StmtKind::Expression(Expr {
1133 kind: ExprKind::PostfixIf {
1134 expr: Box::new(expr),
1135 condition: Box::new(cond),
1136 },
1137 line,
1138 }),
1139 line,
1140 })
1141 }
1142 "unless" => {
1143 self.advance();
1144 let cond = self.parse_expression()?;
1145 Ok(Statement {
1146 label: None,
1147 kind: StmtKind::Expression(Expr {
1148 kind: ExprKind::PostfixUnless {
1149 expr: Box::new(expr),
1150 condition: Box::new(cond),
1151 },
1152 line,
1153 }),
1154 line,
1155 })
1156 }
1157 "while" => {
1158 self.advance();
1159 let cond = self.parse_expression()?;
1160 Ok(Statement {
1161 label: None,
1162 kind: StmtKind::Expression(Expr {
1163 kind: ExprKind::PostfixWhile {
1164 expr: Box::new(expr),
1165 condition: Box::new(cond),
1166 },
1167 line,
1168 }),
1169 line,
1170 })
1171 }
1172 "until" => {
1173 self.advance();
1174 let cond = self.parse_expression()?;
1175 Ok(Statement {
1176 label: None,
1177 kind: StmtKind::Expression(Expr {
1178 kind: ExprKind::PostfixUntil {
1179 expr: Box::new(expr),
1180 condition: Box::new(cond),
1181 },
1182 line,
1183 }),
1184 line,
1185 })
1186 }
1187 "for" | "foreach" => {
1188 self.advance();
1189 let list = self.parse_expression()?;
1190 Ok(Statement {
1191 label: None,
1192 kind: StmtKind::Expression(Expr {
1193 kind: ExprKind::PostfixForeach {
1194 expr: Box::new(expr),
1195 list: Box::new(list),
1196 },
1197 line,
1198 }),
1199 line,
1200 })
1201 }
1202 _ => Ok(Statement {
1203 label: None,
1204 kind: StmtKind::Expression(expr),
1205 line,
1206 }),
1207 },
1208 _ => Ok(Statement {
1209 label: None,
1210 kind: StmtKind::Expression(expr),
1211 line,
1212 }),
1213 }
1214 }
1215
1216 fn try_parse_bareword_stmt_call(&mut self) -> Option<Expr> {
1218 let saved = self.pos;
1219 let line = self.peek_line();
1220 let mut name = match self.peek() {
1221 Token::Ident(n) => n.clone(),
1222 _ => return None,
1223 };
1224 if name.starts_with('\x00') || !Self::bareword_stmt_may_be_sub(&name) {
1226 return None;
1227 }
1228 self.advance();
1229 while self.eat(&Token::PackageSep) {
1230 match self.advance() {
1231 (Token::Ident(part), _) => {
1232 name = format!("{}::{}", name, part);
1233 }
1234 _ => {
1235 self.pos = saved;
1236 return None;
1237 }
1238 }
1239 }
1240 match self.peek() {
1241 Token::Semicolon | Token::RBrace => Some(Expr {
1242 kind: ExprKind::FuncCall { name, args: vec![] },
1243 line,
1244 }),
1245 _ => {
1246 self.pos = saved;
1247 None
1248 }
1249 }
1250 }
1251
1252 fn bareword_stmt_may_be_sub(name: &str) -> bool {
1254 !matches!(
1255 name,
1256 "__FILE__"
1257 | "__LINE__"
1258 | "abs"
1259 | "async"
1260 | "spawn"
1261 | "atan2"
1262 | "await"
1263 | "barrier"
1264 | "bless"
1265 | "caller"
1266 | "capture"
1267 | "cat"
1268 | "chdir"
1269 | "chmod"
1270 | "chomp"
1271 | "chop"
1272 | "chr"
1273 | "chown"
1274 | "closedir"
1275 | "close"
1276 | "collect"
1277 | "cos"
1278 | "crypt"
1279 | "defined"
1280 | "dec"
1281 | "delete"
1282 | "die"
1283 | "deque"
1284 | "do"
1285 | "each"
1286 | "eof"
1287 | "fore"
1288 | "eval"
1289 | "exec"
1290 | "exists"
1291 | "exit"
1292 | "exp"
1293 | "fan"
1294 | "fan_cap"
1295 | "fc"
1296 | "fetch_url"
1297 | "d"
1298 | "dirs"
1299 | "dr"
1300 | "f"
1301 | "fi"
1302 | "files"
1303 | "filesf"
1304 | "filter"
1305 | "fr"
1306 | "getcwd"
1307 | "glob_par"
1308 | "par_sed"
1309 | "glob"
1310 | "grep"
1311 | "greps"
1312 | "heap"
1313 | "hex"
1314 | "inc"
1315 | "index"
1316 | "int"
1317 | "join"
1318 | "keys"
1319 | "lcfirst"
1320 | "lc"
1321 | "length"
1322 | "link"
1323 | "log"
1324 | "lstat"
1325 | "map"
1326 | "flat_map"
1327 | "maps"
1328 | "flat_maps"
1329 | "flatten"
1330 | "frequencies"
1331 | "freq"
1332 | "interleave"
1333 | "ddump"
1334 | "stringify"
1335 | "str"
1336 | "s"
1337 | "input"
1338 | "lines"
1339 | "words"
1340 | "chars"
1341 | "digits"
1342 | "letters"
1343 | "letters_uc"
1344 | "letters_lc"
1345 | "punctuation"
1346 | "sentences"
1347 | "paragraphs"
1348 | "sections"
1349 | "numbers"
1350 | "graphemes"
1351 | "columns"
1352 | "trim"
1353 | "avg"
1354 | "top"
1355 | "pager"
1356 | "pg"
1357 | "less"
1358 | "count_by"
1359 | "to_file"
1360 | "to_json"
1361 | "to_csv"
1362 | "grep_v"
1363 | "select_keys"
1364 | "pluck"
1365 | "clamp"
1366 | "normalize"
1367 | "stddev"
1368 | "squared"
1369 | "square"
1370 | "cubed"
1371 | "cube"
1372 | "expt"
1373 | "pow"
1374 | "pw"
1375 | "snake_case"
1376 | "camel_case"
1377 | "kebab_case"
1378 | "to_toml"
1379 | "to_yaml"
1380 | "to_xml"
1381 | "to_html"
1382 | "to_markdown"
1383 | "xopen"
1384 | "clip"
1385 | "paste"
1386 | "to_table"
1387 | "sparkline"
1388 | "bar_chart"
1389 | "flame"
1390 | "set"
1391 | "list_count"
1392 | "list_size"
1393 | "count"
1394 | "size"
1395 | "cnt"
1396 | "len"
1397 | "all"
1398 | "any"
1399 | "none"
1400 | "take_while"
1401 | "drop_while"
1402 | "skip_while"
1403 | "skip"
1404 | "first_or"
1405 | "tap"
1406 | "peek"
1407 | "partition"
1408 | "min_by"
1409 | "max_by"
1410 | "zip_with"
1411 | "group_by"
1412 | "chunk_by"
1413 | "with_index"
1414 | "puniq"
1415 | "pfirst"
1416 | "pany"
1417 | "uniq"
1418 | "distinct"
1419 | "shuffle"
1420 | "shuffled"
1421 | "chunked"
1422 | "windowed"
1423 | "match"
1424 | "mkdir"
1425 | "every"
1426 | "gen"
1427 | "oct"
1428 | "open"
1429 | "p"
1430 | "opendir"
1431 | "ord"
1432 | "par_lines"
1433 | "par_walk"
1434 | "pipe"
1435 | "pipes"
1436 | "block_devices"
1437 | "char_devices"
1438 | "exe"
1439 | "executables"
1440 | "rate_limit"
1441 | "retry"
1442 | "pcache"
1443 | "pchannel"
1444 | "pfor"
1445 | "pgrep"
1446 | "pgreps"
1447 | "pipeline"
1448 | "pmap_chunked"
1449 | "pmap_reduce"
1450 | "pmap_on"
1451 | "pflat_map_on"
1452 | "pmap"
1453 | "pmaps"
1454 | "pflat_map"
1455 | "pflat_maps"
1456 | "pop"
1457 | "pos"
1458 | "ppool"
1459 | "preduce_init"
1460 | "preduce"
1461 | "pselect"
1462 | "printf"
1463 | "print"
1464 | "pr"
1465 | "psort"
1466 | "push"
1467 | "pwatch"
1468 | "rand"
1469 | "readdir"
1470 | "readlink"
1471 | "reduce"
1472 | "fold"
1473 | "inject"
1474 | "first"
1475 | "detect"
1476 | "find"
1477 | "find_all"
1478 | "ref"
1479 | "rename"
1480 | "require"
1481 | "rev"
1482 | "reverse"
1483 | "reversed"
1484 | "rewinddir"
1485 | "rindex"
1486 | "rmdir"
1487 | "rm"
1488 | "say"
1489 | "scalar"
1490 | "seekdir"
1491 | "shift"
1492 | "sin"
1493 | "slurp"
1494 | "sockets"
1495 | "sort"
1496 | "splice"
1497 | "split"
1498 | "sprintf"
1499 | "sqrt"
1500 | "srand"
1501 | "stat"
1502 | "study"
1503 | "substr"
1504 | "symlink"
1505 | "sym_links"
1506 | "system"
1507 | "telldir"
1508 | "timer"
1509 | "trace"
1510 | "ucfirst"
1511 | "uc"
1512 | "undef"
1513 | "umask"
1514 | "unlink"
1515 | "unshift"
1516 | "utime"
1517 | "values"
1518 | "wantarray"
1519 | "warn"
1520 | "watch"
1521 | "yield"
1522 | "sub"
1523 )
1524 }
1525
1526 fn parse_block(&mut self) -> PerlResult<Block> {
1527 self.expect(&Token::LBrace)?;
1528 let saved_pipe_rhs_depth = self.pipe_rhs_depth;
1531 self.pipe_rhs_depth = 0;
1532 let mut stmts = Vec::new();
1533 if let Some(param_stmts) = self.try_parse_block_params()? {
1537 stmts.extend(param_stmts);
1538 }
1539 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
1540 if self.eat(&Token::Semicolon) {
1541 continue;
1542 }
1543 stmts.push(self.parse_statement()?);
1544 }
1545 self.expect(&Token::RBrace)?;
1546 self.pipe_rhs_depth = saved_pipe_rhs_depth;
1547 Self::default_topic_for_sole_bareword(&mut stmts);
1548 Ok(stmts)
1549 }
1550
1551 fn try_parse_block_params(&mut self) -> PerlResult<Option<Vec<Statement>>> {
1556 if !matches!(self.peek(), Token::BitOr) {
1557 return Ok(None);
1558 }
1559 let mut i = 1; loop {
1562 match self.peek_at(i) {
1563 Token::ScalarVar(_) => i += 1,
1564 _ => return Ok(None), }
1566 match self.peek_at(i) {
1567 Token::BitOr => break, Token::Comma => i += 1, _ => return Ok(None), }
1571 }
1572 let line = self.peek_line();
1574 self.advance(); let mut names = Vec::new();
1576 loop {
1577 if let Token::ScalarVar(ref name) = self.peek().clone() {
1578 names.push(name.clone());
1579 self.advance();
1580 }
1581 if self.eat(&Token::BitOr) {
1582 break;
1583 }
1584 self.expect(&Token::Comma)?;
1585 }
1586 let sources: Vec<&str> = match names.len() {
1591 1 => vec!["_"],
1592 2 => vec!["a", "b"],
1593 n => {
1594 let _ = n;
1596 vec![] }
1598 };
1599 let mut stmts = Vec::with_capacity(names.len());
1600 if !sources.is_empty() {
1601 for (name, src) in names.iter().zip(sources.iter()) {
1602 stmts.push(Statement {
1603 label: None,
1604 kind: StmtKind::My(vec![VarDecl {
1605 sigil: Sigil::Scalar,
1606 name: name.clone(),
1607 initializer: Some(Expr {
1608 kind: ExprKind::ScalarVar(src.to_string()),
1609 line,
1610 }),
1611 frozen: false,
1612 type_annotation: None,
1613 }]),
1614 line,
1615 });
1616 }
1617 } else {
1618 for (idx, name) in names.iter().enumerate() {
1620 let src = if idx == 0 {
1621 "_".to_string()
1622 } else {
1623 format!("_{idx}")
1624 };
1625 stmts.push(Statement {
1626 label: None,
1627 kind: StmtKind::My(vec![VarDecl {
1628 sigil: Sigil::Scalar,
1629 name: name.clone(),
1630 initializer: Some(Expr {
1631 kind: ExprKind::ScalarVar(src),
1632 line,
1633 }),
1634 frozen: false,
1635 type_annotation: None,
1636 }]),
1637 line,
1638 });
1639 }
1640 }
1641 Ok(Some(stmts))
1642 }
1643
1644 fn default_topic_for_sole_bareword(stmts: &mut [Statement]) {
1659 let [only] = stmts else { return };
1660 let StmtKind::Expression(ref mut expr) = only.kind else {
1661 return;
1662 };
1663 let topic_line = expr.line;
1664 let topic_arg = || Expr {
1665 kind: ExprKind::ScalarVar("_".to_string()),
1666 line: topic_line,
1667 };
1668 match expr.kind {
1669 ExprKind::FuncCall {
1671 ref name,
1672 ref mut args,
1673 } if args.is_empty()
1674 && (Self::is_known_bareword(name) || Self::is_try_builtin_name(name)) =>
1675 {
1676 args.push(topic_arg());
1677 }
1678 ExprKind::Bareword(ref name)
1682 if (Self::is_known_bareword(name) || Self::is_try_builtin_name(name)) =>
1683 {
1684 let n = name.clone();
1685 expr.kind = ExprKind::FuncCall {
1686 name: n,
1687 args: vec![topic_arg()],
1688 };
1689 }
1690 _ => {}
1691 }
1692 }
1693
1694 fn parse_defer_stmt(&mut self) -> PerlResult<Statement> {
1698 let line = self.peek_line();
1699 self.advance(); let body = self.parse_block()?;
1701 self.eat(&Token::Semicolon);
1702 let coderef = Expr {
1704 kind: ExprKind::CodeRef {
1705 params: vec![],
1706 body,
1707 },
1708 line,
1709 };
1710 Ok(Statement {
1711 label: None,
1712 kind: StmtKind::Expression(Expr {
1713 kind: ExprKind::FuncCall {
1714 name: "defer__internal".to_string(),
1715 args: vec![coderef],
1716 },
1717 line,
1718 }),
1719 line,
1720 })
1721 }
1722
1723 fn parse_try_catch(&mut self) -> PerlResult<Statement> {
1725 let line = self.peek_line();
1726 self.advance(); let try_block = self.parse_block()?;
1728 match self.peek() {
1729 Token::Ident(ref k) if k == "catch" => {
1730 self.advance();
1731 }
1732 _ => {
1733 return Err(self.syntax_err("expected 'catch' after try block", self.peek_line()));
1734 }
1735 }
1736 self.expect(&Token::LParen)?;
1737 let catch_var = self.parse_scalar_var_name()?;
1738 self.expect(&Token::RParen)?;
1739 let catch_block = self.parse_block()?;
1740 let finally_block = match self.peek() {
1741 Token::Ident(ref k) if k == "finally" => {
1742 self.advance();
1743 Some(self.parse_block()?)
1744 }
1745 _ => None,
1746 };
1747 self.eat(&Token::Semicolon);
1748 Ok(Statement {
1749 label: None,
1750 kind: StmtKind::TryCatch {
1751 try_block,
1752 catch_var,
1753 catch_block,
1754 finally_block,
1755 },
1756 line,
1757 })
1758 }
1759
1760 fn parse_thread_macro(&mut self, _line: usize, thread_last: bool) -> PerlResult<Expr> {
1774 let saved_thread_last = self.thread_last_mode;
1776 self.thread_last_mode = thread_last;
1777
1778 let pipe_rhs_wrap = self.in_pipe_rhs();
1779 let mut result = if pipe_rhs_wrap {
1780 Expr {
1781 kind: ExprKind::ArrayElement {
1782 array: "_".to_string(),
1783 index: Box::new(Expr {
1784 kind: ExprKind::Integer(0),
1785 line: _line,
1786 }),
1787 },
1788 line: _line,
1789 }
1790 } else {
1791 self.suppress_parenless_call = self.suppress_parenless_call.saturating_add(1);
1794 let expr = self.parse_thread_input();
1795 self.suppress_parenless_call = self.suppress_parenless_call.saturating_sub(1);
1796 expr?
1797 };
1798
1799 let mut last_stage_end_line = self.prev_line();
1801
1802 loop {
1804 if self.peek_line() > last_stage_end_line {
1809 break;
1810 }
1811
1812 match self.peek() {
1816 Token::Semicolon
1817 | Token::RBrace
1818 | Token::RParen
1819 | Token::RBracket
1820 | Token::PipeForward
1821 | Token::Eof
1822 | Token::ScalarVar(_)
1823 | Token::ArrayVar(_)
1824 | Token::HashVar(_)
1825 | Token::Comma => break,
1826 Token::Ident(ref kw)
1827 if matches!(
1828 kw.as_str(),
1829 "my" | "our"
1830 | "local"
1831 | "state"
1832 | "if"
1833 | "unless"
1834 | "while"
1835 | "until"
1836 | "for"
1837 | "foreach"
1838 | "return"
1839 | "last"
1840 | "next"
1841 | "redo"
1842 ) =>
1843 {
1844 break
1845 }
1846 _ => {}
1847 }
1848
1849 let stage_line = self.peek_line();
1850
1851 match self.peek().clone() {
1853 Token::ArrowBrace => {
1855 self.advance(); let mut stmts = Vec::new();
1857 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
1858 if self.eat(&Token::Semicolon) {
1859 continue;
1860 }
1861 stmts.push(self.parse_statement()?);
1862 }
1863 self.expect(&Token::RBrace)?;
1864 let code_ref = Expr {
1865 kind: ExprKind::CodeRef {
1866 params: vec![],
1867 body: stmts,
1868 },
1869 line: stage_line,
1870 };
1871 result = self.pipe_forward_apply(result, code_ref, stage_line)?;
1872 }
1873 Token::Ident(ref name) if name == "sub" => {
1875 if crate::no_interop_mode() {
1876 return Err(self.syntax_err(
1877 "stryke uses `fn {}` instead of `sub {}` (--no-interop)",
1878 stage_line,
1879 ));
1880 }
1881 self.advance(); let (params, _prototype) = self.parse_sub_sig_or_prototype_opt()?;
1883 let body = self.parse_block()?;
1884 let code_ref = Expr {
1885 kind: ExprKind::CodeRef { params, body },
1886 line: stage_line,
1887 };
1888 result = self.pipe_forward_apply(result, code_ref, stage_line)?;
1889 }
1890 Token::Ident(ref name) if name == "fn" => {
1892 self.advance(); let (params, _prototype) = self.parse_sub_sig_or_prototype_opt()?;
1894 let body = self.parse_block()?;
1895 let code_ref = Expr {
1896 kind: ExprKind::CodeRef { params, body },
1897 line: stage_line,
1898 };
1899 result = self.pipe_forward_apply(result, code_ref, stage_line)?;
1900 }
1901 Token::Ident(ref name) => {
1903 let mut func_name = name.clone();
1904 self.advance();
1905
1906 while matches!(self.peek(), Token::PackageSep) {
1908 self.advance(); if let Token::Ident(ref part) = self.peek().clone() {
1910 func_name.push_str("::");
1911 func_name.push_str(part);
1912 self.advance();
1913 } else {
1914 return Err(self.syntax_err(
1915 format!(
1916 "Expected identifier after `::` in thread stage, got {:?}",
1917 self.peek()
1918 ),
1919 stage_line,
1920 ));
1921 }
1922 }
1923
1924 if func_name.starts_with('\x00') {
1926 let parts: Vec<&str> = func_name.split('\x00').collect();
1927 if parts.len() >= 4 && parts[1] == "s" {
1928 let delim = parts.get(5).and_then(|s| s.chars().next()).unwrap_or('/');
1929 let stage = Expr {
1930 kind: ExprKind::Substitution {
1931 expr: Box::new(result.clone()),
1932 pattern: parts[2].to_string(),
1933 replacement: parts[3].to_string(),
1934 flags: format!("{}r", parts.get(4).unwrap_or(&"")),
1935 delim,
1936 },
1937 line: stage_line,
1938 };
1939 result = stage;
1940 last_stage_end_line = self.prev_line();
1941 continue;
1942 }
1943 if parts.len() >= 4 && parts[1] == "tr" {
1944 let delim = parts.get(5).and_then(|s| s.chars().next()).unwrap_or('/');
1945 let stage = Expr {
1946 kind: ExprKind::Transliterate {
1947 expr: Box::new(result.clone()),
1948 from: parts[2].to_string(),
1949 to: parts[3].to_string(),
1950 flags: format!("{}r", parts.get(4).unwrap_or(&"")),
1951 delim,
1952 },
1953 line: stage_line,
1954 };
1955 result = stage;
1956 last_stage_end_line = self.prev_line();
1957 continue;
1958 }
1959 return Err(
1960 self.syntax_err("Unexpected encoded token in thread", stage_line)
1961 );
1962 }
1963
1964 if matches!(self.peek(), Token::Plus)
1969 && matches!(self.peek_at(1), Token::LBrace)
1970 {
1971 self.advance(); self.expect(&Token::LBrace)?;
1973 let pairs = self.try_parse_hash_ref()?;
1975 let hashref_expr = Expr {
1976 kind: ExprKind::HashRef(pairs),
1977 line: stage_line,
1978 };
1979 let flatten_array_refs =
1980 matches!(func_name.as_str(), "flat_map" | "flat_maps");
1981 let stream = matches!(func_name.as_str(), "maps" | "flat_maps");
1982 let placeholder = Expr {
1984 kind: ExprKind::Undef,
1985 line: stage_line,
1986 };
1987 let map_node = Expr {
1988 kind: ExprKind::MapExprComma {
1989 expr: Box::new(hashref_expr),
1990 list: Box::new(placeholder),
1991 flatten_array_refs,
1992 stream,
1993 },
1994 line: stage_line,
1995 };
1996 result = self.pipe_forward_apply(result, map_node, stage_line)?;
1997 } else if func_name == "pmap_chunked" {
1999 let chunk_size = self.parse_assign_expr()?;
2000 let block = self.parse_block_or_bareword_block()?;
2001 let placeholder = self.pipe_placeholder_list(stage_line);
2002 let stage = Expr {
2003 kind: ExprKind::PMapChunkedExpr {
2004 chunk_size: Box::new(chunk_size),
2005 block,
2006 list: Box::new(placeholder),
2007 progress: None,
2008 },
2009 line: stage_line,
2010 };
2011 result = self.pipe_forward_apply(result, stage, stage_line)?;
2012 } else if func_name == "preduce_init" {
2014 let init = self.parse_assign_expr()?;
2015 let block = self.parse_block_or_bareword_block()?;
2016 let placeholder = self.pipe_placeholder_list(stage_line);
2017 let stage = Expr {
2018 kind: ExprKind::PReduceInitExpr {
2019 init: Box::new(init),
2020 block,
2021 list: Box::new(placeholder),
2022 progress: None,
2023 },
2024 line: stage_line,
2025 };
2026 result = self.pipe_forward_apply(result, stage, stage_line)?;
2027 } else if func_name == "pmap_reduce" {
2029 let map_block = self.parse_block_or_bareword_block()?;
2030 let reduce_block = if matches!(self.peek(), Token::LBrace) {
2031 self.parse_block()?
2032 } else {
2033 self.expect(&Token::Comma)?;
2034 self.parse_block_or_bareword_cmp_block()?
2035 };
2036 let placeholder = self.pipe_placeholder_list(stage_line);
2037 let stage = Expr {
2038 kind: ExprKind::PMapReduceExpr {
2039 map_block,
2040 reduce_block,
2041 list: Box::new(placeholder),
2042 progress: None,
2043 },
2044 line: stage_line,
2045 };
2046 result = self.pipe_forward_apply(result, stage, stage_line)?;
2047 } else if func_name == "pmap_on" || func_name == "pflat_map_on" {
2052 self.suppress_scalar_hash_brace =
2055 self.suppress_scalar_hash_brace.saturating_add(1);
2056 let cluster = self.parse_assign_expr();
2057 self.suppress_scalar_hash_brace =
2058 self.suppress_scalar_hash_brace.saturating_sub(1);
2059 let cluster = cluster?;
2060 self.eat(&Token::Comma);
2063 let block = self.parse_block_or_bareword_block()?;
2064 let placeholder = self.pipe_placeholder_list(stage_line);
2065 let stage = Expr {
2066 kind: ExprKind::PMapExpr {
2067 block,
2068 list: Box::new(placeholder),
2069 progress: None,
2070 flat_outputs: func_name == "pflat_map_on",
2071 on_cluster: Some(Box::new(cluster)),
2072 stream: false,
2073 },
2074 line: stage_line,
2075 };
2076 result = self.pipe_forward_apply(result, stage, stage_line)?;
2077 } else if matches!(self.peek(), Token::LBrace) {
2079 self.pipe_rhs_depth = self.pipe_rhs_depth.saturating_add(1);
2081 let stage = self.parse_thread_stage_with_block(&func_name, stage_line)?;
2082 self.pipe_rhs_depth = self.pipe_rhs_depth.saturating_sub(1);
2083 result = self.pipe_forward_apply(result, stage, stage_line)?;
2084 } else if matches!(self.peek(), Token::LParen) {
2085 if func_name == "join" {
2088 self.advance(); let separator = self.parse_assign_expr()?;
2090 self.expect(&Token::RParen)?;
2091 let placeholder = self.pipe_placeholder_list(stage_line);
2092 let stage = Expr {
2093 kind: ExprKind::JoinExpr {
2094 separator: Box::new(separator),
2095 list: Box::new(placeholder),
2096 },
2097 line: stage_line,
2098 };
2099 result = self.pipe_forward_apply(result, stage, stage_line)?;
2100 } else if func_name == "split" {
2101 self.advance(); let pattern = self.parse_assign_expr()?;
2103 let limit = if self.eat(&Token::Comma) {
2104 Some(Box::new(self.parse_assign_expr()?))
2105 } else {
2106 None
2107 };
2108 self.expect(&Token::RParen)?;
2109 let placeholder = Expr {
2110 kind: ExprKind::ScalarVar("_".to_string()),
2111 line: stage_line,
2112 };
2113 let stage = Expr {
2114 kind: ExprKind::SplitExpr {
2115 pattern: Box::new(pattern),
2116 string: Box::new(placeholder),
2117 limit,
2118 },
2119 line: stage_line,
2120 };
2121 result = self.pipe_forward_apply(result, stage, stage_line)?;
2122 } else {
2123 self.advance(); let mut call_args = Vec::new();
2134 while !matches!(self.peek(), Token::RParen | Token::Eof) {
2135 call_args.push(self.parse_assign_expr()?);
2136 if !self.eat(&Token::Comma) {
2137 break;
2138 }
2139 }
2140 self.expect(&Token::RParen)?;
2141 if !call_args.iter().any(Self::expr_contains_topic_var) {
2145 let topic = Expr {
2146 kind: ExprKind::ScalarVar("_".to_string()),
2147 line: stage_line,
2148 };
2149 if self.thread_last_mode {
2150 call_args.push(topic);
2151 } else {
2152 call_args.insert(0, topic);
2153 }
2154 }
2155 let call_expr = Expr {
2156 kind: ExprKind::FuncCall {
2157 name: func_name.clone(),
2158 args: call_args,
2159 },
2160 line: stage_line,
2161 };
2162 let code_ref = Expr {
2163 kind: ExprKind::CodeRef {
2164 params: vec![],
2165 body: vec![Statement {
2166 label: None,
2167 kind: StmtKind::Expression(call_expr),
2168 line: stage_line,
2169 }],
2170 },
2171 line: stage_line,
2172 };
2173 result = self.pipe_forward_apply(result, code_ref, stage_line)?;
2174 }
2175 } else {
2176 result = self.thread_apply_bare_func(&func_name, result, stage_line)?;
2178 }
2179 }
2180 Token::Regex(ref pattern, ref flags, delim) => {
2182 let pattern = pattern.clone();
2183 let flags = flags.clone();
2184 self.advance();
2185 result =
2186 self.thread_regex_grep_stage(result, pattern, flags, delim, stage_line);
2187 }
2188 Token::Slash => {
2191 self.advance(); if let Token::Ident(ref ident_s) = self.peek().clone() {
2197 if matches!(ident_s.as_str(), "m" | "s" | "tr" | "y" | "qr")
2198 && matches!(self.peek_at(1), Token::Regex(..))
2199 {
2200 self.advance(); if let Token::Regex(ref misparsed_pattern, ref misparsed_flags, _) =
2207 self.peek().clone()
2208 {
2209 let _ = (misparsed_pattern, misparsed_flags);
2219 }
2220 }
2221 }
2222
2223 let mut pattern = String::new();
2225 loop {
2226 match self.peek().clone() {
2227 Token::Slash => {
2228 self.advance(); break;
2230 }
2231 Token::Eof | Token::Semicolon | Token::Newline => {
2232 return Err(self
2233 .syntax_err("Unterminated regex in thread stage", stage_line));
2234 }
2235 Token::Regex(ref inner_pattern, ref inner_flags, delim) => {
2237 if pattern.is_empty()
2245 || matches!(pattern.as_str(), "m" | "s" | "tr" | "y" | "qr")
2246 {
2247 let _ = (inner_pattern, inner_flags, delim);
2253 }
2254 return Err(self.syntax_err(
2256 "Complex regex in thread stage - use m/pattern/ syntax instead",
2257 stage_line,
2258 ));
2259 }
2260 Token::Ident(ref s) => {
2261 pattern.push_str(s);
2262 self.advance();
2263 }
2264 Token::Integer(n) => {
2265 pattern.push_str(&n.to_string());
2266 self.advance();
2267 }
2268 Token::ScalarVar(ref v) => {
2269 pattern.push('$');
2270 pattern.push_str(v);
2271 self.advance();
2272 }
2273 Token::Dot => {
2274 pattern.push('.');
2275 self.advance();
2276 }
2277 Token::Star => {
2278 pattern.push('*');
2279 self.advance();
2280 }
2281 Token::Plus => {
2282 pattern.push('+');
2283 self.advance();
2284 }
2285 Token::Question => {
2286 pattern.push('?');
2287 self.advance();
2288 }
2289 Token::LParen => {
2290 pattern.push('(');
2291 self.advance();
2292 }
2293 Token::RParen => {
2294 pattern.push(')');
2295 self.advance();
2296 }
2297 Token::LBracket => {
2298 pattern.push('[');
2299 self.advance();
2300 }
2301 Token::RBracket => {
2302 pattern.push(']');
2303 self.advance();
2304 }
2305 Token::Backslash => {
2306 pattern.push('\\');
2307 self.advance();
2308 }
2309 Token::BitOr => {
2310 pattern.push('|');
2311 self.advance();
2312 }
2313 Token::Power => {
2314 pattern.push_str("**");
2315 self.advance();
2316 }
2317 Token::BitXor => {
2318 pattern.push('^');
2319 self.advance();
2320 }
2321 Token::Minus => {
2322 pattern.push('-');
2323 self.advance();
2324 }
2325 _ => {
2326 return Err(self.syntax_err(
2327 format!("Unexpected token in regex pattern: {:?}", self.peek()),
2328 stage_line,
2329 ));
2330 }
2331 }
2332 }
2333 let mut flags = String::new();
2337 if let Token::Ident(ref s) = self.peek().clone() {
2338 let is_flag_only =
2339 s.chars().all(|c| "gimsxecor".contains(c)) && s.len() <= 6;
2340 let followed_by_brace = matches!(self.peek_at(1), Token::LBrace);
2341 if is_flag_only && !followed_by_brace {
2342 flags.push_str(s);
2343 self.advance();
2344 }
2345 }
2346 result = self.thread_regex_grep_stage(result, pattern, flags, '/', stage_line);
2347 }
2348 tok => {
2349 return Err(self.syntax_err(
2350 format!(
2351 "thread: expected stage (ident, fn {{}}, s///, tr///, or /re/), got {:?}",
2352 tok
2353 ),
2354 stage_line,
2355 ));
2356 }
2357 };
2358 last_stage_end_line = self.prev_line();
2359 }
2360
2361 self.thread_last_mode = saved_thread_last;
2363
2364 if pipe_rhs_wrap {
2365 let body_line = result.line;
2368 return Ok(Expr {
2369 kind: ExprKind::CodeRef {
2370 params: vec![],
2371 body: vec![Statement {
2372 label: None,
2373 kind: StmtKind::Expression(result),
2374 line: body_line,
2375 }],
2376 },
2377 line: _line,
2378 });
2379 }
2380 Ok(result)
2381 }
2382
2383 fn thread_regex_grep_stage(
2385 &self,
2386 list: Expr,
2387 pattern: String,
2388 flags: String,
2389 delim: char,
2390 line: usize,
2391 ) -> Expr {
2392 let topic = Expr {
2393 kind: ExprKind::ScalarVar("_".to_string()),
2394 line,
2395 };
2396 let match_expr = Expr {
2397 kind: ExprKind::Match {
2398 expr: Box::new(topic),
2399 pattern,
2400 flags,
2401 scalar_g: false,
2402 delim,
2403 },
2404 line,
2405 };
2406 let block = vec![Statement {
2407 label: None,
2408 kind: StmtKind::Expression(match_expr),
2409 line,
2410 }];
2411 Expr {
2412 kind: ExprKind::GrepExpr {
2413 block,
2414 list: Box::new(list),
2415 keyword: crate::ast::GrepBuiltinKeyword::Grep,
2416 },
2417 line,
2418 }
2419 }
2420
2421 fn expr_contains_topic_var(e: &Expr) -> bool {
2432 format!("{:?}", e).contains("ScalarVar(\"_\")")
2433 }
2434
2435 fn thread_apply_bare_func(&self, name: &str, arg: Expr, line: usize) -> PerlResult<Expr> {
2437 let kind = match name {
2438 "uc" => ExprKind::Uc(Box::new(arg)),
2440 "lc" => ExprKind::Lc(Box::new(arg)),
2441 "ucfirst" | "ufc" => ExprKind::Ucfirst(Box::new(arg)),
2442 "lcfirst" | "lfc" => ExprKind::Lcfirst(Box::new(arg)),
2443 "fc" => ExprKind::Fc(Box::new(arg)),
2444 "chomp" => ExprKind::Chomp(Box::new(arg)),
2445 "chop" => ExprKind::Chop(Box::new(arg)),
2446 "length" => ExprKind::Length(Box::new(arg)),
2447 "len" | "cnt" => ExprKind::FuncCall {
2448 name: "count".to_string(),
2449 args: vec![arg],
2450 },
2451 "quotemeta" | "qm" => ExprKind::FuncCall {
2452 name: "quotemeta".to_string(),
2453 args: vec![arg],
2454 },
2455 "abs" => ExprKind::Abs(Box::new(arg)),
2457 "int" => ExprKind::Int(Box::new(arg)),
2458 "sqrt" | "sq" => ExprKind::Sqrt(Box::new(arg)),
2459 "sin" => ExprKind::Sin(Box::new(arg)),
2460 "cos" => ExprKind::Cos(Box::new(arg)),
2461 "exp" => ExprKind::Exp(Box::new(arg)),
2462 "log" => ExprKind::Log(Box::new(arg)),
2463 "hex" => ExprKind::Hex(Box::new(arg)),
2464 "oct" => ExprKind::Oct(Box::new(arg)),
2465 "chr" => ExprKind::Chr(Box::new(arg)),
2466 "ord" => ExprKind::Ord(Box::new(arg)),
2467 "defined" | "def" => ExprKind::Defined(Box::new(arg)),
2469 "ref" => ExprKind::Ref(Box::new(arg)),
2470 "scalar" => ExprKind::ScalarContext(Box::new(arg)),
2471 "keys" => ExprKind::Keys(Box::new(arg)),
2473 "values" => ExprKind::Values(Box::new(arg)),
2474 "each" => ExprKind::Each(Box::new(arg)),
2475 "pop" => ExprKind::Pop(Box::new(arg)),
2476 "shift" => ExprKind::Shift(Box::new(arg)),
2477 "reverse" => {
2478 if crate::no_interop_mode() {
2479 return Err(self.syntax_err(
2480 "stryke uses `rev` instead of `reverse` (--no-interop)",
2481 line,
2482 ));
2483 }
2484 ExprKind::ReverseExpr(Box::new(arg))
2485 }
2486 "reversed" | "rv" | "rev" => ExprKind::Rev(Box::new(arg)),
2487 "sort" | "so" => ExprKind::SortExpr {
2488 cmp: None,
2489 list: Box::new(arg),
2490 },
2491 "psort" => ExprKind::PSortExpr {
2492 cmp: None,
2493 list: Box::new(arg),
2494 progress: None,
2495 },
2496 "uniq" | "distinct" | "uq" => ExprKind::FuncCall {
2497 name: "uniq".to_string(),
2498 args: vec![arg],
2499 },
2500 "trim" | "tm" => ExprKind::FuncCall {
2501 name: "trim".to_string(),
2502 args: vec![arg],
2503 },
2504 "flatten" | "fl" => ExprKind::FuncCall {
2505 name: "flatten".to_string(),
2506 args: vec![arg],
2507 },
2508 "compact" | "cpt" => ExprKind::FuncCall {
2509 name: "compact".to_string(),
2510 args: vec![arg],
2511 },
2512 "shuffle" | "shuf" => ExprKind::FuncCall {
2513 name: "shuffle".to_string(),
2514 args: vec![arg],
2515 },
2516 "frequencies" | "freq" | "frq" => ExprKind::FuncCall {
2517 name: "frequencies".to_string(),
2518 args: vec![arg],
2519 },
2520 "dedup" | "dup" => ExprKind::FuncCall {
2521 name: "dedup".to_string(),
2522 args: vec![arg],
2523 },
2524 "enumerate" | "en" => ExprKind::FuncCall {
2525 name: "enumerate".to_string(),
2526 args: vec![arg],
2527 },
2528 "lines" | "ln" => ExprKind::FuncCall {
2529 name: "lines".to_string(),
2530 args: vec![arg],
2531 },
2532 "words" | "wd" => ExprKind::FuncCall {
2533 name: "words".to_string(),
2534 args: vec![arg],
2535 },
2536 "chars" | "ch" => ExprKind::FuncCall {
2537 name: "chars".to_string(),
2538 args: vec![arg],
2539 },
2540 "digits" | "dg" => ExprKind::FuncCall {
2541 name: "digits".to_string(),
2542 args: vec![arg],
2543 },
2544 "letters" | "lts" => ExprKind::FuncCall {
2545 name: "letters".to_string(),
2546 args: vec![arg],
2547 },
2548 "letters_uc" => ExprKind::FuncCall {
2549 name: "letters_uc".to_string(),
2550 args: vec![arg],
2551 },
2552 "letters_lc" => ExprKind::FuncCall {
2553 name: "letters_lc".to_string(),
2554 args: vec![arg],
2555 },
2556 "punctuation" | "punct" => ExprKind::FuncCall {
2557 name: "punctuation".to_string(),
2558 args: vec![arg],
2559 },
2560 "sentences" | "sents" => ExprKind::FuncCall {
2561 name: "sentences".to_string(),
2562 args: vec![arg],
2563 },
2564 "paragraphs" | "paras" => ExprKind::FuncCall {
2565 name: "paragraphs".to_string(),
2566 args: vec![arg],
2567 },
2568 "sections" | "sects" => ExprKind::FuncCall {
2569 name: "sections".to_string(),
2570 args: vec![arg],
2571 },
2572 "numbers" | "nums" => ExprKind::FuncCall {
2573 name: "numbers".to_string(),
2574 args: vec![arg],
2575 },
2576 "graphemes" | "grs" => ExprKind::FuncCall {
2577 name: "graphemes".to_string(),
2578 args: vec![arg],
2579 },
2580 "columns" | "cols" => ExprKind::FuncCall {
2581 name: "columns".to_string(),
2582 args: vec![arg],
2583 },
2584 "slurp" | "sl" => ExprKind::Slurp(Box::new(arg)),
2586 "chdir" => ExprKind::Chdir(Box::new(arg)),
2587 "stat" => ExprKind::Stat(Box::new(arg)),
2588 "lstat" => ExprKind::Lstat(Box::new(arg)),
2589 "readlink" => ExprKind::Readlink(Box::new(arg)),
2590 "readdir" => ExprKind::Readdir(Box::new(arg)),
2591 "close" => ExprKind::Close(Box::new(arg)),
2592 "basename" | "bn" => ExprKind::FuncCall {
2593 name: "basename".to_string(),
2594 args: vec![arg],
2595 },
2596 "dirname" | "dn" => ExprKind::FuncCall {
2597 name: "dirname".to_string(),
2598 args: vec![arg],
2599 },
2600 "realpath" | "rp" => ExprKind::FuncCall {
2601 name: "realpath".to_string(),
2602 args: vec![arg],
2603 },
2604 "which" | "wh" => ExprKind::FuncCall {
2605 name: "which".to_string(),
2606 args: vec![arg],
2607 },
2608 "eval" => ExprKind::Eval(Box::new(arg)),
2610 "require" => ExprKind::Require(Box::new(arg)),
2611 "study" => ExprKind::Study(Box::new(arg)),
2612 "snake_case" | "sc" => ExprKind::FuncCall {
2614 name: "snake_case".to_string(),
2615 args: vec![arg],
2616 },
2617 "camel_case" | "cc" => ExprKind::FuncCall {
2618 name: "camel_case".to_string(),
2619 args: vec![arg],
2620 },
2621 "kebab_case" | "kc" => ExprKind::FuncCall {
2622 name: "kebab_case".to_string(),
2623 args: vec![arg],
2624 },
2625 "to_json" | "tj" => ExprKind::FuncCall {
2627 name: "to_json".to_string(),
2628 args: vec![arg],
2629 },
2630 "to_yaml" | "ty" => ExprKind::FuncCall {
2631 name: "to_yaml".to_string(),
2632 args: vec![arg],
2633 },
2634 "to_toml" | "tt" => ExprKind::FuncCall {
2635 name: "to_toml".to_string(),
2636 args: vec![arg],
2637 },
2638 "to_csv" | "tc" => ExprKind::FuncCall {
2639 name: "to_csv".to_string(),
2640 args: vec![arg],
2641 },
2642 "to_xml" | "tx" => ExprKind::FuncCall {
2643 name: "to_xml".to_string(),
2644 args: vec![arg],
2645 },
2646 "to_html" | "th" => ExprKind::FuncCall {
2647 name: "to_html".to_string(),
2648 args: vec![arg],
2649 },
2650 "to_markdown" | "to_md" | "tmd" => ExprKind::FuncCall {
2651 name: "to_markdown".to_string(),
2652 args: vec![arg],
2653 },
2654 "xopen" | "xo" => ExprKind::FuncCall {
2655 name: "xopen".to_string(),
2656 args: vec![arg],
2657 },
2658 "clip" | "clipboard" | "pbcopy" => ExprKind::FuncCall {
2659 name: "clip".to_string(),
2660 args: vec![arg],
2661 },
2662 "to_table" | "table" | "tbl" => ExprKind::FuncCall {
2663 name: "to_table".to_string(),
2664 args: vec![arg],
2665 },
2666 "sparkline" | "spark" => ExprKind::FuncCall {
2667 name: "sparkline".to_string(),
2668 args: vec![arg],
2669 },
2670 "bar_chart" | "bars" => ExprKind::FuncCall {
2671 name: "bar_chart".to_string(),
2672 args: vec![arg],
2673 },
2674 "flame" | "flamechart" => ExprKind::FuncCall {
2675 name: "flame".to_string(),
2676 args: vec![arg],
2677 },
2678 "ddump" | "dd" => ExprKind::FuncCall {
2679 name: "ddump".to_string(),
2680 args: vec![arg],
2681 },
2682 "say" => {
2683 if crate::no_interop_mode() {
2684 return Err(
2685 self.syntax_err("stryke uses `p` instead of `say` (--no-interop)", line)
2686 );
2687 }
2688 ExprKind::Say {
2689 handle: None,
2690 args: vec![arg],
2691 }
2692 }
2693 "p" => ExprKind::Say {
2694 handle: None,
2695 args: vec![arg],
2696 },
2697 "print" => ExprKind::Print {
2698 handle: None,
2699 args: vec![arg],
2700 },
2701 "warn" => ExprKind::Warn(vec![arg]),
2702 "die" => ExprKind::Die(vec![arg]),
2703 "stringify" | "str" => ExprKind::FuncCall {
2704 name: "stringify".to_string(),
2705 args: vec![arg],
2706 },
2707 "json_decode" | "jd" => ExprKind::FuncCall {
2708 name: "json_decode".to_string(),
2709 args: vec![arg],
2710 },
2711 "yaml_decode" | "yd" => ExprKind::FuncCall {
2712 name: "yaml_decode".to_string(),
2713 args: vec![arg],
2714 },
2715 "toml_decode" | "td" => ExprKind::FuncCall {
2716 name: "toml_decode".to_string(),
2717 args: vec![arg],
2718 },
2719 "xml_decode" | "xd" => ExprKind::FuncCall {
2720 name: "xml_decode".to_string(),
2721 args: vec![arg],
2722 },
2723 "json_encode" | "je" => ExprKind::FuncCall {
2724 name: "json_encode".to_string(),
2725 args: vec![arg],
2726 },
2727 "yaml_encode" | "ye" => ExprKind::FuncCall {
2728 name: "yaml_encode".to_string(),
2729 args: vec![arg],
2730 },
2731 "toml_encode" | "te" => ExprKind::FuncCall {
2732 name: "toml_encode".to_string(),
2733 args: vec![arg],
2734 },
2735 "xml_encode" | "xe" => ExprKind::FuncCall {
2736 name: "xml_encode".to_string(),
2737 args: vec![arg],
2738 },
2739 "base64_encode" | "b64e" => ExprKind::FuncCall {
2741 name: "base64_encode".to_string(),
2742 args: vec![arg],
2743 },
2744 "base64_decode" | "b64d" => ExprKind::FuncCall {
2745 name: "base64_decode".to_string(),
2746 args: vec![arg],
2747 },
2748 "hex_encode" | "hxe" => ExprKind::FuncCall {
2749 name: "hex_encode".to_string(),
2750 args: vec![arg],
2751 },
2752 "hex_decode" | "hxd" => ExprKind::FuncCall {
2753 name: "hex_decode".to_string(),
2754 args: vec![arg],
2755 },
2756 "url_encode" | "uri_escape" | "ue" => ExprKind::FuncCall {
2757 name: "url_encode".to_string(),
2758 args: vec![arg],
2759 },
2760 "url_decode" | "uri_unescape" | "ud" => ExprKind::FuncCall {
2761 name: "url_decode".to_string(),
2762 args: vec![arg],
2763 },
2764 "gzip" | "gz" => ExprKind::FuncCall {
2765 name: "gzip".to_string(),
2766 args: vec![arg],
2767 },
2768 "gunzip" | "ugz" => ExprKind::FuncCall {
2769 name: "gunzip".to_string(),
2770 args: vec![arg],
2771 },
2772 "zstd" | "zst" => ExprKind::FuncCall {
2773 name: "zstd".to_string(),
2774 args: vec![arg],
2775 },
2776 "zstd_decode" | "uzst" => ExprKind::FuncCall {
2777 name: "zstd_decode".to_string(),
2778 args: vec![arg],
2779 },
2780 "sha256" | "s256" => ExprKind::FuncCall {
2782 name: "sha256".to_string(),
2783 args: vec![arg],
2784 },
2785 "sha1" | "s1" => ExprKind::FuncCall {
2786 name: "sha1".to_string(),
2787 args: vec![arg],
2788 },
2789 "md5" | "m5" => ExprKind::FuncCall {
2790 name: "md5".to_string(),
2791 args: vec![arg],
2792 },
2793 "uuid" | "uid" => ExprKind::FuncCall {
2794 name: "uuid".to_string(),
2795 args: vec![arg],
2796 },
2797 "datetime_utc" | "utc" => ExprKind::FuncCall {
2799 name: "datetime_utc".to_string(),
2800 args: vec![arg],
2801 },
2802 "e" | "fore" | "ep" => ExprKind::ForEachExpr {
2805 block: vec![Statement {
2806 label: None,
2807 kind: StmtKind::Expression(Expr {
2808 kind: ExprKind::Say {
2809 handle: None,
2810 args: vec![Expr {
2811 kind: ExprKind::ScalarVar("_".into()),
2812 line,
2813 }],
2814 },
2815 line,
2816 }),
2817 line,
2818 }],
2819 list: Box::new(arg),
2820 },
2821 _ => ExprKind::FuncCall {
2823 name: name.to_string(),
2824 args: vec![arg],
2825 },
2826 };
2827 Ok(Expr { kind, line })
2828 }
2829
2830 fn parse_thread_stage_with_block(&mut self, name: &str, line: usize) -> PerlResult<Expr> {
2833 let block = self.parse_block()?;
2834 let placeholder = self.pipe_placeholder_list(line);
2836
2837 match name {
2838 "map" | "flat_map" | "maps" | "flat_maps" => {
2839 let flatten_array_refs = matches!(name, "flat_map" | "flat_maps");
2840 let stream = matches!(name, "maps" | "flat_maps");
2841 Ok(Expr {
2842 kind: ExprKind::MapExpr {
2843 block,
2844 list: Box::new(placeholder),
2845 flatten_array_refs,
2846 stream,
2847 },
2848 line,
2849 })
2850 }
2851 "grep" | "greps" | "filter" | "fi" | "find_all" | "gr" => {
2852 let keyword = match name {
2853 "grep" | "gr" => crate::ast::GrepBuiltinKeyword::Grep,
2854 "greps" => crate::ast::GrepBuiltinKeyword::Greps,
2855 "filter" | "fi" => crate::ast::GrepBuiltinKeyword::Filter,
2856 "find_all" => crate::ast::GrepBuiltinKeyword::FindAll,
2857 _ => unreachable!(),
2858 };
2859 Ok(Expr {
2860 kind: ExprKind::GrepExpr {
2861 block,
2862 list: Box::new(placeholder),
2863 keyword,
2864 },
2865 line,
2866 })
2867 }
2868 "sort" | "so" => Ok(Expr {
2869 kind: ExprKind::SortExpr {
2870 cmp: Some(SortComparator::Block(block)),
2871 list: Box::new(placeholder),
2872 },
2873 line,
2874 }),
2875 "reduce" | "rd" => Ok(Expr {
2876 kind: ExprKind::ReduceExpr {
2877 block,
2878 list: Box::new(placeholder),
2879 },
2880 line,
2881 }),
2882 "fore" | "e" | "ep" => Ok(Expr {
2883 kind: ExprKind::ForEachExpr {
2884 block,
2885 list: Box::new(placeholder),
2886 },
2887 line,
2888 }),
2889 "pmap" | "pflat_map" | "pmaps" | "pflat_maps" => Ok(Expr {
2890 kind: ExprKind::PMapExpr {
2891 block,
2892 list: Box::new(placeholder),
2893 progress: None,
2894 flat_outputs: name == "pflat_map" || name == "pflat_maps",
2895 on_cluster: None,
2896 stream: name == "pmaps" || name == "pflat_maps",
2897 },
2898 line,
2899 }),
2900 "pgrep" | "pgreps" => Ok(Expr {
2901 kind: ExprKind::PGrepExpr {
2902 block,
2903 list: Box::new(placeholder),
2904 progress: None,
2905 stream: name == "pgreps",
2906 },
2907 line,
2908 }),
2909 "pfor" => Ok(Expr {
2910 kind: ExprKind::PForExpr {
2911 block,
2912 list: Box::new(placeholder),
2913 progress: None,
2914 },
2915 line,
2916 }),
2917 "preduce" => Ok(Expr {
2918 kind: ExprKind::PReduceExpr {
2919 block,
2920 list: Box::new(placeholder),
2921 progress: None,
2922 },
2923 line,
2924 }),
2925 "pcache" => Ok(Expr {
2926 kind: ExprKind::PcacheExpr {
2927 block,
2928 list: Box::new(placeholder),
2929 progress: None,
2930 },
2931 line,
2932 }),
2933 "psort" => Ok(Expr {
2934 kind: ExprKind::PSortExpr {
2935 cmp: Some(block),
2936 list: Box::new(placeholder),
2937 progress: None,
2938 },
2939 line,
2940 }),
2941 _ => {
2942 let code_ref = Expr {
2949 kind: ExprKind::CodeRef {
2950 params: vec![],
2951 body: block,
2952 },
2953 line,
2954 };
2955 let args = if Self::is_block_then_list_pipe_builtin(name) {
2956 vec![code_ref, placeholder]
2957 } else {
2958 vec![code_ref]
2959 };
2960 Ok(Expr {
2961 kind: ExprKind::FuncCall {
2962 name: name.to_string(),
2963 args,
2964 },
2965 line,
2966 })
2967 }
2968 }
2969 }
2970
2971 fn parse_tie_stmt(&mut self) -> PerlResult<Statement> {
2973 let line = self.peek_line();
2974 self.advance(); let target = match self.peek().clone() {
2976 Token::HashVar(h) => {
2977 self.advance();
2978 TieTarget::Hash(h)
2979 }
2980 Token::ArrayVar(a) => {
2981 self.advance();
2982 TieTarget::Array(a)
2983 }
2984 Token::ScalarVar(s) => {
2985 self.advance();
2986 TieTarget::Scalar(s)
2987 }
2988 tok => {
2989 return Err(self.syntax_err(
2990 format!("tie expects $scalar, @array, or %hash, got {:?}", tok),
2991 self.peek_line(),
2992 ));
2993 }
2994 };
2995 self.expect(&Token::Comma)?;
2996 let class = self.parse_assign_expr()?;
2997 let mut args = Vec::new();
2998 while self.eat(&Token::Comma) {
2999 if matches!(self.peek(), Token::Semicolon | Token::RBrace | Token::Eof) {
3000 break;
3001 }
3002 args.push(self.parse_assign_expr()?);
3003 }
3004 self.eat(&Token::Semicolon);
3005 Ok(Statement {
3006 label: None,
3007 kind: StmtKind::Tie {
3008 target,
3009 class,
3010 args,
3011 },
3012 line,
3013 })
3014 }
3015
3016 fn parse_given(&mut self) -> PerlResult<Statement> {
3018 let line = self.peek_line();
3019 self.advance();
3020 self.expect(&Token::LParen)?;
3021 let topic = self.parse_expression()?;
3022 self.expect(&Token::RParen)?;
3023 let body = self.parse_block()?;
3024 self.eat(&Token::Semicolon);
3025 Ok(Statement {
3026 label: None,
3027 kind: StmtKind::Given { topic, body },
3028 line,
3029 })
3030 }
3031
3032 fn parse_when_stmt(&mut self) -> PerlResult<Statement> {
3034 let line = self.peek_line();
3035 self.advance();
3036 self.expect(&Token::LParen)?;
3037 let cond = self.parse_expression()?;
3038 self.expect(&Token::RParen)?;
3039 let body = self.parse_block()?;
3040 self.eat(&Token::Semicolon);
3041 Ok(Statement {
3042 label: None,
3043 kind: StmtKind::When { cond, body },
3044 line,
3045 })
3046 }
3047
3048 fn parse_default_stmt(&mut self) -> PerlResult<Statement> {
3050 let line = self.peek_line();
3051 self.advance();
3052 let body = self.parse_block()?;
3053 self.eat(&Token::Semicolon);
3054 Ok(Statement {
3055 label: None,
3056 kind: StmtKind::DefaultCase { body },
3057 line,
3058 })
3059 }
3060
3061 fn parse_cond_expr(&mut self, line: usize) -> PerlResult<Expr> {
3067 self.expect(&Token::LBrace)?;
3068
3069 let mut arms: Vec<(Expr, Block)> = Vec::new();
3070 let mut else_block: Option<Block> = None;
3071
3072 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
3073 let arm_line = self.peek_line();
3074
3075 let is_default = matches!(self.peek(), Token::Ident(ref s) if s == "default")
3077 && matches!(self.peek_at(1), Token::FatArrow);
3078
3079 if is_default {
3080 self.advance(); self.advance(); let body = if matches!(self.peek(), Token::LBrace) {
3083 self.parse_block()?
3084 } else {
3085 let expr = self.parse_assign_expr()?;
3086 vec![Statement {
3087 label: None,
3088 kind: StmtKind::Expression(expr),
3089 line: arm_line,
3090 }]
3091 };
3092 else_block = Some(body);
3093 self.eat(&Token::Comma);
3094 break; }
3096
3097 let condition = self.parse_assign_expr()?;
3099 self.expect(&Token::FatArrow)?;
3100
3101 let body = if matches!(self.peek(), Token::LBrace) {
3102 self.parse_block()?
3103 } else {
3104 let expr = self.parse_assign_expr()?;
3105 vec![Statement {
3106 label: None,
3107 kind: StmtKind::Expression(expr),
3108 line: arm_line,
3109 }]
3110 };
3111
3112 arms.push((condition, body));
3113 self.eat(&Token::Comma);
3114 }
3115
3116 self.expect(&Token::RBrace)?;
3117
3118 if arms.is_empty() {
3119 return Err(self.syntax_err("cond requires at least one condition arm", line));
3120 }
3121
3122 let (first_cond, first_body) = arms.remove(0);
3124 let elsifs: Vec<(Expr, Block)> = arms;
3125
3126 let if_stmt = Statement {
3128 label: None,
3129 kind: StmtKind::If {
3130 condition: first_cond,
3131 body: first_body,
3132 elsifs,
3133 else_block,
3134 },
3135 line,
3136 };
3137 let inner = Expr {
3138 kind: ExprKind::CodeRef {
3139 params: vec![],
3140 body: vec![if_stmt],
3141 },
3142 line,
3143 };
3144 Ok(Expr {
3145 kind: ExprKind::Do(Box::new(inner)),
3146 line,
3147 })
3148 }
3149
3150 fn parse_algebraic_match_expr(&mut self, line: usize) -> PerlResult<Expr> {
3152 self.expect(&Token::LParen)?;
3153 let subject = self.parse_expression()?;
3154 self.expect(&Token::RParen)?;
3155 self.expect(&Token::LBrace)?;
3156 let mut arms = Vec::new();
3157 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
3158 if self.eat(&Token::Semicolon) {
3159 continue;
3160 }
3161 let pattern = self.parse_match_pattern()?;
3162 let guard = if matches!(self.peek(), Token::Ident(ref s) if s == "if") {
3163 self.advance();
3164 Some(Box::new(self.parse_assign_expr()?))
3167 } else {
3168 None
3169 };
3170 self.expect(&Token::FatArrow)?;
3171 let body = self.parse_assign_expr()?;
3173 arms.push(MatchArm {
3174 pattern,
3175 guard,
3176 body,
3177 });
3178 self.eat(&Token::Comma);
3179 }
3180 self.expect(&Token::RBrace)?;
3181 Ok(Expr {
3182 kind: ExprKind::AlgebraicMatch {
3183 subject: Box::new(subject),
3184 arms,
3185 },
3186 line,
3187 })
3188 }
3189
3190 fn parse_match_pattern(&mut self) -> PerlResult<MatchPattern> {
3191 match self.peek().clone() {
3192 Token::Regex(pattern, flags, _delim) => {
3193 self.advance();
3194 Ok(MatchPattern::Regex { pattern, flags })
3195 }
3196 Token::Ident(ref s) if s == "_" => {
3197 self.advance();
3198 Ok(MatchPattern::Any)
3199 }
3200 Token::Ident(ref s) if s == "Some" => {
3201 self.advance();
3202 self.expect(&Token::LParen)?;
3203 let name = self.parse_scalar_var_name()?;
3204 self.expect(&Token::RParen)?;
3205 Ok(MatchPattern::OptionSome(name))
3206 }
3207 Token::LBracket => self.parse_match_array_pattern(),
3208 Token::LBrace => self.parse_match_hash_pattern(),
3209 Token::LParen => {
3210 self.advance();
3211 let e = self.parse_expression()?;
3212 self.expect(&Token::RParen)?;
3213 Ok(MatchPattern::Value(Box::new(e)))
3214 }
3215 _ => {
3216 let e = self.parse_assign_expr()?;
3217 Ok(MatchPattern::Value(Box::new(e)))
3218 }
3219 }
3220 }
3221
3222 fn parse_match_array_elems_until_rbracket(&mut self) -> PerlResult<Vec<MatchArrayElem>> {
3224 let mut elems = Vec::new();
3225 if self.eat(&Token::RBracket) {
3226 return Ok(vec![]);
3227 }
3228 loop {
3229 if matches!(self.peek(), Token::Star) {
3230 self.advance();
3231 elems.push(MatchArrayElem::Rest);
3232 self.eat(&Token::Comma);
3233 if !matches!(self.peek(), Token::RBracket) {
3234 return Err(self.syntax_err(
3235 "`*` must be the last element in an array match pattern",
3236 self.peek_line(),
3237 ));
3238 }
3239 self.expect(&Token::RBracket)?;
3240 return Ok(elems);
3241 }
3242 if let Token::ArrayVar(name) = self.peek().clone() {
3243 self.advance();
3244 elems.push(MatchArrayElem::RestBind(name));
3245 self.eat(&Token::Comma);
3246 if !matches!(self.peek(), Token::RBracket) {
3247 return Err(self.syntax_err(
3248 "`@name` rest bind must be the last element in an array match pattern",
3249 self.peek_line(),
3250 ));
3251 }
3252 self.expect(&Token::RBracket)?;
3253 return Ok(elems);
3254 }
3255 if let Token::ScalarVar(name) = self.peek().clone() {
3256 self.advance();
3257 elems.push(MatchArrayElem::CaptureScalar(name));
3258 if self.eat(&Token::Comma) {
3259 if matches!(self.peek(), Token::RBracket) {
3260 break;
3261 }
3262 continue;
3263 }
3264 break;
3265 }
3266 let e = self.parse_assign_expr()?;
3267 elems.push(MatchArrayElem::Expr(e));
3268 if self.eat(&Token::Comma) {
3269 if matches!(self.peek(), Token::RBracket) {
3270 break;
3271 }
3272 continue;
3273 }
3274 break;
3275 }
3276 self.expect(&Token::RBracket)?;
3277 Ok(elems)
3278 }
3279
3280 fn parse_match_array_pattern(&mut self) -> PerlResult<MatchPattern> {
3281 self.expect(&Token::LBracket)?;
3282 let elems = self.parse_match_array_elems_until_rbracket()?;
3283 Ok(MatchPattern::Array(elems))
3284 }
3285
3286 fn parse_match_hash_pattern(&mut self) -> PerlResult<MatchPattern> {
3287 self.expect(&Token::LBrace)?;
3288 let mut pairs = Vec::new();
3289 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
3290 if self.eat(&Token::Semicolon) {
3291 continue;
3292 }
3293 let key = self.parse_assign_expr()?;
3294 self.expect(&Token::FatArrow)?;
3295 match self.advance().0 {
3296 Token::Ident(ref s) if s == "_" => {
3297 pairs.push(MatchHashPair::KeyOnly { key });
3298 }
3299 Token::ScalarVar(name) => {
3300 pairs.push(MatchHashPair::Capture { key, name });
3301 }
3302 tok => {
3303 return Err(self.syntax_err(
3304 format!(
3305 "hash match pattern must bind with `=> $name` or `=> _`, got {:?}",
3306 tok
3307 ),
3308 self.peek_line(),
3309 ));
3310 }
3311 }
3312 self.eat(&Token::Comma);
3313 }
3314 self.expect(&Token::RBrace)?;
3315 Ok(MatchPattern::Hash(pairs))
3316 }
3317
3318 fn parse_eval_timeout(&mut self) -> PerlResult<Statement> {
3320 let line = self.peek_line();
3321 self.advance();
3322 let timeout = self.parse_postfix()?;
3323 let body = self.parse_block_or_bareword_block_no_args()?;
3324 self.eat(&Token::Semicolon);
3325 Ok(Statement {
3326 label: None,
3327 kind: StmtKind::EvalTimeout { timeout, body },
3328 line,
3329 })
3330 }
3331
3332 fn mark_match_scalar_g_for_boolean_condition(cond: &mut Expr) {
3333 match &mut cond.kind {
3334 ExprKind::Match {
3335 flags, scalar_g, ..
3336 } if flags.contains('g') => {
3337 *scalar_g = true;
3338 }
3339 ExprKind::UnaryOp {
3340 op: UnaryOp::LogNot,
3341 expr,
3342 } => {
3343 if let ExprKind::Match {
3344 flags, scalar_g, ..
3345 } = &mut expr.kind
3346 {
3347 if flags.contains('g') {
3348 *scalar_g = true;
3349 }
3350 }
3351 }
3352 _ => {}
3353 }
3354 }
3355
3356 fn parse_if(&mut self) -> PerlResult<Statement> {
3357 let line = self.peek_line();
3358 self.advance(); if matches!(self.peek(), Token::Ident(ref s) if s == "let") {
3360 if crate::compat_mode() {
3361 return Err(self.syntax_err(
3362 "`if let` is a stryke extension (disabled by --compat)",
3363 line,
3364 ));
3365 }
3366 return self.parse_if_let(line);
3367 }
3368 self.expect(&Token::LParen)?;
3369 let mut cond = self.parse_expression()?;
3370 Self::mark_match_scalar_g_for_boolean_condition(&mut cond);
3371 self.expect(&Token::RParen)?;
3372 let body = self.parse_block()?;
3373
3374 let mut elsifs = Vec::new();
3375 let mut else_block = None;
3376
3377 loop {
3378 if let Token::Ident(ref kw) = self.peek().clone() {
3379 if kw == "elsif" {
3380 self.advance();
3381 self.expect(&Token::LParen)?;
3382 let mut c = self.parse_expression()?;
3383 Self::mark_match_scalar_g_for_boolean_condition(&mut c);
3384 self.expect(&Token::RParen)?;
3385 let b = self.parse_block()?;
3386 elsifs.push((c, b));
3387 continue;
3388 }
3389 if kw == "else" {
3390 self.advance();
3391 else_block = Some(self.parse_block()?);
3392 }
3393 }
3394 break;
3395 }
3396
3397 Ok(Statement {
3398 label: None,
3399 kind: StmtKind::If {
3400 condition: cond,
3401 body,
3402 elsifs,
3403 else_block,
3404 },
3405 line,
3406 })
3407 }
3408
3409 fn parse_if_let(&mut self, line: usize) -> PerlResult<Statement> {
3411 self.advance(); let pattern = self.parse_match_pattern()?;
3413 self.expect(&Token::Assign)?;
3414 self.suppress_scalar_hash_brace = self.suppress_scalar_hash_brace.saturating_add(1);
3416 let rhs = self.parse_assign_expr();
3417 self.suppress_scalar_hash_brace = self.suppress_scalar_hash_brace.saturating_sub(1);
3418 let rhs = rhs?;
3419 let then_block = self.parse_block()?;
3420 let else_block_opt = match self.peek().clone() {
3421 Token::Ident(ref kw) if kw == "else" => {
3422 self.advance();
3423 Some(self.parse_block()?)
3424 }
3425 Token::Ident(ref kw) if kw == "elsif" => {
3426 return Err(self.syntax_err(
3427 "`if let` does not support `elsif`; use `else { }` or a full `match`",
3428 self.peek_line(),
3429 ));
3430 }
3431 _ => None,
3432 };
3433 let then_expr = Self::expr_do_anon_block(then_block, line);
3434 let else_expr = if let Some(eb) = else_block_opt {
3435 Self::expr_do_anon_block(eb, line)
3436 } else {
3437 Expr {
3438 kind: ExprKind::Undef,
3439 line,
3440 }
3441 };
3442 let arms = vec![
3443 MatchArm {
3444 pattern,
3445 guard: None,
3446 body: then_expr,
3447 },
3448 MatchArm {
3449 pattern: MatchPattern::Any,
3450 guard: None,
3451 body: else_expr,
3452 },
3453 ];
3454 Ok(Statement {
3455 label: None,
3456 kind: StmtKind::Expression(Expr {
3457 kind: ExprKind::AlgebraicMatch {
3458 subject: Box::new(rhs),
3459 arms,
3460 },
3461 line,
3462 }),
3463 line,
3464 })
3465 }
3466
3467 fn expr_do_anon_block(block: Block, outer_line: usize) -> Expr {
3468 let inner_line = block.first().map(|s| s.line).unwrap_or(outer_line);
3469 Expr {
3470 kind: ExprKind::Do(Box::new(Expr {
3471 kind: ExprKind::CodeRef {
3472 params: vec![],
3473 body: block,
3474 },
3475 line: inner_line,
3476 })),
3477 line: outer_line,
3478 }
3479 }
3480
3481 fn parse_unless(&mut self) -> PerlResult<Statement> {
3482 let line = self.peek_line();
3483 self.advance(); self.expect(&Token::LParen)?;
3485 let mut cond = self.parse_expression()?;
3486 Self::mark_match_scalar_g_for_boolean_condition(&mut cond);
3487 self.expect(&Token::RParen)?;
3488 let body = self.parse_block()?;
3489 let else_block = if let Token::Ident(ref kw) = self.peek().clone() {
3490 if kw == "else" {
3491 self.advance();
3492 Some(self.parse_block()?)
3493 } else {
3494 None
3495 }
3496 } else {
3497 None
3498 };
3499 Ok(Statement {
3500 label: None,
3501 kind: StmtKind::Unless {
3502 condition: cond,
3503 body,
3504 else_block,
3505 },
3506 line,
3507 })
3508 }
3509
3510 fn parse_while(&mut self) -> PerlResult<Statement> {
3511 let line = self.peek_line();
3512 self.advance(); if matches!(self.peek(), Token::Ident(ref s) if s == "let") {
3514 if crate::compat_mode() {
3515 return Err(self.syntax_err(
3516 "`while let` is a stryke extension (disabled by --compat)",
3517 line,
3518 ));
3519 }
3520 return self.parse_while_let(line);
3521 }
3522 self.expect(&Token::LParen)?;
3523 let mut cond = self.parse_expression()?;
3524 Self::mark_match_scalar_g_for_boolean_condition(&mut cond);
3525 self.expect(&Token::RParen)?;
3526 let body = self.parse_block()?;
3527 let continue_block = self.parse_optional_continue_block()?;
3528 Ok(Statement {
3529 label: None,
3530 kind: StmtKind::While {
3531 condition: cond,
3532 body,
3533 label: None,
3534 continue_block,
3535 },
3536 line,
3537 })
3538 }
3539
3540 fn parse_while_let(&mut self, line: usize) -> PerlResult<Statement> {
3543 self.advance(); let pattern = self.parse_match_pattern()?;
3545 self.expect(&Token::Assign)?;
3546 self.suppress_scalar_hash_brace = self.suppress_scalar_hash_brace.saturating_add(1);
3547 let rhs = self.parse_assign_expr();
3548 self.suppress_scalar_hash_brace = self.suppress_scalar_hash_brace.saturating_sub(1);
3549 let rhs = rhs?;
3550 let mut user_body = self.parse_block()?;
3551 let continue_block = self.parse_optional_continue_block()?;
3552 user_body.push(Statement::new(
3553 StmtKind::Expression(Expr {
3554 kind: ExprKind::Integer(1),
3555 line,
3556 }),
3557 line,
3558 ));
3559 let tmp = format!("__while_let_{}", self.alloc_desugar_tmp());
3560 let match_expr = Expr {
3561 kind: ExprKind::AlgebraicMatch {
3562 subject: Box::new(rhs),
3563 arms: vec![
3564 MatchArm {
3565 pattern,
3566 guard: None,
3567 body: Self::expr_do_anon_block(user_body, line),
3568 },
3569 MatchArm {
3570 pattern: MatchPattern::Any,
3571 guard: None,
3572 body: Expr {
3573 kind: ExprKind::Integer(0),
3574 line,
3575 },
3576 },
3577 ],
3578 },
3579 line,
3580 };
3581 let my_stmt = Statement::new(
3582 StmtKind::My(vec![VarDecl {
3583 sigil: Sigil::Scalar,
3584 name: tmp.clone(),
3585 initializer: Some(match_expr),
3586 frozen: false,
3587 type_annotation: None,
3588 }]),
3589 line,
3590 );
3591 let unless_last = Statement::new(
3592 StmtKind::Unless {
3593 condition: Expr {
3594 kind: ExprKind::ScalarVar(tmp),
3595 line,
3596 },
3597 body: vec![Statement::new(StmtKind::Last(None), line)],
3598 else_block: None,
3599 },
3600 line,
3601 );
3602 Ok(Statement::new(
3603 StmtKind::While {
3604 condition: Expr {
3605 kind: ExprKind::Integer(1),
3606 line,
3607 },
3608 body: vec![my_stmt, unless_last],
3609 label: None,
3610 continue_block,
3611 },
3612 line,
3613 ))
3614 }
3615
3616 fn parse_until(&mut self) -> PerlResult<Statement> {
3617 let line = self.peek_line();
3618 self.advance(); self.expect(&Token::LParen)?;
3620 let mut cond = self.parse_expression()?;
3621 Self::mark_match_scalar_g_for_boolean_condition(&mut cond);
3622 self.expect(&Token::RParen)?;
3623 let body = self.parse_block()?;
3624 let continue_block = self.parse_optional_continue_block()?;
3625 Ok(Statement {
3626 label: None,
3627 kind: StmtKind::Until {
3628 condition: cond,
3629 body,
3630 label: None,
3631 continue_block,
3632 },
3633 line,
3634 })
3635 }
3636
3637 fn parse_optional_continue_block(&mut self) -> PerlResult<Option<Block>> {
3639 if let Token::Ident(ref kw) = self.peek().clone() {
3640 if kw == "continue" {
3641 self.advance();
3642 return Ok(Some(self.parse_block()?));
3643 }
3644 }
3645 Ok(None)
3646 }
3647
3648 fn parse_for_or_foreach(&mut self) -> PerlResult<Statement> {
3649 let line = self.peek_line();
3650 self.advance(); match self.peek() {
3656 Token::LParen => {
3657 let saved = self.pos;
3662 self.advance(); let mut depth = 1;
3665 let mut has_semi = false;
3666 let mut scan = self.pos;
3667 while scan < self.tokens.len() {
3668 match &self.tokens[scan].0 {
3669 Token::LParen => depth += 1,
3670 Token::RParen => {
3671 depth -= 1;
3672 if depth == 0 {
3673 break;
3674 }
3675 }
3676 Token::Semicolon if depth == 1 => {
3677 has_semi = true;
3678 break;
3679 }
3680 _ => {}
3681 }
3682 scan += 1;
3683 }
3684 self.pos = saved;
3685
3686 if has_semi {
3687 self.parse_c_style_for(line)
3688 } else {
3689 self.expect(&Token::LParen)?;
3691 let list = self.parse_expression()?;
3692 self.expect(&Token::RParen)?;
3693 let body = self.parse_block()?;
3694 let continue_block = self.parse_optional_continue_block()?;
3695 Ok(Statement {
3696 label: None,
3697 kind: StmtKind::Foreach {
3698 var: "_".to_string(),
3699 list,
3700 body,
3701 label: None,
3702 continue_block,
3703 },
3704 line,
3705 })
3706 }
3707 }
3708 Token::Ident(ref kw) if kw == "my" => {
3709 self.advance(); let var = self.parse_scalar_var_name()?;
3711 self.expect(&Token::LParen)?;
3712 let list = self.parse_expression()?;
3713 self.expect(&Token::RParen)?;
3714 let body = self.parse_block()?;
3715 let continue_block = self.parse_optional_continue_block()?;
3716 Ok(Statement {
3717 label: None,
3718 kind: StmtKind::Foreach {
3719 var,
3720 list,
3721 body,
3722 label: None,
3723 continue_block,
3724 },
3725 line,
3726 })
3727 }
3728 Token::ScalarVar(_) => {
3729 let var = self.parse_scalar_var_name()?;
3730 self.expect(&Token::LParen)?;
3731 let list = self.parse_expression()?;
3732 self.expect(&Token::RParen)?;
3733 let body = self.parse_block()?;
3734 let continue_block = self.parse_optional_continue_block()?;
3735 Ok(Statement {
3736 label: None,
3737 kind: StmtKind::Foreach {
3738 var,
3739 list,
3740 body,
3741 label: None,
3742 continue_block,
3743 },
3744 line,
3745 })
3746 }
3747 _ => self.parse_c_style_for(line),
3748 }
3749 }
3750
3751 fn parse_c_style_for(&mut self, line: usize) -> PerlResult<Statement> {
3752 self.expect(&Token::LParen)?;
3753 let init = if self.eat(&Token::Semicolon) {
3754 None
3755 } else {
3756 let s = self.parse_statement()?;
3757 self.eat(&Token::Semicolon);
3758 Some(Box::new(s))
3759 };
3760 let mut condition = if matches!(self.peek(), Token::Semicolon) {
3761 None
3762 } else {
3763 Some(self.parse_expression()?)
3764 };
3765 if let Some(ref mut c) = condition {
3766 Self::mark_match_scalar_g_for_boolean_condition(c);
3767 }
3768 self.expect(&Token::Semicolon)?;
3769 let step = if matches!(self.peek(), Token::RParen) {
3770 None
3771 } else {
3772 Some(self.parse_expression()?)
3773 };
3774 self.expect(&Token::RParen)?;
3775 let body = self.parse_block()?;
3776 let continue_block = self.parse_optional_continue_block()?;
3777 Ok(Statement {
3778 label: None,
3779 kind: StmtKind::For {
3780 init,
3781 condition,
3782 step,
3783 body,
3784 label: None,
3785 continue_block,
3786 },
3787 line,
3788 })
3789 }
3790
3791 fn parse_foreach(&mut self) -> PerlResult<Statement> {
3792 let line = self.peek_line();
3793 self.advance(); let var = match self.peek() {
3795 Token::Ident(ref kw) if kw == "my" => {
3796 self.advance();
3797 self.parse_scalar_var_name()?
3798 }
3799 Token::ScalarVar(_) => self.parse_scalar_var_name()?,
3800 _ => "_".to_string(),
3801 };
3802 self.expect(&Token::LParen)?;
3803 let list = self.parse_expression()?;
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::Foreach {
3810 var,
3811 list,
3812 body,
3813 label: None,
3814 continue_block,
3815 },
3816 line,
3817 })
3818 }
3819
3820 fn parse_scalar_var_name(&mut self) -> PerlResult<String> {
3821 match self.advance() {
3822 (Token::ScalarVar(name), _) => Ok(name),
3823 (tok, line) => {
3824 Err(self.syntax_err(format!("Expected scalar variable, got {:?}", tok), line))
3825 }
3826 }
3827 }
3828
3829 fn parse_legacy_sub_prototype_tail(&mut self) -> PerlResult<String> {
3831 let mut s = String::new();
3832 loop {
3833 match self.peek().clone() {
3834 Token::RParen => {
3835 self.advance();
3836 break;
3837 }
3838 Token::Eof => {
3839 return Err(self.syntax_err(
3840 "Unterminated sub prototype (expected ')' before end of input)",
3841 self.peek_line(),
3842 ));
3843 }
3844 Token::ScalarVar(v) if v == ")" => {
3845 self.advance();
3848 s.push('$');
3849 if matches!(self.peek(), Token::LBrace) {
3850 break;
3851 }
3852 }
3853 Token::Ident(i) => {
3854 let i = i.clone();
3855 self.advance();
3856 s.push_str(&i);
3857 }
3858 Token::Semicolon => {
3859 self.advance();
3860 s.push(';');
3861 }
3862 Token::LParen => {
3863 self.advance();
3864 s.push('(');
3865 }
3866 Token::LBracket => {
3867 self.advance();
3868 s.push('[');
3869 }
3870 Token::RBracket => {
3871 self.advance();
3872 s.push(']');
3873 }
3874 Token::Backslash => {
3875 self.advance();
3876 s.push('\\');
3877 }
3878 Token::Comma => {
3879 self.advance();
3880 s.push(',');
3881 }
3882 Token::ScalarVar(v) => {
3883 let v = v.clone();
3884 self.advance();
3885 s.push('$');
3886 s.push_str(&v);
3887 }
3888 Token::ArrayVar(v) => {
3889 let v = v.clone();
3890 self.advance();
3891 s.push('@');
3892 s.push_str(&v);
3893 }
3894 Token::ArrayAt => {
3896 self.advance();
3897 s.push('@');
3898 }
3899 Token::HashVar(v) => {
3900 let v = v.clone();
3901 self.advance();
3902 s.push('%');
3903 s.push_str(&v);
3904 }
3905 Token::HashPercent => {
3906 self.advance();
3907 s.push('%');
3908 }
3909 Token::Plus => {
3910 self.advance();
3911 s.push('+');
3912 }
3913 Token::Minus => {
3914 self.advance();
3915 s.push('-');
3916 }
3917 Token::BitAnd => {
3918 self.advance();
3919 s.push('&');
3920 }
3921 tok => {
3922 return Err(self.syntax_err(
3923 format!("Unexpected token in sub prototype: {:?}", tok),
3924 self.peek_line(),
3925 ));
3926 }
3927 }
3928 }
3929 Ok(s)
3930 }
3931
3932 fn sub_signature_list_starts_here(&self) -> bool {
3933 match self.peek() {
3934 Token::LBrace | Token::LBracket => true,
3935 Token::ScalarVar(name) if name != "$$" && name != ")" => true,
3936 Token::ArrayVar(_) | Token::HashVar(_) => true,
3937 _ => false,
3938 }
3939 }
3940
3941 fn parse_sub_signature_hash_key(&mut self) -> PerlResult<String> {
3942 let (tok, line) = self.advance();
3943 match tok {
3944 Token::Ident(i) => Ok(i),
3945 Token::SingleString(s) | Token::DoubleString(s) => Ok(s),
3946 tok => Err(self.syntax_err(
3947 format!(
3948 "sub signature: expected hash key (identifier or string), got {:?}",
3949 tok
3950 ),
3951 line,
3952 )),
3953 }
3954 }
3955
3956 fn parse_sub_signature_param_list(&mut self) -> PerlResult<Vec<SubSigParam>> {
3957 let mut params = Vec::new();
3958 loop {
3959 if matches!(self.peek(), Token::RParen) {
3960 break;
3961 }
3962 match self.peek().clone() {
3963 Token::ScalarVar(name) => {
3964 if name == "$$" || name == ")" {
3965 return Err(self.syntax_err(
3966 format!(
3967 "`{name}` cannot start a stryke sub signature (use legacy prototype `($$)` etc.)"
3968 ),
3969 self.peek_line(),
3970 ));
3971 }
3972 self.advance();
3973 let ty = if self.eat(&Token::Colon) {
3974 match self.peek() {
3975 Token::Ident(ref tname) => {
3976 let tname = tname.clone();
3977 self.advance();
3978 Some(match tname.as_str() {
3979 "Int" => PerlTypeName::Int,
3980 "Str" => PerlTypeName::Str,
3981 "Float" => PerlTypeName::Float,
3982 "Bool" => PerlTypeName::Bool,
3983 "Array" => PerlTypeName::Array,
3984 "Hash" => PerlTypeName::Hash,
3985 "Ref" => PerlTypeName::Ref,
3986 "Any" => PerlTypeName::Any,
3987 _ => PerlTypeName::Struct(tname),
3988 })
3989 }
3990 _ => {
3991 return Err(self.syntax_err(
3992 "expected type name after `:` in sub signature",
3993 self.peek_line(),
3994 ));
3995 }
3996 }
3997 } else {
3998 None
3999 };
4000 let default = if self.eat(&Token::Assign) {
4002 Some(Box::new(self.parse_ternary()?))
4003 } else {
4004 None
4005 };
4006 params.push(SubSigParam::Scalar(name, ty, default));
4007 }
4008 Token::ArrayVar(name) => {
4009 self.advance();
4010 let default = if self.eat(&Token::Assign) {
4011 Some(Box::new(self.parse_ternary()?))
4012 } else {
4013 None
4014 };
4015 params.push(SubSigParam::Array(name, default));
4016 }
4017 Token::HashVar(name) => {
4018 self.advance();
4019 let default = if self.eat(&Token::Assign) {
4020 Some(Box::new(self.parse_ternary()?))
4021 } else {
4022 None
4023 };
4024 params.push(SubSigParam::Hash(name, default));
4025 }
4026 Token::LBracket => {
4027 self.advance();
4028 let elems = self.parse_match_array_elems_until_rbracket()?;
4029 params.push(SubSigParam::ArrayDestruct(elems));
4030 }
4031 Token::LBrace => {
4032 self.advance();
4033 let mut pairs = Vec::new();
4034 loop {
4035 if matches!(self.peek(), Token::RBrace | Token::Eof) {
4036 break;
4037 }
4038 if self.eat(&Token::Comma) {
4039 continue;
4040 }
4041 let key = self.parse_sub_signature_hash_key()?;
4042 self.expect(&Token::FatArrow)?;
4043 let bind = self.parse_scalar_var_name()?;
4044 pairs.push((key, bind));
4045 self.eat(&Token::Comma);
4046 }
4047 self.expect(&Token::RBrace)?;
4048 params.push(SubSigParam::HashDestruct(pairs));
4049 }
4050 tok => {
4051 return Err(self.syntax_err(
4052 format!(
4053 "expected `$name`, `[ ... ]`, or `{{ ... }}` in sub signature, got {:?}",
4054 tok
4055 ),
4056 self.peek_line(),
4057 ));
4058 }
4059 }
4060 match self.peek() {
4061 Token::Comma => {
4062 self.advance();
4063 if matches!(self.peek(), Token::RParen) {
4064 return Err(self.syntax_err(
4065 "trailing `,` before `)` in sub signature",
4066 self.peek_line(),
4067 ));
4068 }
4069 }
4070 Token::RParen => break,
4071 _ => {
4072 return Err(self.syntax_err(
4073 format!(
4074 "expected `,` or `)` after sub signature parameter, got {:?}",
4075 self.peek()
4076 ),
4077 self.peek_line(),
4078 ));
4079 }
4080 }
4081 }
4082 Ok(params)
4083 }
4084
4085 fn parse_sub_sig_or_prototype_opt(&mut self) -> PerlResult<(Vec<SubSigParam>, Option<String>)> {
4087 if !matches!(self.peek(), Token::LParen) {
4088 return Ok((vec![], None));
4089 }
4090 self.advance();
4091 if matches!(self.peek(), Token::RParen) {
4092 self.advance();
4093 return Ok((vec![], Some(String::new())));
4094 }
4095 if self.sub_signature_list_starts_here() {
4096 let params = self.parse_sub_signature_param_list()?;
4097 self.expect(&Token::RParen)?;
4098 return Ok((params, None));
4099 }
4100 let proto = self.parse_legacy_sub_prototype_tail()?;
4101 Ok((vec![], Some(proto)))
4102 }
4103
4104 fn parse_sub_attributes(&mut self) -> PerlResult<()> {
4106 while self.eat(&Token::Colon) {
4107 match self.advance() {
4108 (Token::Ident(_), _) => {}
4109 (tok, line) => {
4110 return Err(self.syntax_err(
4111 format!("Expected attribute name after `:`, got {:?}", tok),
4112 line,
4113 ));
4114 }
4115 }
4116 if self.eat(&Token::LParen) {
4117 let mut depth = 1usize;
4118 while depth > 0 {
4119 match self.advance().0 {
4120 Token::LParen => depth += 1,
4121 Token::RParen => {
4122 depth -= 1;
4123 }
4124 Token::Eof => {
4125 return Err(self.syntax_err(
4126 "Unterminated sub attribute argument list",
4127 self.peek_line(),
4128 ));
4129 }
4130 _ => {}
4131 }
4132 }
4133 }
4134 }
4135 Ok(())
4136 }
4137
4138 fn parse_sub_decl(&mut self, is_sub_keyword: bool) -> PerlResult<Statement> {
4139 let line = self.peek_line();
4140 self.advance(); match self.peek().clone() {
4142 Token::Ident(_) => {
4143 let name = self.parse_package_qualified_identifier()?;
4144 let allow_shadow =
4151 crate::compat_mode() || (self.parsing_module && !crate::no_interop_mode());
4152 if !allow_shadow {
4153 self.check_udf_shadows_builtin(&name, line)?;
4154 }
4155 self.declared_subs.insert(name.clone());
4156 let (params, prototype) = self.parse_sub_sig_or_prototype_opt()?;
4157 self.parse_sub_attributes()?;
4158 let body = self.parse_block()?;
4159 Ok(Statement {
4160 label: None,
4161 kind: StmtKind::SubDecl {
4162 name,
4163 params,
4164 body,
4165 prototype,
4166 },
4167 line,
4168 })
4169 }
4170 Token::LParen | Token::LBrace | Token::Colon => {
4171 if is_sub_keyword && crate::no_interop_mode() {
4173 return Err(self.syntax_err(
4174 "stryke uses `fn {}` instead of `sub {}` (--no-interop)",
4175 line,
4176 ));
4177 }
4178 let (params, _prototype) = self.parse_sub_sig_or_prototype_opt()?;
4180 self.parse_sub_attributes()?;
4181 let body = self.parse_block()?;
4182 Ok(Statement {
4183 label: None,
4184 kind: StmtKind::Expression(Expr {
4185 kind: ExprKind::CodeRef { params, body },
4186 line,
4187 }),
4188 line,
4189 })
4190 }
4191 tok => Err(self.syntax_err(
4192 format!("Expected sub name, `(`, `{{`, or `:`, got {:?}", tok),
4193 self.peek_line(),
4194 )),
4195 }
4196 }
4197
4198 fn parse_struct_decl(&mut self) -> PerlResult<Statement> {
4200 let line = self.peek_line();
4201 self.advance(); let name = match self.advance() {
4203 (Token::Ident(n), _) => n,
4204 (tok, err_line) => {
4205 return Err(
4206 self.syntax_err(format!("Expected struct name, got {:?}", tok), err_line)
4207 )
4208 }
4209 };
4210 self.expect(&Token::LBrace)?;
4211 let mut fields = Vec::new();
4212 let mut methods = Vec::new();
4213 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
4214 let is_method = match self.peek() {
4216 Token::Ident(s) => s == "fn" || s == "sub",
4217 _ => false,
4218 };
4219 if is_method {
4220 self.advance(); let method_name = match self.advance() {
4222 (Token::Ident(n), _) => n,
4223 (tok, err_line) => {
4224 return Err(self
4225 .syntax_err(format!("Expected method name, got {:?}", tok), err_line))
4226 }
4227 };
4228 let params = if self.eat(&Token::LParen) {
4230 let p = self.parse_sub_signature_param_list()?;
4231 self.expect(&Token::RParen)?;
4232 p
4233 } else {
4234 Vec::new()
4235 };
4236 let body = self.parse_block()?;
4238 methods.push(crate::ast::StructMethod {
4239 name: method_name,
4240 params,
4241 body,
4242 });
4243 self.eat(&Token::Comma);
4245 self.eat(&Token::Semicolon);
4246 continue;
4247 }
4248
4249 let field_name = match self.advance() {
4250 (Token::Ident(n), _) => n,
4251 (tok, err_line) => {
4252 return Err(
4253 self.syntax_err(format!("Expected field name, got {:?}", tok), err_line)
4254 )
4255 }
4256 };
4257 let ty = if self.eat(&Token::FatArrow) {
4259 self.parse_type_name()?
4260 } else {
4261 crate::ast::PerlTypeName::Any
4262 };
4263 let default = if self.eat(&Token::Assign) {
4264 Some(self.parse_ternary()?)
4266 } else {
4267 None
4268 };
4269 fields.push(StructField {
4270 name: field_name,
4271 ty,
4272 default,
4273 });
4274 if !self.eat(&Token::Comma) {
4275 self.eat(&Token::Semicolon);
4277 }
4278 }
4279 self.expect(&Token::RBrace)?;
4280 self.eat(&Token::Semicolon);
4281 Ok(Statement {
4282 label: None,
4283 kind: StmtKind::StructDecl {
4284 def: StructDef {
4285 name,
4286 fields,
4287 methods,
4288 },
4289 },
4290 line,
4291 })
4292 }
4293
4294 fn parse_enum_decl(&mut self) -> PerlResult<Statement> {
4296 let line = self.peek_line();
4297 self.advance(); let name = match self.advance() {
4299 (Token::Ident(n), _) => n,
4300 (tok, err_line) => {
4301 return Err(self.syntax_err(format!("Expected enum name, got {:?}", tok), err_line))
4302 }
4303 };
4304 self.expect(&Token::LBrace)?;
4305 let mut variants = Vec::new();
4306 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
4307 let variant_name = match self.advance() {
4308 (Token::Ident(n), _) => n,
4309 (tok, err_line) => {
4310 return Err(
4311 self.syntax_err(format!("Expected variant name, got {:?}", tok), err_line)
4312 )
4313 }
4314 };
4315 let ty = if self.eat(&Token::FatArrow) {
4316 Some(self.parse_type_name()?)
4317 } else {
4318 None
4319 };
4320 variants.push(EnumVariant {
4321 name: variant_name,
4322 ty,
4323 });
4324 if !self.eat(&Token::Comma) {
4325 self.eat(&Token::Semicolon);
4326 }
4327 }
4328 self.expect(&Token::RBrace)?;
4329 self.eat(&Token::Semicolon);
4330 Ok(Statement {
4331 label: None,
4332 kind: StmtKind::EnumDecl {
4333 def: EnumDef { name, variants },
4334 },
4335 line,
4336 })
4337 }
4338
4339 fn parse_class_decl(&mut self, is_abstract: bool, is_final: bool) -> PerlResult<Statement> {
4341 use crate::ast::{ClassDef, ClassField, ClassMethod, ClassStaticField, Visibility};
4342 let line = self.peek_line();
4343 self.advance(); let name = match self.advance() {
4345 (Token::Ident(n), _) => n,
4346 (tok, err_line) => {
4347 return Err(self.syntax_err(format!("Expected class name, got {:?}", tok), err_line))
4348 }
4349 };
4350
4351 let mut extends = Vec::new();
4353 if matches!(self.peek(), Token::Ident(ref s) if s == "extends") {
4354 self.advance(); loop {
4356 match self.advance() {
4357 (Token::Ident(parent), _) => extends.push(parent),
4358 (tok, err_line) => {
4359 return Err(self.syntax_err(
4360 format!("Expected parent class name after `extends`, got {:?}", tok),
4361 err_line,
4362 ))
4363 }
4364 }
4365 if !self.eat(&Token::Comma) {
4366 break;
4367 }
4368 }
4369 }
4370
4371 let mut implements = Vec::new();
4373 if matches!(self.peek(), Token::Ident(ref s) if s == "impl") {
4374 self.advance(); loop {
4376 match self.advance() {
4377 (Token::Ident(trait_name), _) => implements.push(trait_name),
4378 (tok, err_line) => {
4379 return Err(self.syntax_err(
4380 format!("Expected trait name after `impl`, got {:?}", tok),
4381 err_line,
4382 ))
4383 }
4384 }
4385 if !self.eat(&Token::Comma) {
4386 break;
4387 }
4388 }
4389 }
4390
4391 self.expect(&Token::LBrace)?;
4392 let mut fields = Vec::new();
4393 let mut methods = Vec::new();
4394 let mut static_fields = Vec::new();
4395
4396 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
4397 let visibility = match self.peek() {
4399 Token::Ident(ref s) if s == "pub" => {
4400 self.advance();
4401 Visibility::Public
4402 }
4403 Token::Ident(ref s) if s == "priv" => {
4404 self.advance();
4405 Visibility::Private
4406 }
4407 Token::Ident(ref s) if s == "prot" => {
4408 self.advance();
4409 Visibility::Protected
4410 }
4411 _ => Visibility::Public, };
4413
4414 if matches!(self.peek(), Token::Ident(ref s) if s == "static") {
4416 self.advance(); if matches!(self.peek(), Token::Ident(ref s) if s == "fn" || s == "sub") {
4420 return Err(self.syntax_err(
4422 "use `fn Self.name` for static methods, not `static fn`",
4423 self.peek_line(),
4424 ));
4425 }
4426
4427 let field_name = match self.advance() {
4428 (Token::Ident(n), _) => n,
4429 (tok, err_line) => {
4430 return Err(self.syntax_err(
4431 format!("Expected static field name, got {:?}", tok),
4432 err_line,
4433 ))
4434 }
4435 };
4436
4437 let ty = if self.eat(&Token::Colon) {
4438 self.parse_type_name()?
4439 } else {
4440 crate::ast::PerlTypeName::Any
4441 };
4442
4443 let default = if self.eat(&Token::Assign) {
4444 Some(self.parse_ternary()?)
4445 } else {
4446 None
4447 };
4448
4449 static_fields.push(ClassStaticField {
4450 name: field_name,
4451 ty,
4452 visibility,
4453 default,
4454 });
4455
4456 if !self.eat(&Token::Comma) {
4457 self.eat(&Token::Semicolon);
4458 }
4459 continue;
4460 }
4461
4462 let method_is_final = matches!(self.peek(), Token::Ident(ref s) if s == "final");
4464 if method_is_final {
4465 self.advance(); }
4467
4468 let is_method = matches!(self.peek(), Token::Ident(ref s) if s == "fn" || s == "sub");
4470 if is_method {
4471 self.advance(); let is_static = matches!(self.peek(), Token::Ident(ref s) if s == "Self");
4475 if is_static {
4476 self.advance(); self.expect(&Token::Dot)?;
4478 }
4479
4480 let method_name = match self.advance() {
4481 (Token::Ident(n), _) => n,
4482 (tok, err_line) => {
4483 return Err(self
4484 .syntax_err(format!("Expected method name, got {:?}", tok), err_line))
4485 }
4486 };
4487
4488 let params = if self.eat(&Token::LParen) {
4490 let p = self.parse_sub_signature_param_list()?;
4491 self.expect(&Token::RParen)?;
4492 p
4493 } else {
4494 Vec::new()
4495 };
4496
4497 let body = if matches!(self.peek(), Token::LBrace) {
4499 Some(self.parse_block()?)
4500 } else {
4501 None
4502 };
4503
4504 methods.push(ClassMethod {
4505 name: method_name,
4506 params,
4507 body,
4508 visibility,
4509 is_static,
4510 is_final: method_is_final,
4511 });
4512 self.eat(&Token::Comma);
4513 self.eat(&Token::Semicolon);
4514 continue;
4515 } else if method_is_final {
4516 return Err(self.syntax_err("`final` must be followed by `fn`", self.peek_line()));
4517 }
4518
4519 let field_name = match self.advance() {
4521 (Token::Ident(n), _) => n,
4522 (tok, err_line) => {
4523 return Err(
4524 self.syntax_err(format!("Expected field name, got {:?}", tok), err_line)
4525 )
4526 }
4527 };
4528
4529 let ty = if self.eat(&Token::Colon) {
4531 self.parse_type_name()?
4532 } else {
4533 crate::ast::PerlTypeName::Any
4534 };
4535
4536 let default = if self.eat(&Token::Assign) {
4538 Some(self.parse_ternary()?)
4539 } else {
4540 None
4541 };
4542
4543 fields.push(ClassField {
4544 name: field_name,
4545 ty,
4546 visibility,
4547 default,
4548 });
4549
4550 if !self.eat(&Token::Comma) {
4551 self.eat(&Token::Semicolon);
4552 }
4553 }
4554
4555 self.expect(&Token::RBrace)?;
4556 self.eat(&Token::Semicolon);
4557
4558 Ok(Statement {
4559 label: None,
4560 kind: StmtKind::ClassDecl {
4561 def: ClassDef {
4562 name,
4563 is_abstract,
4564 is_final,
4565 extends,
4566 implements,
4567 fields,
4568 methods,
4569 static_fields,
4570 },
4571 },
4572 line,
4573 })
4574 }
4575
4576 fn parse_trait_decl(&mut self) -> PerlResult<Statement> {
4578 use crate::ast::{ClassMethod, TraitDef, Visibility};
4579 let line = self.peek_line();
4580 self.advance(); let name = match self.advance() {
4582 (Token::Ident(n), _) => n,
4583 (tok, err_line) => {
4584 return Err(self.syntax_err(format!("Expected trait name, got {:?}", tok), err_line))
4585 }
4586 };
4587
4588 self.expect(&Token::LBrace)?;
4589 let mut methods = Vec::new();
4590
4591 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
4592 let visibility = match self.peek() {
4594 Token::Ident(ref s) if s == "pub" => {
4595 self.advance();
4596 Visibility::Public
4597 }
4598 Token::Ident(ref s) if s == "priv" => {
4599 self.advance();
4600 Visibility::Private
4601 }
4602 Token::Ident(ref s) if s == "prot" => {
4603 self.advance();
4604 Visibility::Protected
4605 }
4606 _ => Visibility::Public,
4607 };
4608
4609 if !matches!(self.peek(), Token::Ident(ref s) if s == "fn" || s == "sub") {
4611 return Err(self.syntax_err("Expected `fn` in trait definition", self.peek_line()));
4612 }
4613 self.advance(); let method_name = match self.advance() {
4616 (Token::Ident(n), _) => n,
4617 (tok, err_line) => {
4618 return Err(
4619 self.syntax_err(format!("Expected method name, got {:?}", tok), err_line)
4620 )
4621 }
4622 };
4623
4624 let params = if self.eat(&Token::LParen) {
4626 let p = self.parse_sub_signature_param_list()?;
4627 self.expect(&Token::RParen)?;
4628 p
4629 } else {
4630 Vec::new()
4631 };
4632
4633 let body = if matches!(self.peek(), Token::LBrace) {
4635 Some(self.parse_block()?)
4636 } else {
4637 None
4638 };
4639
4640 methods.push(ClassMethod {
4641 name: method_name,
4642 params,
4643 body,
4644 visibility,
4645 is_static: false,
4646 is_final: false,
4647 });
4648
4649 self.eat(&Token::Comma);
4650 self.eat(&Token::Semicolon);
4651 }
4652
4653 self.expect(&Token::RBrace)?;
4654 self.eat(&Token::Semicolon);
4655
4656 Ok(Statement {
4657 label: None,
4658 kind: StmtKind::TraitDecl {
4659 def: TraitDef { name, methods },
4660 },
4661 line,
4662 })
4663 }
4664
4665 fn local_simple_target_to_var_decl(target: &Expr) -> Option<VarDecl> {
4666 match &target.kind {
4667 ExprKind::ScalarVar(name) => Some(VarDecl {
4668 sigil: Sigil::Scalar,
4669 name: name.clone(),
4670 initializer: None,
4671 frozen: false,
4672 type_annotation: None,
4673 }),
4674 ExprKind::ArrayVar(name) => Some(VarDecl {
4675 sigil: Sigil::Array,
4676 name: name.clone(),
4677 initializer: None,
4678 frozen: false,
4679 type_annotation: None,
4680 }),
4681 ExprKind::HashVar(name) => Some(VarDecl {
4682 sigil: Sigil::Hash,
4683 name: name.clone(),
4684 initializer: None,
4685 frozen: false,
4686 type_annotation: None,
4687 }),
4688 ExprKind::Typeglob(name) => Some(VarDecl {
4689 sigil: Sigil::Typeglob,
4690 name: name.clone(),
4691 initializer: None,
4692 frozen: false,
4693 type_annotation: None,
4694 }),
4695 _ => None,
4696 }
4697 }
4698
4699 fn parse_decl_array_destructure(
4700 &mut self,
4701 keyword: &str,
4702 line: usize,
4703 ) -> PerlResult<Statement> {
4704 self.expect(&Token::LBracket)?;
4705 let elems = self.parse_match_array_elems_until_rbracket()?;
4706 self.expect(&Token::Assign)?;
4707 self.suppress_scalar_hash_brace += 1;
4708 let rhs = self.parse_expression()?;
4709 self.suppress_scalar_hash_brace -= 1;
4710 let stmt = self.desugar_array_destructure(keyword, line, elems, rhs)?;
4711 self.parse_stmt_postfix_modifier(stmt)
4712 }
4713
4714 fn parse_decl_hash_destructure(&mut self, keyword: &str, line: usize) -> PerlResult<Statement> {
4715 let MatchPattern::Hash(pairs) = self.parse_match_hash_pattern()? else {
4716 unreachable!("parse_match_hash_pattern returns Hash");
4717 };
4718 self.expect(&Token::Assign)?;
4719 self.suppress_scalar_hash_brace += 1;
4720 let rhs = self.parse_expression()?;
4721 self.suppress_scalar_hash_brace -= 1;
4722 let stmt = self.desugar_hash_destructure(keyword, line, pairs, rhs)?;
4723 self.parse_stmt_postfix_modifier(stmt)
4724 }
4725
4726 fn desugar_array_destructure(
4727 &mut self,
4728 keyword: &str,
4729 line: usize,
4730 elems: Vec<MatchArrayElem>,
4731 rhs: Expr,
4732 ) -> PerlResult<Statement> {
4733 let tmp = format!("__stryke_ds_{}", self.alloc_desugar_tmp());
4734 let mut stmts: Vec<Statement> = Vec::new();
4735 stmts.push(destructure_stmt_from_var_decls(
4736 keyword,
4737 vec![VarDecl {
4738 sigil: Sigil::Scalar,
4739 name: tmp.clone(),
4740 initializer: Some(rhs),
4741 frozen: false,
4742 type_annotation: None,
4743 }],
4744 line,
4745 ));
4746
4747 let has_rest = elems
4748 .iter()
4749 .any(|e| matches!(e, MatchArrayElem::Rest | MatchArrayElem::RestBind(_)));
4750 let fixed_slots = elems
4751 .iter()
4752 .filter(|e| {
4753 matches!(
4754 e,
4755 MatchArrayElem::CaptureScalar(_) | MatchArrayElem::Expr(_)
4756 )
4757 })
4758 .count();
4759 if !has_rest {
4760 let cond = Expr {
4761 kind: ExprKind::BinOp {
4762 left: Box::new(destructure_expr_array_len(&tmp, line)),
4763 op: BinOp::NumEq,
4764 right: Box::new(Expr {
4765 kind: ExprKind::Integer(fixed_slots as i64),
4766 line,
4767 }),
4768 },
4769 line,
4770 };
4771 stmts.push(destructure_stmt_unless_die(
4772 line,
4773 cond,
4774 "array destructure: length mismatch",
4775 ));
4776 }
4777
4778 let mut idx: i64 = 0;
4779 for elem in elems {
4780 match elem {
4781 MatchArrayElem::Rest => break,
4782 MatchArrayElem::RestBind(name) => {
4783 let list_source = Expr {
4784 kind: ExprKind::Deref {
4785 expr: Box::new(destructure_expr_scalar_tmp(&tmp, line)),
4786 kind: Sigil::Array,
4787 },
4788 line,
4789 };
4790 let last_ix = Expr {
4791 kind: ExprKind::BinOp {
4792 left: Box::new(destructure_expr_array_len(&tmp, line)),
4793 op: BinOp::Sub,
4794 right: Box::new(Expr {
4795 kind: ExprKind::Integer(1),
4796 line,
4797 }),
4798 },
4799 line,
4800 };
4801 let range = Expr {
4802 kind: ExprKind::Range {
4803 from: Box::new(Expr {
4804 kind: ExprKind::Integer(idx),
4805 line,
4806 }),
4807 to: Box::new(last_ix),
4808 exclusive: false,
4809 step: None,
4810 },
4811 line,
4812 };
4813 let slice = Expr {
4814 kind: ExprKind::AnonymousListSlice {
4815 source: Box::new(list_source),
4816 indices: vec![range],
4817 },
4818 line,
4819 };
4820 stmts.push(destructure_stmt_from_var_decls(
4821 keyword,
4822 vec![VarDecl {
4823 sigil: Sigil::Array,
4824 name,
4825 initializer: Some(slice),
4826 frozen: false,
4827 type_annotation: None,
4828 }],
4829 line,
4830 ));
4831 break;
4832 }
4833 MatchArrayElem::CaptureScalar(name) => {
4834 let arrow = Expr {
4835 kind: ExprKind::ArrowDeref {
4836 expr: Box::new(destructure_expr_scalar_tmp(&tmp, line)),
4837 index: Box::new(Expr {
4838 kind: ExprKind::Integer(idx),
4839 line,
4840 }),
4841 kind: DerefKind::Array,
4842 },
4843 line,
4844 };
4845 stmts.push(destructure_stmt_from_var_decls(
4846 keyword,
4847 vec![VarDecl {
4848 sigil: Sigil::Scalar,
4849 name,
4850 initializer: Some(arrow),
4851 frozen: false,
4852 type_annotation: None,
4853 }],
4854 line,
4855 ));
4856 idx += 1;
4857 }
4858 MatchArrayElem::Expr(e) => {
4859 let elem_subj = Expr {
4860 kind: ExprKind::ArrowDeref {
4861 expr: Box::new(destructure_expr_scalar_tmp(&tmp, line)),
4862 index: Box::new(Expr {
4863 kind: ExprKind::Integer(idx),
4864 line,
4865 }),
4866 kind: DerefKind::Array,
4867 },
4868 line,
4869 };
4870 let match_expr = Expr {
4871 kind: ExprKind::AlgebraicMatch {
4872 subject: Box::new(elem_subj),
4873 arms: vec![
4874 MatchArm {
4875 pattern: MatchPattern::Value(Box::new(e.clone())),
4876 guard: None,
4877 body: Expr {
4878 kind: ExprKind::Integer(0),
4879 line,
4880 },
4881 },
4882 MatchArm {
4883 pattern: MatchPattern::Any,
4884 guard: None,
4885 body: Expr {
4886 kind: ExprKind::Die(vec![Expr {
4887 kind: ExprKind::String(
4888 "array destructure: element pattern mismatch"
4889 .to_string(),
4890 ),
4891 line,
4892 }]),
4893 line,
4894 },
4895 },
4896 ],
4897 },
4898 line,
4899 };
4900 stmts.push(Statement {
4901 label: None,
4902 kind: StmtKind::Expression(match_expr),
4903 line,
4904 });
4905 idx += 1;
4906 }
4907 }
4908 }
4909
4910 Ok(Statement {
4911 label: None,
4912 kind: StmtKind::StmtGroup(stmts),
4913 line,
4914 })
4915 }
4916
4917 fn desugar_hash_destructure(
4918 &mut self,
4919 keyword: &str,
4920 line: usize,
4921 pairs: Vec<MatchHashPair>,
4922 rhs: Expr,
4923 ) -> PerlResult<Statement> {
4924 let tmp = format!("__stryke_ds_{}", self.alloc_desugar_tmp());
4925 let mut stmts: Vec<Statement> = Vec::new();
4926 stmts.push(destructure_stmt_from_var_decls(
4927 keyword,
4928 vec![VarDecl {
4929 sigil: Sigil::Scalar,
4930 name: tmp.clone(),
4931 initializer: Some(rhs),
4932 frozen: false,
4933 type_annotation: None,
4934 }],
4935 line,
4936 ));
4937
4938 for pair in pairs {
4939 match pair {
4940 MatchHashPair::KeyOnly { key } => {
4941 let exists_op = Expr {
4942 kind: ExprKind::Exists(Box::new(Expr {
4943 kind: ExprKind::ArrowDeref {
4944 expr: Box::new(destructure_expr_scalar_tmp(&tmp, line)),
4945 index: Box::new(key),
4946 kind: DerefKind::Hash,
4947 },
4948 line,
4949 })),
4950 line,
4951 };
4952 stmts.push(destructure_stmt_unless_die(
4953 line,
4954 exists_op,
4955 "hash destructure: missing required key",
4956 ));
4957 }
4958 MatchHashPair::Capture { key, name } => {
4959 let init = Expr {
4960 kind: ExprKind::ArrowDeref {
4961 expr: Box::new(destructure_expr_scalar_tmp(&tmp, line)),
4962 index: Box::new(key),
4963 kind: DerefKind::Hash,
4964 },
4965 line,
4966 };
4967 stmts.push(destructure_stmt_from_var_decls(
4968 keyword,
4969 vec![VarDecl {
4970 sigil: Sigil::Scalar,
4971 name,
4972 initializer: Some(init),
4973 frozen: false,
4974 type_annotation: None,
4975 }],
4976 line,
4977 ));
4978 }
4979 }
4980 }
4981
4982 Ok(Statement {
4983 label: None,
4984 kind: StmtKind::StmtGroup(stmts),
4985 line,
4986 })
4987 }
4988
4989 fn parse_my_our_local(
4990 &mut self,
4991 keyword: &str,
4992 allow_type_annotation: bool,
4993 ) -> PerlResult<Statement> {
4994 let line = self.peek_line();
4995 self.advance(); if keyword == "local"
4998 && !matches!(self.peek(), Token::LParen | Token::LBracket | Token::LBrace)
4999 {
5000 let target = self.parse_postfix()?;
5001 let mut initializer: Option<Expr> = None;
5002 if self.eat(&Token::Assign) {
5003 initializer = Some(self.parse_expression()?);
5004 } else if matches!(
5005 self.peek(),
5006 Token::OrAssign | Token::DefinedOrAssign | Token::AndAssign
5007 ) {
5008 if matches!(&target.kind, ExprKind::Typeglob(_)) {
5009 return Err(self.syntax_err(
5010 "compound assignment on typeglob declaration is not supported",
5011 self.peek_line(),
5012 ));
5013 }
5014 let op = match self.peek().clone() {
5015 Token::OrAssign => BinOp::LogOr,
5016 Token::DefinedOrAssign => BinOp::DefinedOr,
5017 Token::AndAssign => BinOp::LogAnd,
5018 _ => unreachable!(),
5019 };
5020 self.advance();
5021 let rhs = self.parse_assign_expr()?;
5022 let tgt_line = target.line;
5023 initializer = Some(Expr {
5024 kind: ExprKind::CompoundAssign {
5025 target: Box::new(target.clone()),
5026 op,
5027 value: Box::new(rhs),
5028 },
5029 line: tgt_line,
5030 });
5031 }
5032
5033 let kind = if let Some(mut decl) = Self::local_simple_target_to_var_decl(&target) {
5034 decl.initializer = initializer;
5035 StmtKind::Local(vec![decl])
5036 } else {
5037 StmtKind::LocalExpr {
5038 target,
5039 initializer,
5040 }
5041 };
5042 let stmt = Statement {
5043 label: None,
5044 kind,
5045 line,
5046 };
5047 return self.parse_stmt_postfix_modifier(stmt);
5048 }
5049
5050 if matches!(self.peek(), Token::LBracket) {
5051 return self.parse_decl_array_destructure(keyword, line);
5052 }
5053 if matches!(self.peek(), Token::LBrace) {
5054 return self.parse_decl_hash_destructure(keyword, line);
5055 }
5056
5057 let mut decls = Vec::new();
5058
5059 if self.eat(&Token::LParen) {
5060 while !matches!(self.peek(), Token::RParen | Token::Eof) {
5062 let decl = self.parse_var_decl(allow_type_annotation)?;
5063 decls.push(decl);
5064 if !self.eat(&Token::Comma) {
5065 break;
5066 }
5067 }
5068 self.expect(&Token::RParen)?;
5069 } else {
5070 decls.push(self.parse_var_decl(allow_type_annotation)?);
5071 }
5072
5073 if self.eat(&Token::Assign) {
5075 if keyword == "our" && decls.len() == 1 {
5076 while matches!(self.peek(), Token::Ident(ref i) if i == "our") {
5077 self.advance();
5078 decls.push(self.parse_var_decl(allow_type_annotation)?);
5079 if !self.eat(&Token::Assign) {
5080 return Err(self.syntax_err(
5081 "expected `=` after `our` in chained our-declaration",
5082 self.peek_line(),
5083 ));
5084 }
5085 }
5086 }
5087 let val = self.parse_expression()?;
5088 if !crate::compat_mode() && decls.len() == 1 {
5091 let decl = &decls[0];
5092 let target_kind = match decl.sigil {
5093 Sigil::Scalar => ExprKind::ScalarVar(decl.name.clone()),
5094 Sigil::Array => ExprKind::ArrayVar(decl.name.clone()),
5095 Sigil::Hash => ExprKind::HashVar(decl.name.clone()),
5096 Sigil::Typeglob => {
5097 if decls.len() == 1 {
5099 decls[0].initializer = Some(val);
5100 } else {
5101 for d in &mut decls {
5102 d.initializer = Some(val.clone());
5103 }
5104 }
5105 return Ok(Statement {
5106 label: None,
5107 kind: match keyword {
5108 "my" => StmtKind::My(decls),
5109 "mysync" => StmtKind::MySync(decls),
5110 "our" => StmtKind::Our(decls),
5111 "local" => StmtKind::Local(decls),
5112 "state" => StmtKind::State(decls),
5113 _ => unreachable!(),
5114 },
5115 line,
5116 });
5117 }
5118 };
5119 let target = Expr {
5120 kind: target_kind,
5121 line,
5122 };
5123 self.validate_assignment(&target, &val, line)?;
5124 }
5125 if decls.len() == 1 {
5126 decls[0].initializer = Some(val);
5127 } else {
5128 for decl in &mut decls {
5129 decl.initializer = Some(val.clone());
5130 }
5131 }
5132 } else if decls.len() == 1 {
5133 let op = match self.peek().clone() {
5135 Token::OrAssign => Some(BinOp::LogOr),
5136 Token::DefinedOrAssign => Some(BinOp::DefinedOr),
5137 Token::AndAssign => Some(BinOp::LogAnd),
5138 _ => None,
5139 };
5140 if let Some(op) = op {
5141 let d = &decls[0];
5142 if matches!(d.sigil, Sigil::Typeglob) {
5143 return Err(self.syntax_err(
5144 "compound assignment on typeglob declaration is not supported",
5145 self.peek_line(),
5146 ));
5147 }
5148 self.advance();
5149 let rhs = self.parse_assign_expr()?;
5150 let target = Expr {
5151 kind: match d.sigil {
5152 Sigil::Scalar => ExprKind::ScalarVar(d.name.clone()),
5153 Sigil::Array => ExprKind::ArrayVar(d.name.clone()),
5154 Sigil::Hash => ExprKind::HashVar(d.name.clone()),
5155 Sigil::Typeglob => unreachable!(),
5156 },
5157 line,
5158 };
5159 decls[0].initializer = Some(Expr {
5160 kind: ExprKind::CompoundAssign {
5161 target: Box::new(target),
5162 op,
5163 value: Box::new(rhs),
5164 },
5165 line,
5166 });
5167 }
5168 }
5169
5170 let kind = match keyword {
5171 "my" => StmtKind::My(decls),
5172 "mysync" => StmtKind::MySync(decls),
5173 "our" => StmtKind::Our(decls),
5174 "local" => StmtKind::Local(decls),
5175 "state" => StmtKind::State(decls),
5176 _ => unreachable!(),
5177 };
5178 let stmt = Statement {
5179 label: None,
5180 kind,
5181 line,
5182 };
5183 self.parse_stmt_postfix_modifier(stmt)
5185 }
5186
5187 fn parse_var_decl(&mut self, allow_type_annotation: bool) -> PerlResult<VarDecl> {
5188 let mut decl = match self.advance() {
5189 (Token::ScalarVar(name), _) => VarDecl {
5190 sigil: Sigil::Scalar,
5191 name,
5192 initializer: None,
5193 frozen: false,
5194 type_annotation: None,
5195 },
5196 (Token::ArrayVar(name), _) => VarDecl {
5197 sigil: Sigil::Array,
5198 name,
5199 initializer: None,
5200 frozen: false,
5201 type_annotation: None,
5202 },
5203 (Token::HashVar(name), line) => {
5204 if !crate::compat_mode() {
5205 self.check_hash_shadows_reserved(&name, line)?;
5206 }
5207 VarDecl {
5208 sigil: Sigil::Hash,
5209 name,
5210 initializer: None,
5211 frozen: false,
5212 type_annotation: None,
5213 }
5214 }
5215 (Token::Star, _line) => {
5216 let name = match self.advance() {
5217 (Token::Ident(n), _) => n,
5218 (tok, l) => {
5219 return Err(self
5220 .syntax_err(format!("Expected identifier after *, got {:?}", tok), l));
5221 }
5222 };
5223 VarDecl {
5224 sigil: Sigil::Typeglob,
5225 name,
5226 initializer: None,
5227 frozen: false,
5228 type_annotation: None,
5229 }
5230 }
5231 (Token::Ident(ref kw), _) if kw == "undef" => VarDecl {
5236 sigil: Sigil::Scalar,
5237 name: format!("__undef_sink_{}", self.pos),
5241 initializer: None,
5242 frozen: false,
5243 type_annotation: None,
5244 },
5245 (tok, line) => {
5246 return Err(self.syntax_err(
5247 format!("Expected variable in declaration, got {:?}", tok),
5248 line,
5249 ));
5250 }
5251 };
5252 if allow_type_annotation && self.eat(&Token::Colon) {
5253 let ty = self.parse_type_name()?;
5254 if decl.sigil != Sigil::Scalar {
5255 return Err(self.syntax_err(
5256 "`: Type` is only valid for scalar declarations (typed my $name : Int)",
5257 self.peek_line(),
5258 ));
5259 }
5260 decl.type_annotation = Some(ty);
5261 }
5262 Ok(decl)
5263 }
5264
5265 fn parse_type_name(&mut self) -> PerlResult<PerlTypeName> {
5266 match self.advance() {
5267 (Token::Ident(name), _) => match name.as_str() {
5268 "Int" => Ok(PerlTypeName::Int),
5269 "Str" => Ok(PerlTypeName::Str),
5270 "Float" => Ok(PerlTypeName::Float),
5271 "Bool" => Ok(PerlTypeName::Bool),
5272 "Array" => Ok(PerlTypeName::Array),
5273 "Hash" => Ok(PerlTypeName::Hash),
5274 "Ref" => Ok(PerlTypeName::Ref),
5275 "Any" => Ok(PerlTypeName::Any),
5276 _ => Ok(PerlTypeName::Struct(name)),
5277 },
5278 (tok, err_line) => Err(self.syntax_err(
5279 format!("Expected type name after `:`, got {:?}", tok),
5280 err_line,
5281 )),
5282 }
5283 }
5284
5285 fn parse_package(&mut self) -> PerlResult<Statement> {
5286 let line = self.peek_line();
5287 self.advance(); let name = match self.advance() {
5289 (Token::Ident(n), _) => n,
5290 (tok, line) => {
5291 return Err(self.syntax_err(format!("Expected package name, got {:?}", tok), line))
5292 }
5293 };
5294 let mut full_name = name;
5296 while self.eat(&Token::PackageSep) {
5297 if let (Token::Ident(part), _) = self.advance() {
5298 full_name = format!("{}::{}", full_name, part);
5299 }
5300 }
5301 self.eat(&Token::Semicolon);
5302 Ok(Statement {
5303 label: None,
5304 kind: StmtKind::Package { name: full_name },
5305 line,
5306 })
5307 }
5308
5309 fn parse_use(&mut self) -> PerlResult<Statement> {
5310 let line = self.peek_line();
5311 self.advance(); let (tok, tok_line) = self.advance();
5313 match tok {
5314 Token::Float(v) => {
5315 self.eat(&Token::Semicolon);
5316 Ok(Statement {
5317 label: None,
5318 kind: StmtKind::UsePerlVersion { version: v },
5319 line,
5320 })
5321 }
5322 Token::Integer(n) => {
5323 if matches!(self.peek(), Token::Semicolon | Token::Eof) {
5324 self.eat(&Token::Semicolon);
5325 Ok(Statement {
5326 label: None,
5327 kind: StmtKind::UsePerlVersion { version: n as f64 },
5328 line,
5329 })
5330 } else {
5331 Err(self.syntax_err(
5332 format!("Expected ';' after use VERSION (got {:?})", self.peek()),
5333 line,
5334 ))
5335 }
5336 }
5337 Token::Ident(n) => {
5338 let mut full_name = n;
5339 while self.eat(&Token::PackageSep) {
5340 if let (Token::Ident(part), _) = self.advance() {
5341 full_name = format!("{}::{}", full_name, part);
5342 }
5343 }
5344 if full_name == "overload" {
5345 let mut pairs = Vec::new();
5346 let mut parse_overload_pairs = |this: &mut Self| -> PerlResult<()> {
5347 loop {
5348 if matches!(this.peek(), Token::RParen | Token::Semicolon | Token::Eof)
5349 {
5350 break;
5351 }
5352 let key_e = this.parse_assign_expr()?;
5353 this.expect(&Token::FatArrow)?;
5354 let val_e = this.parse_assign_expr()?;
5355 let key = this.expr_to_overload_key(&key_e)?;
5356 let val = this.expr_to_overload_sub(&val_e)?;
5357 pairs.push((key, val));
5358 if !this.eat(&Token::Comma) {
5359 break;
5360 }
5361 }
5362 Ok(())
5363 };
5364 if self.eat(&Token::LParen) {
5365 parse_overload_pairs(self)?;
5367 self.expect(&Token::RParen)?;
5368 } else if !matches!(self.peek(), Token::Semicolon | Token::Eof) {
5369 parse_overload_pairs(self)?;
5370 }
5371 self.eat(&Token::Semicolon);
5372 return Ok(Statement {
5373 label: None,
5374 kind: StmtKind::UseOverload { pairs },
5375 line,
5376 });
5377 }
5378 let mut imports = Vec::new();
5379 if !matches!(self.peek(), Token::Semicolon | Token::Eof)
5380 && !self.next_is_new_statement_start(tok_line)
5381 {
5382 loop {
5383 if matches!(self.peek(), Token::Semicolon | Token::Eof) {
5384 break;
5385 }
5386 imports.push(self.parse_expression()?);
5387 if !self.eat(&Token::Comma) {
5388 break;
5389 }
5390 }
5391 }
5392 self.eat(&Token::Semicolon);
5393 Ok(Statement {
5394 label: None,
5395 kind: StmtKind::Use {
5396 module: full_name,
5397 imports,
5398 },
5399 line,
5400 })
5401 }
5402 other => Err(self.syntax_err(
5403 format!("Expected module name or version after use, got {:?}", other),
5404 tok_line,
5405 )),
5406 }
5407 }
5408
5409 fn parse_no(&mut self) -> PerlResult<Statement> {
5410 let line = self.peek_line();
5411 self.advance(); let module = match self.advance() {
5413 (Token::Ident(n), tok_line) => (n, tok_line),
5414 (tok, line) => {
5415 return Err(self.syntax_err(
5416 format!("Expected module name after no, got {:?}", tok),
5417 line,
5418 ))
5419 }
5420 };
5421 let (module_name, tok_line) = module;
5422 let mut full_name = module_name;
5423 while self.eat(&Token::PackageSep) {
5424 if let (Token::Ident(part), _) = self.advance() {
5425 full_name = format!("{}::{}", full_name, part);
5426 }
5427 }
5428 let mut imports = Vec::new();
5429 if !matches!(self.peek(), Token::Semicolon | Token::Eof)
5430 && !self.next_is_new_statement_start(tok_line)
5431 {
5432 loop {
5433 if matches!(self.peek(), Token::Semicolon | Token::Eof) {
5434 break;
5435 }
5436 imports.push(self.parse_expression()?);
5437 if !self.eat(&Token::Comma) {
5438 break;
5439 }
5440 }
5441 }
5442 self.eat(&Token::Semicolon);
5443 Ok(Statement {
5444 label: None,
5445 kind: StmtKind::No {
5446 module: full_name,
5447 imports,
5448 },
5449 line,
5450 })
5451 }
5452
5453 fn parse_return(&mut self) -> PerlResult<Statement> {
5454 let line = self.peek_line();
5455 self.advance(); let val = if matches!(self.peek(), Token::Semicolon | Token::RBrace | Token::Eof) {
5457 None
5458 } else {
5459 Some(self.parse_assign_expr()?)
5461 };
5462 let stmt = Statement {
5464 label: None,
5465 kind: StmtKind::Return(val),
5466 line,
5467 };
5468 if let Token::Ident(ref kw) = self.peek().clone() {
5469 match kw.as_str() {
5470 "if" => {
5471 self.advance();
5472 let cond = self.parse_expression()?;
5473 self.eat(&Token::Semicolon);
5474 return Ok(Statement {
5475 label: None,
5476 kind: StmtKind::If {
5477 condition: cond,
5478 body: vec![stmt],
5479 elsifs: vec![],
5480 else_block: None,
5481 },
5482 line,
5483 });
5484 }
5485 "unless" => {
5486 self.advance();
5487 let cond = self.parse_expression()?;
5488 self.eat(&Token::Semicolon);
5489 return Ok(Statement {
5490 label: None,
5491 kind: StmtKind::Unless {
5492 condition: cond,
5493 body: vec![stmt],
5494 else_block: None,
5495 },
5496 line,
5497 });
5498 }
5499 _ => {}
5500 }
5501 }
5502 self.eat(&Token::Semicolon);
5503 Ok(stmt)
5504 }
5505
5506 fn parse_expression(&mut self) -> PerlResult<Expr> {
5509 self.parse_comma_expr()
5510 }
5511
5512 fn parse_comma_expr(&mut self) -> PerlResult<Expr> {
5513 let expr = self.parse_assign_expr()?;
5514 let mut exprs = vec![expr];
5515 while self.eat(&Token::Comma) || self.eat(&Token::FatArrow) {
5516 if matches!(
5517 self.peek(),
5518 Token::RParen | Token::RBracket | Token::RBrace | Token::Semicolon | Token::Eof
5519 ) {
5520 break; }
5522 exprs.push(self.parse_assign_expr()?);
5523 }
5524 if exprs.len() == 1 {
5525 return Ok(exprs.pop().unwrap());
5526 }
5527 let line = exprs[0].line;
5528 Ok(Expr {
5529 kind: ExprKind::List(exprs),
5530 line,
5531 })
5532 }
5533
5534 fn parse_assign_expr(&mut self) -> PerlResult<Expr> {
5535 let expr = self.parse_ternary()?;
5536 let line = expr.line;
5537
5538 match self.peek().clone() {
5539 Token::Assign => {
5540 self.advance();
5541 let right = self.parse_assign_expr()?;
5542 if let ExprKind::MethodCall { ref args, .. } = expr.kind {
5544 if args.is_empty() {
5545 let ExprKind::MethodCall {
5547 object,
5548 method,
5549 super_call,
5550 ..
5551 } = expr.kind
5552 else {
5553 unreachable!()
5554 };
5555 return Ok(Expr {
5556 kind: ExprKind::MethodCall {
5557 object,
5558 method,
5559 args: vec![right],
5560 super_call,
5561 },
5562 line,
5563 });
5564 }
5565 }
5566 self.validate_assignment(&expr, &right, line)?;
5567 Ok(Expr {
5568 kind: ExprKind::Assign {
5569 target: Box::new(expr),
5570 value: Box::new(right),
5571 },
5572 line,
5573 })
5574 }
5575 Token::PlusAssign => {
5576 self.advance();
5577 let r = self.parse_assign_expr()?;
5578 Ok(Expr {
5579 kind: ExprKind::CompoundAssign {
5580 target: Box::new(expr),
5581 op: BinOp::Add,
5582 value: Box::new(r),
5583 },
5584 line,
5585 })
5586 }
5587 Token::MinusAssign => {
5588 self.advance();
5589 let r = self.parse_assign_expr()?;
5590 Ok(Expr {
5591 kind: ExprKind::CompoundAssign {
5592 target: Box::new(expr),
5593 op: BinOp::Sub,
5594 value: Box::new(r),
5595 },
5596 line,
5597 })
5598 }
5599 Token::MulAssign => {
5600 self.advance();
5601 let r = self.parse_assign_expr()?;
5602 Ok(Expr {
5603 kind: ExprKind::CompoundAssign {
5604 target: Box::new(expr),
5605 op: BinOp::Mul,
5606 value: Box::new(r),
5607 },
5608 line,
5609 })
5610 }
5611 Token::DivAssign => {
5612 self.advance();
5613 let r = self.parse_assign_expr()?;
5614 Ok(Expr {
5615 kind: ExprKind::CompoundAssign {
5616 target: Box::new(expr),
5617 op: BinOp::Div,
5618 value: Box::new(r),
5619 },
5620 line,
5621 })
5622 }
5623 Token::ModAssign => {
5624 self.advance();
5625 let r = self.parse_assign_expr()?;
5626 Ok(Expr {
5627 kind: ExprKind::CompoundAssign {
5628 target: Box::new(expr),
5629 op: BinOp::Mod,
5630 value: Box::new(r),
5631 },
5632 line,
5633 })
5634 }
5635 Token::PowAssign => {
5636 self.advance();
5637 let r = self.parse_assign_expr()?;
5638 Ok(Expr {
5639 kind: ExprKind::CompoundAssign {
5640 target: Box::new(expr),
5641 op: BinOp::Pow,
5642 value: Box::new(r),
5643 },
5644 line,
5645 })
5646 }
5647 Token::DotAssign => {
5648 self.advance();
5649 let r = self.parse_assign_expr()?;
5650 Ok(Expr {
5651 kind: ExprKind::CompoundAssign {
5652 target: Box::new(expr),
5653 op: BinOp::Concat,
5654 value: Box::new(r),
5655 },
5656 line,
5657 })
5658 }
5659 Token::BitAndAssign => {
5660 self.advance();
5661 let r = self.parse_assign_expr()?;
5662 Ok(Expr {
5663 kind: ExprKind::CompoundAssign {
5664 target: Box::new(expr),
5665 op: BinOp::BitAnd,
5666 value: Box::new(r),
5667 },
5668 line,
5669 })
5670 }
5671 Token::BitOrAssign => {
5672 self.advance();
5673 let r = self.parse_assign_expr()?;
5674 Ok(Expr {
5675 kind: ExprKind::CompoundAssign {
5676 target: Box::new(expr),
5677 op: BinOp::BitOr,
5678 value: Box::new(r),
5679 },
5680 line,
5681 })
5682 }
5683 Token::XorAssign => {
5684 self.advance();
5685 let r = self.parse_assign_expr()?;
5686 Ok(Expr {
5687 kind: ExprKind::CompoundAssign {
5688 target: Box::new(expr),
5689 op: BinOp::BitXor,
5690 value: Box::new(r),
5691 },
5692 line,
5693 })
5694 }
5695 Token::ShiftLeftAssign => {
5696 self.advance();
5697 let r = self.parse_assign_expr()?;
5698 Ok(Expr {
5699 kind: ExprKind::CompoundAssign {
5700 target: Box::new(expr),
5701 op: BinOp::ShiftLeft,
5702 value: Box::new(r),
5703 },
5704 line,
5705 })
5706 }
5707 Token::ShiftRightAssign => {
5708 self.advance();
5709 let r = self.parse_assign_expr()?;
5710 Ok(Expr {
5711 kind: ExprKind::CompoundAssign {
5712 target: Box::new(expr),
5713 op: BinOp::ShiftRight,
5714 value: Box::new(r),
5715 },
5716 line,
5717 })
5718 }
5719 Token::OrAssign => {
5720 self.advance();
5721 let r = self.parse_assign_expr()?;
5722 Ok(Expr {
5723 kind: ExprKind::CompoundAssign {
5724 target: Box::new(expr),
5725 op: BinOp::LogOr,
5726 value: Box::new(r),
5727 },
5728 line,
5729 })
5730 }
5731 Token::DefinedOrAssign => {
5732 self.advance();
5733 let r = self.parse_assign_expr()?;
5734 Ok(Expr {
5735 kind: ExprKind::CompoundAssign {
5736 target: Box::new(expr),
5737 op: BinOp::DefinedOr,
5738 value: Box::new(r),
5739 },
5740 line,
5741 })
5742 }
5743 Token::AndAssign => {
5744 self.advance();
5745 let r = self.parse_assign_expr()?;
5746 Ok(Expr {
5747 kind: ExprKind::CompoundAssign {
5748 target: Box::new(expr),
5749 op: BinOp::LogAnd,
5750 value: Box::new(r),
5751 },
5752 line,
5753 })
5754 }
5755 _ => Ok(expr),
5756 }
5757 }
5758
5759 fn parse_ternary(&mut self) -> PerlResult<Expr> {
5760 let expr = self.parse_pipe_forward()?;
5761 if self.eat(&Token::Question) {
5762 let line = expr.line;
5763 self.suppress_colon_range = self.suppress_colon_range.saturating_add(1);
5764 let then_expr = self.parse_assign_expr();
5765 self.suppress_colon_range = self.suppress_colon_range.saturating_sub(1);
5766 let then_expr = then_expr?;
5767 self.expect(&Token::Colon)?;
5768 let else_expr = self.parse_assign_expr()?;
5769 return Ok(Expr {
5770 kind: ExprKind::Ternary {
5771 condition: Box::new(expr),
5772 then_expr: Box::new(then_expr),
5773 else_expr: Box::new(else_expr),
5774 },
5775 line,
5776 });
5777 }
5778 Ok(expr)
5779 }
5780
5781 fn parse_pipe_forward(&mut self) -> PerlResult<Expr> {
5787 let mut left = self.parse_or_word()?;
5788 if self.no_pipe_forward_depth > 0 {
5794 return Ok(left);
5795 }
5796 while matches!(self.peek(), Token::PipeForward) {
5797 if crate::compat_mode() {
5798 return Err(self.syntax_err(
5799 "pipe-forward operator `|>` is a stryke extension (disabled by --compat)",
5800 left.line,
5801 ));
5802 }
5803 let line = left.line;
5804 self.advance();
5805 self.pipe_rhs_depth = self.pipe_rhs_depth.saturating_add(1);
5808 let right_result = self.parse_or_word();
5809 self.pipe_rhs_depth = self.pipe_rhs_depth.saturating_sub(1);
5810 let right = right_result?;
5811 left = self.pipe_forward_apply(left, right, line)?;
5812 }
5813 Ok(left)
5814 }
5815
5816 fn pipe_forward_apply(&self, lhs: Expr, rhs: Expr, line: usize) -> PerlResult<Expr> {
5838 let Expr { kind, line: rline } = rhs;
5839 let new_kind = match kind {
5840 ExprKind::FuncCall { name, mut args } => {
5842 let dispatch_name: &str = name.strip_prefix("CORE::").unwrap_or(name.as_str());
5845 match dispatch_name {
5846 "puniq" | "uniq" | "distinct" | "flatten" | "set" | "list_count"
5847 | "list_size" | "count" | "size" | "cnt" | "len" | "with_index" | "shuffle"
5848 | "shuffled" | "frequencies" | "freq" | "interleave" | "ddump"
5849 | "stringify" | "str" | "lines" | "words" | "chars" | "digits" | "letters"
5850 | "letters_uc" | "letters_lc" | "punctuation" | "numbers" | "graphemes"
5851 | "columns" | "sentences" | "paragraphs" | "sections" | "trim" | "avg"
5852 | "to_json" | "to_csv" | "to_toml" | "to_yaml" | "to_xml" | "to_html"
5853 | "from_json" | "from_csv" | "from_toml" | "from_yaml" | "from_xml"
5854 | "to_markdown" | "to_table" | "xopen" | "clip" | "sparkline" | "bar_chart"
5855 | "flame" | "stddev" | "squared" | "sq" | "square" | "cubed" | "cb"
5856 | "cube" | "normalize" | "snake_case" | "camel_case" | "kebab_case" => {
5857 if args.is_empty() {
5858 args.push(lhs);
5859 } else {
5860 args[0] = lhs;
5861 }
5862 }
5863 "chunked" | "windowed" => {
5864 if args.is_empty() {
5865 return Err(self.syntax_err(
5866 "|>: chunked(N) / windowed(N) needs size — e.g. `@a |> windowed(2)`",
5867 line,
5868 ));
5869 }
5870 args.insert(0, lhs);
5871 }
5872 "reduce" | "fold" => {
5873 args.push(lhs);
5874 }
5875 "grep_v" | "pluck" | "tee" | "nth" | "chunk" => {
5876 args.push(lhs);
5882 }
5883 "enumerate" | "dedup" => {
5884 args.insert(0, lhs);
5887 }
5888 "clamp" => {
5889 args.push(lhs);
5891 }
5892 n if Self::is_block_then_list_pipe_builtin(n) => {
5893 if args.len() < 2 {
5894 return Err(self.syntax_err(
5895 format!(
5896 "|>: `{name}` needs {{ BLOCK }}, LIST so the list can receive the pipe"
5897 ),
5898 line,
5899 ));
5900 }
5901 args[1] = lhs;
5902 }
5903 "take" | "head" | "tail" | "drop" => {
5904 if args.is_empty() {
5905 return Err(self.syntax_err(
5906 "|>: `{name}` needs N last — e.g. `@a |> take(3)` for `take(@a, 3)`",
5907 line,
5908 ));
5909 }
5910 args.insert(0, lhs);
5912 }
5913 _ => {
5914 if self.thread_last_mode {
5915 args.push(lhs);
5916 } else {
5917 args.insert(0, lhs);
5918 }
5919 }
5920 }
5921 ExprKind::FuncCall { name, args }
5922 }
5923 ExprKind::MethodCall {
5924 object,
5925 method,
5926 mut args,
5927 super_call,
5928 } => {
5929 if self.thread_last_mode {
5930 args.push(lhs);
5931 } else {
5932 args.insert(0, lhs);
5933 }
5934 ExprKind::MethodCall {
5935 object,
5936 method,
5937 args,
5938 super_call,
5939 }
5940 }
5941 ExprKind::IndirectCall {
5942 target,
5943 mut args,
5944 ampersand,
5945 pass_caller_arglist: _,
5946 } => {
5947 if self.thread_last_mode {
5948 args.push(lhs);
5949 } else {
5950 args.insert(0, lhs);
5951 }
5952 ExprKind::IndirectCall {
5953 target,
5954 args,
5955 ampersand,
5956 pass_caller_arglist: false,
5959 }
5960 }
5961
5962 ExprKind::Print { handle, mut args } => {
5964 if self.thread_last_mode {
5965 args.push(lhs);
5966 } else {
5967 args.insert(0, lhs);
5968 }
5969 ExprKind::Print { handle, args }
5970 }
5971 ExprKind::Say { handle, mut args } => {
5972 if self.thread_last_mode {
5973 args.push(lhs);
5974 } else {
5975 args.insert(0, lhs);
5976 }
5977 ExprKind::Say { handle, args }
5978 }
5979 ExprKind::Printf { handle, mut args } => {
5980 if self.thread_last_mode {
5981 args.push(lhs);
5982 } else {
5983 args.insert(0, lhs);
5984 }
5985 ExprKind::Printf { handle, args }
5986 }
5987 ExprKind::Die(mut args) => {
5988 if self.thread_last_mode {
5989 args.push(lhs);
5990 } else {
5991 args.insert(0, lhs);
5992 }
5993 ExprKind::Die(args)
5994 }
5995 ExprKind::Warn(mut args) => {
5996 if self.thread_last_mode {
5997 args.push(lhs);
5998 } else {
5999 args.insert(0, lhs);
6000 }
6001 ExprKind::Warn(args)
6002 }
6003
6004 ExprKind::Sprintf { format, mut args } => {
6010 if self.thread_last_mode {
6011 args.push(lhs);
6012 } else {
6013 args.insert(0, lhs);
6014 }
6015 ExprKind::Sprintf { format, args }
6016 }
6017
6018 ExprKind::System(mut args) => {
6020 if self.thread_last_mode {
6021 args.push(lhs);
6022 } else {
6023 args.insert(0, lhs);
6024 }
6025 ExprKind::System(args)
6026 }
6027 ExprKind::Exec(mut args) => {
6028 if self.thread_last_mode {
6029 args.push(lhs);
6030 } else {
6031 args.insert(0, lhs);
6032 }
6033 ExprKind::Exec(args)
6034 }
6035 ExprKind::Unlink(mut args) => {
6036 if self.thread_last_mode {
6037 args.push(lhs);
6038 } else {
6039 args.insert(0, lhs);
6040 }
6041 ExprKind::Unlink(args)
6042 }
6043 ExprKind::Chmod(mut args) => {
6044 if self.thread_last_mode {
6045 args.push(lhs);
6046 } else {
6047 args.insert(0, lhs);
6048 }
6049 ExprKind::Chmod(args)
6050 }
6051 ExprKind::Chown(mut args) => {
6052 if self.thread_last_mode {
6053 args.push(lhs);
6054 } else {
6055 args.insert(0, lhs);
6056 }
6057 ExprKind::Chown(args)
6058 }
6059 ExprKind::Glob(mut args) => {
6060 if self.thread_last_mode {
6061 args.push(lhs);
6062 } else {
6063 args.insert(0, lhs);
6064 }
6065 ExprKind::Glob(args)
6066 }
6067 ExprKind::Files(mut args) => {
6068 if self.thread_last_mode {
6069 args.push(lhs);
6070 } else {
6071 args.insert(0, lhs);
6072 }
6073 ExprKind::Files(args)
6074 }
6075 ExprKind::Filesf(mut args) => {
6076 if self.thread_last_mode {
6077 args.push(lhs);
6078 } else {
6079 args.insert(0, lhs);
6080 }
6081 ExprKind::Filesf(args)
6082 }
6083 ExprKind::FilesfRecursive(mut args) => {
6084 if self.thread_last_mode {
6085 args.push(lhs);
6086 } else {
6087 args.insert(0, lhs);
6088 }
6089 ExprKind::FilesfRecursive(args)
6090 }
6091 ExprKind::Dirs(mut args) => {
6092 if self.thread_last_mode {
6093 args.push(lhs);
6094 } else {
6095 args.insert(0, lhs);
6096 }
6097 ExprKind::Dirs(args)
6098 }
6099 ExprKind::DirsRecursive(mut args) => {
6100 if self.thread_last_mode {
6101 args.push(lhs);
6102 } else {
6103 args.insert(0, lhs);
6104 }
6105 ExprKind::DirsRecursive(args)
6106 }
6107 ExprKind::SymLinks(mut args) => {
6108 if self.thread_last_mode {
6109 args.push(lhs);
6110 } else {
6111 args.insert(0, lhs);
6112 }
6113 ExprKind::SymLinks(args)
6114 }
6115 ExprKind::Sockets(mut args) => {
6116 if self.thread_last_mode {
6117 args.push(lhs);
6118 } else {
6119 args.insert(0, lhs);
6120 }
6121 ExprKind::Sockets(args)
6122 }
6123 ExprKind::Pipes(mut args) => {
6124 if self.thread_last_mode {
6125 args.push(lhs);
6126 } else {
6127 args.insert(0, lhs);
6128 }
6129 ExprKind::Pipes(args)
6130 }
6131 ExprKind::BlockDevices(mut args) => {
6132 if self.thread_last_mode {
6133 args.push(lhs);
6134 } else {
6135 args.insert(0, lhs);
6136 }
6137 ExprKind::BlockDevices(args)
6138 }
6139 ExprKind::CharDevices(mut args) => {
6140 if self.thread_last_mode {
6141 args.push(lhs);
6142 } else {
6143 args.insert(0, lhs);
6144 }
6145 ExprKind::CharDevices(args)
6146 }
6147 ExprKind::GlobPar { mut args, progress } => {
6148 if self.thread_last_mode {
6149 args.push(lhs);
6150 } else {
6151 args.insert(0, lhs);
6152 }
6153 ExprKind::GlobPar { args, progress }
6154 }
6155 ExprKind::ParSed { mut args, progress } => {
6156 if self.thread_last_mode {
6157 args.push(lhs);
6158 } else {
6159 args.insert(0, lhs);
6160 }
6161 ExprKind::ParSed { args, progress }
6162 }
6163
6164 ExprKind::Length(_) => ExprKind::Length(Box::new(lhs)),
6166 ExprKind::Abs(_) => ExprKind::Abs(Box::new(lhs)),
6167 ExprKind::Int(_) => ExprKind::Int(Box::new(lhs)),
6168 ExprKind::Sqrt(_) => ExprKind::Sqrt(Box::new(lhs)),
6169 ExprKind::Sin(_) => ExprKind::Sin(Box::new(lhs)),
6170 ExprKind::Cos(_) => ExprKind::Cos(Box::new(lhs)),
6171 ExprKind::Exp(_) => ExprKind::Exp(Box::new(lhs)),
6172 ExprKind::Log(_) => ExprKind::Log(Box::new(lhs)),
6173 ExprKind::Hex(_) => ExprKind::Hex(Box::new(lhs)),
6174 ExprKind::Oct(_) => ExprKind::Oct(Box::new(lhs)),
6175 ExprKind::Lc(_) => ExprKind::Lc(Box::new(lhs)),
6176 ExprKind::Uc(_) => ExprKind::Uc(Box::new(lhs)),
6177 ExprKind::Lcfirst(_) => ExprKind::Lcfirst(Box::new(lhs)),
6178 ExprKind::Ucfirst(_) => ExprKind::Ucfirst(Box::new(lhs)),
6179 ExprKind::Fc(_) => ExprKind::Fc(Box::new(lhs)),
6180 ExprKind::Chr(_) => ExprKind::Chr(Box::new(lhs)),
6181 ExprKind::Ord(_) => ExprKind::Ord(Box::new(lhs)),
6182 ExprKind::Chomp(_) => ExprKind::Chomp(Box::new(lhs)),
6183 ExprKind::Chop(_) => ExprKind::Chop(Box::new(lhs)),
6184 ExprKind::Defined(_) => ExprKind::Defined(Box::new(lhs)),
6185 ExprKind::Ref(_) => ExprKind::Ref(Box::new(lhs)),
6186 ExprKind::ScalarContext(_) => ExprKind::ScalarContext(Box::new(lhs)),
6187 ExprKind::Keys(_) => ExprKind::Keys(Box::new(lhs)),
6188 ExprKind::Values(_) => ExprKind::Values(Box::new(lhs)),
6189 ExprKind::Each(_) => ExprKind::Each(Box::new(lhs)),
6190 ExprKind::Pop(_) => ExprKind::Pop(Box::new(lhs)),
6191 ExprKind::Shift(_) => ExprKind::Shift(Box::new(lhs)),
6192 ExprKind::Delete(_) => ExprKind::Delete(Box::new(lhs)),
6193 ExprKind::Exists(_) => ExprKind::Exists(Box::new(lhs)),
6194 ExprKind::ReverseExpr(_) => ExprKind::ReverseExpr(Box::new(lhs)),
6195 ExprKind::Rev(_) => ExprKind::Rev(Box::new(lhs)),
6196 ExprKind::Slurp(_) => ExprKind::Slurp(Box::new(lhs)),
6197 ExprKind::Capture(_) => ExprKind::Capture(Box::new(lhs)),
6198 ExprKind::Qx(_) => ExprKind::Qx(Box::new(lhs)),
6199 ExprKind::FetchUrl(_) => ExprKind::FetchUrl(Box::new(lhs)),
6200 ExprKind::Close(_) => ExprKind::Close(Box::new(lhs)),
6201 ExprKind::Chdir(_) => ExprKind::Chdir(Box::new(lhs)),
6202 ExprKind::Readdir(_) => ExprKind::Readdir(Box::new(lhs)),
6203 ExprKind::Closedir(_) => ExprKind::Closedir(Box::new(lhs)),
6204 ExprKind::Rewinddir(_) => ExprKind::Rewinddir(Box::new(lhs)),
6205 ExprKind::Telldir(_) => ExprKind::Telldir(Box::new(lhs)),
6206 ExprKind::Stat(_) => ExprKind::Stat(Box::new(lhs)),
6207 ExprKind::Lstat(_) => ExprKind::Lstat(Box::new(lhs)),
6208 ExprKind::Readlink(_) => ExprKind::Readlink(Box::new(lhs)),
6209 ExprKind::Study(_) => ExprKind::Study(Box::new(lhs)),
6210 ExprKind::Await(_) => ExprKind::Await(Box::new(lhs)),
6211 ExprKind::Eval(_) => ExprKind::Eval(Box::new(lhs)),
6212 ExprKind::Rand(_) => ExprKind::Rand(Some(Box::new(lhs))),
6213 ExprKind::Srand(_) => ExprKind::Srand(Some(Box::new(lhs))),
6214 ExprKind::Pos(_) => ExprKind::Pos(Some(Box::new(lhs))),
6215 ExprKind::Exit(_) => ExprKind::Exit(Some(Box::new(lhs))),
6216
6217 ExprKind::MapExpr {
6219 block,
6220 list: _,
6221 flatten_array_refs,
6222 stream,
6223 } => ExprKind::MapExpr {
6224 block,
6225 list: Box::new(lhs),
6226 flatten_array_refs,
6227 stream,
6228 },
6229 ExprKind::MapExprComma {
6230 expr,
6231 list: _,
6232 flatten_array_refs,
6233 stream,
6234 } => ExprKind::MapExprComma {
6235 expr,
6236 list: Box::new(lhs),
6237 flatten_array_refs,
6238 stream,
6239 },
6240 ExprKind::GrepExpr {
6241 block,
6242 list: _,
6243 keyword,
6244 } => ExprKind::GrepExpr {
6245 block,
6246 list: Box::new(lhs),
6247 keyword,
6248 },
6249 ExprKind::GrepExprComma {
6250 expr,
6251 list: _,
6252 keyword,
6253 } => ExprKind::GrepExprComma {
6254 expr,
6255 list: Box::new(lhs),
6256 keyword,
6257 },
6258 ExprKind::ForEachExpr { block, list: _ } => ExprKind::ForEachExpr {
6259 block,
6260 list: Box::new(lhs),
6261 },
6262 ExprKind::SortExpr { cmp, list: _ } => ExprKind::SortExpr {
6263 cmp,
6264 list: Box::new(lhs),
6265 },
6266 ExprKind::JoinExpr { separator, list: _ } => ExprKind::JoinExpr {
6267 separator,
6268 list: Box::new(lhs),
6269 },
6270 ExprKind::ReduceExpr { block, list: _ } => ExprKind::ReduceExpr {
6271 block,
6272 list: Box::new(lhs),
6273 },
6274 ExprKind::PMapExpr {
6275 block,
6276 list: _,
6277 progress,
6278 flat_outputs,
6279 on_cluster,
6280 stream,
6281 } => ExprKind::PMapExpr {
6282 block,
6283 list: Box::new(lhs),
6284 progress,
6285 flat_outputs,
6286 on_cluster,
6287 stream,
6288 },
6289 ExprKind::PMapChunkedExpr {
6290 chunk_size,
6291 block,
6292 list: _,
6293 progress,
6294 } => ExprKind::PMapChunkedExpr {
6295 chunk_size,
6296 block,
6297 list: Box::new(lhs),
6298 progress,
6299 },
6300 ExprKind::PGrepExpr {
6301 block,
6302 list: _,
6303 progress,
6304 stream,
6305 } => ExprKind::PGrepExpr {
6306 block,
6307 list: Box::new(lhs),
6308 progress,
6309 stream,
6310 },
6311 ExprKind::PForExpr {
6312 block,
6313 list: _,
6314 progress,
6315 } => ExprKind::PForExpr {
6316 block,
6317 list: Box::new(lhs),
6318 progress,
6319 },
6320 ExprKind::PSortExpr {
6321 cmp,
6322 list: _,
6323 progress,
6324 } => ExprKind::PSortExpr {
6325 cmp,
6326 list: Box::new(lhs),
6327 progress,
6328 },
6329 ExprKind::PReduceExpr {
6330 block,
6331 list: _,
6332 progress,
6333 } => ExprKind::PReduceExpr {
6334 block,
6335 list: Box::new(lhs),
6336 progress,
6337 },
6338 ExprKind::PcacheExpr {
6339 block,
6340 list: _,
6341 progress,
6342 } => ExprKind::PcacheExpr {
6343 block,
6344 list: Box::new(lhs),
6345 progress,
6346 },
6347 ExprKind::PReduceInitExpr {
6348 init,
6349 block,
6350 list: _,
6351 progress,
6352 } => ExprKind::PReduceInitExpr {
6353 init,
6354 block,
6355 list: Box::new(lhs),
6356 progress,
6357 },
6358 ExprKind::PMapReduceExpr {
6359 map_block,
6360 reduce_block,
6361 list: _,
6362 progress,
6363 } => ExprKind::PMapReduceExpr {
6364 map_block,
6365 reduce_block,
6366 list: Box::new(lhs),
6367 progress,
6368 },
6369
6370 ExprKind::Push { array, mut values } => {
6375 values.insert(0, lhs);
6376 ExprKind::Push { array, values }
6377 }
6378 ExprKind::Unshift { array, mut values } => {
6379 values.insert(0, lhs);
6380 ExprKind::Unshift { array, values }
6381 }
6382
6383 ExprKind::SplitExpr {
6385 pattern,
6386 string: _,
6387 limit,
6388 } => ExprKind::SplitExpr {
6389 pattern,
6390 string: Box::new(lhs),
6391 limit,
6392 },
6393
6394 ExprKind::Substitution {
6398 pattern,
6399 replacement,
6400 mut flags,
6401 expr: _,
6402 delim,
6403 } => {
6404 if !flags.contains('r') {
6405 flags.push('r');
6406 }
6407 ExprKind::Substitution {
6408 expr: Box::new(lhs),
6409 pattern,
6410 replacement,
6411 flags,
6412 delim,
6413 }
6414 }
6415 ExprKind::Transliterate {
6416 from,
6417 to,
6418 mut flags,
6419 expr: _,
6420 delim,
6421 } => {
6422 if !flags.contains('r') {
6423 flags.push('r');
6424 }
6425 ExprKind::Transliterate {
6426 expr: Box::new(lhs),
6427 from,
6428 to,
6429 flags,
6430 delim,
6431 }
6432 }
6433 ExprKind::Match {
6434 pattern,
6435 flags,
6436 scalar_g,
6437 expr: _,
6438 delim,
6439 } => ExprKind::Match {
6440 expr: Box::new(lhs),
6441 pattern,
6442 flags,
6443 scalar_g,
6444 delim,
6445 },
6446 ExprKind::Regex(pattern, flags) => ExprKind::Match {
6448 expr: Box::new(lhs),
6449 pattern,
6450 flags,
6451 scalar_g: false,
6452 delim: '/',
6453 },
6454
6455 ExprKind::Bareword(name) => match name.as_str() {
6457 "reverse" => {
6458 if crate::no_interop_mode() {
6459 return Err(self.syntax_err(
6460 "stryke uses `rev` instead of `reverse` (--no-interop)",
6461 line,
6462 ));
6463 }
6464 ExprKind::ReverseExpr(Box::new(lhs))
6465 }
6466 "rv" | "reversed" | "rev" => ExprKind::Rev(Box::new(lhs)),
6467 "uq" | "uniq" | "distinct" => ExprKind::FuncCall {
6468 name: "uniq".to_string(),
6469 args: vec![lhs],
6470 },
6471 "fl" | "flatten" => ExprKind::FuncCall {
6472 name: "flatten".to_string(),
6473 args: vec![lhs],
6474 },
6475 _ => ExprKind::FuncCall {
6476 name,
6477 args: vec![lhs],
6478 },
6479 },
6480
6481 kind @ (ExprKind::ScalarVar(_)
6483 | ExprKind::ArrayElement { .. }
6484 | ExprKind::HashElement { .. }
6485 | ExprKind::Deref { .. }
6486 | ExprKind::ArrowDeref { .. }
6487 | ExprKind::CodeRef { .. }
6488 | ExprKind::SubroutineRef(_)
6489 | ExprKind::SubroutineCodeRef(_)
6490 | ExprKind::DynamicSubCodeRef(_)) => ExprKind::IndirectCall {
6491 target: Box::new(Expr { kind, line: rline }),
6492 args: vec![lhs],
6493 ampersand: false,
6494 pass_caller_arglist: false,
6495 },
6496
6497 ExprKind::Do(inner) if matches!(inner.kind, ExprKind::CodeRef { .. }) => {
6501 ExprKind::IndirectCall {
6502 target: inner,
6503 args: vec![lhs],
6504 ampersand: false,
6505 pass_caller_arglist: false,
6506 }
6507 }
6508
6509 other => {
6510 return Err(self.syntax_err(
6511 format!(
6512 "right-hand side of `|>` must be a call, builtin, or coderef \
6513 expression (got {})",
6514 Self::expr_kind_name(&other)
6515 ),
6516 line,
6517 ));
6518 }
6519 };
6520 Ok(Expr {
6521 kind: new_kind,
6522 line,
6523 })
6524 }
6525
6526 fn expr_kind_name(kind: &ExprKind) -> &'static str {
6528 match kind {
6529 ExprKind::Integer(_) | ExprKind::Float(_) => "numeric literal",
6530 ExprKind::String(_) | ExprKind::InterpolatedString(_) => "string literal",
6531 ExprKind::BinOp { .. } => "binary expression",
6532 ExprKind::UnaryOp { .. } => "unary expression",
6533 ExprKind::Ternary { .. } => "ternary expression",
6534 ExprKind::Assign { .. } | ExprKind::CompoundAssign { .. } => "assignment",
6535 ExprKind::List(_) => "list expression",
6536 ExprKind::Range { .. } => "range expression",
6537 _ => "expression",
6538 }
6539 }
6540
6541 fn parse_or_word(&mut self) -> PerlResult<Expr> {
6543 let mut left = self.parse_and_word()?;
6544 while matches!(self.peek(), Token::LogOrWord) {
6545 let line = left.line;
6546 self.advance();
6547 let right = self.parse_and_word()?;
6548 left = Expr {
6549 kind: ExprKind::BinOp {
6550 left: Box::new(left),
6551 op: BinOp::LogOrWord,
6552 right: Box::new(right),
6553 },
6554 line,
6555 };
6556 }
6557 Ok(left)
6558 }
6559
6560 fn parse_and_word(&mut self) -> PerlResult<Expr> {
6561 let mut left = self.parse_not_word()?;
6562 while matches!(self.peek(), Token::LogAndWord) {
6563 let line = left.line;
6564 self.advance();
6565 let right = self.parse_not_word()?;
6566 left = Expr {
6567 kind: ExprKind::BinOp {
6568 left: Box::new(left),
6569 op: BinOp::LogAndWord,
6570 right: Box::new(right),
6571 },
6572 line,
6573 };
6574 }
6575 Ok(left)
6576 }
6577
6578 fn parse_not_word(&mut self) -> PerlResult<Expr> {
6579 if matches!(self.peek(), Token::LogNotWord) {
6580 let line = self.peek_line();
6581 self.advance();
6582 let expr = self.parse_not_word()?;
6583 return Ok(Expr {
6584 kind: ExprKind::UnaryOp {
6585 op: UnaryOp::LogNotWord,
6586 expr: Box::new(expr),
6587 },
6588 line,
6589 });
6590 }
6591 self.parse_range()
6592 }
6593
6594 fn parse_log_or(&mut self) -> PerlResult<Expr> {
6595 let mut left = self.parse_log_and()?;
6596 loop {
6597 let op = match self.peek() {
6598 Token::LogOr => BinOp::LogOr,
6599 Token::DefinedOr => BinOp::DefinedOr,
6600 _ => break,
6601 };
6602 let line = left.line;
6603 self.advance();
6604 let right = self.parse_log_and()?;
6605 left = Expr {
6606 kind: ExprKind::BinOp {
6607 left: Box::new(left),
6608 op,
6609 right: Box::new(right),
6610 },
6611 line,
6612 };
6613 }
6614 Ok(left)
6615 }
6616
6617 fn parse_log_and(&mut self) -> PerlResult<Expr> {
6618 let mut left = self.parse_bit_or()?;
6619 while matches!(self.peek(), Token::LogAnd) {
6620 let line = left.line;
6621 self.advance();
6622 let right = self.parse_bit_or()?;
6623 left = Expr {
6624 kind: ExprKind::BinOp {
6625 left: Box::new(left),
6626 op: BinOp::LogAnd,
6627 right: Box::new(right),
6628 },
6629 line,
6630 };
6631 }
6632 Ok(left)
6633 }
6634
6635 fn parse_bit_or(&mut self) -> PerlResult<Expr> {
6636 let mut left = self.parse_bit_xor()?;
6637 while matches!(self.peek(), Token::BitOr) {
6638 let line = left.line;
6639 self.advance();
6640 let right = self.parse_bit_xor()?;
6641 left = Expr {
6642 kind: ExprKind::BinOp {
6643 left: Box::new(left),
6644 op: BinOp::BitOr,
6645 right: Box::new(right),
6646 },
6647 line,
6648 };
6649 }
6650 Ok(left)
6651 }
6652
6653 fn parse_bit_xor(&mut self) -> PerlResult<Expr> {
6654 let mut left = self.parse_bit_and()?;
6655 while matches!(self.peek(), Token::BitXor) {
6656 let line = left.line;
6657 self.advance();
6658 let right = self.parse_bit_and()?;
6659 left = Expr {
6660 kind: ExprKind::BinOp {
6661 left: Box::new(left),
6662 op: BinOp::BitXor,
6663 right: Box::new(right),
6664 },
6665 line,
6666 };
6667 }
6668 Ok(left)
6669 }
6670
6671 fn parse_bit_and(&mut self) -> PerlResult<Expr> {
6672 let mut left = self.parse_equality()?;
6673 while matches!(self.peek(), Token::BitAnd) {
6674 let line = left.line;
6675 self.advance();
6676 let right = self.parse_equality()?;
6677 left = Expr {
6678 kind: ExprKind::BinOp {
6679 left: Box::new(left),
6680 op: BinOp::BitAnd,
6681 right: Box::new(right),
6682 },
6683 line,
6684 };
6685 }
6686 Ok(left)
6687 }
6688
6689 fn parse_equality(&mut self) -> PerlResult<Expr> {
6690 let mut left = self.parse_comparison()?;
6691 loop {
6692 let op = match self.peek() {
6693 Token::NumEq => BinOp::NumEq,
6694 Token::NumNe => BinOp::NumNe,
6695 Token::StrEq => BinOp::StrEq,
6696 Token::StrNe => BinOp::StrNe,
6697 Token::Spaceship => BinOp::Spaceship,
6698 Token::StrCmp => BinOp::StrCmp,
6699 _ => break,
6700 };
6701 let line = left.line;
6702 self.advance();
6703 let right = self.parse_comparison()?;
6704 left = Expr {
6705 kind: ExprKind::BinOp {
6706 left: Box::new(left),
6707 op,
6708 right: Box::new(right),
6709 },
6710 line,
6711 };
6712 }
6713 Ok(left)
6714 }
6715
6716 fn parse_comparison(&mut self) -> PerlResult<Expr> {
6717 let left = self.parse_shift()?;
6718 let first_op = match self.peek() {
6719 Token::NumLt => BinOp::NumLt,
6720 Token::NumGt => BinOp::NumGt,
6721 Token::NumLe => BinOp::NumLe,
6722 Token::NumGe => BinOp::NumGe,
6723 Token::StrLt => BinOp::StrLt,
6724 Token::StrGt => BinOp::StrGt,
6725 Token::StrLe => BinOp::StrLe,
6726 Token::StrGe => BinOp::StrGe,
6727 _ => return Ok(left),
6728 };
6729 let line = left.line;
6730 self.advance();
6731 let middle = self.parse_shift()?;
6732
6733 let second_op = match self.peek() {
6734 Token::NumLt => Some(BinOp::NumLt),
6735 Token::NumGt => Some(BinOp::NumGt),
6736 Token::NumLe => Some(BinOp::NumLe),
6737 Token::NumGe => Some(BinOp::NumGe),
6738 Token::StrLt => Some(BinOp::StrLt),
6739 Token::StrGt => Some(BinOp::StrGt),
6740 Token::StrLe => Some(BinOp::StrLe),
6741 Token::StrGe => Some(BinOp::StrGe),
6742 _ => None,
6743 };
6744
6745 if second_op.is_none() {
6746 return Ok(Expr {
6747 kind: ExprKind::BinOp {
6748 left: Box::new(left),
6749 op: first_op,
6750 right: Box::new(middle),
6751 },
6752 line,
6753 });
6754 }
6755
6756 let mut operands = vec![left, middle];
6759 let mut ops = vec![first_op];
6760
6761 loop {
6762 let op = match self.peek() {
6763 Token::NumLt => BinOp::NumLt,
6764 Token::NumGt => BinOp::NumGt,
6765 Token::NumLe => BinOp::NumLe,
6766 Token::NumGe => BinOp::NumGe,
6767 Token::StrLt => BinOp::StrLt,
6768 Token::StrGt => BinOp::StrGt,
6769 Token::StrLe => BinOp::StrLe,
6770 Token::StrGe => BinOp::StrGe,
6771 _ => break,
6772 };
6773 self.advance();
6774 ops.push(op);
6775 operands.push(self.parse_shift()?);
6776 }
6777
6778 let mut result = Expr {
6780 kind: ExprKind::BinOp {
6781 left: Box::new(operands[0].clone()),
6782 op: ops[0],
6783 right: Box::new(operands[1].clone()),
6784 },
6785 line,
6786 };
6787
6788 for i in 1..ops.len() {
6789 let cmp = Expr {
6790 kind: ExprKind::BinOp {
6791 left: Box::new(operands[i].clone()),
6792 op: ops[i],
6793 right: Box::new(operands[i + 1].clone()),
6794 },
6795 line,
6796 };
6797 result = Expr {
6798 kind: ExprKind::BinOp {
6799 left: Box::new(result),
6800 op: BinOp::LogAnd,
6801 right: Box::new(cmp),
6802 },
6803 line,
6804 };
6805 }
6806
6807 Ok(result)
6808 }
6809
6810 fn parse_shift(&mut self) -> PerlResult<Expr> {
6811 let mut left = self.parse_addition()?;
6812 loop {
6813 let op = match self.peek() {
6814 Token::ShiftLeft => BinOp::ShiftLeft,
6815 Token::ShiftRight => BinOp::ShiftRight,
6816 _ => break,
6817 };
6818 let line = left.line;
6819 self.advance();
6820 let right = self.parse_addition()?;
6821 left = Expr {
6822 kind: ExprKind::BinOp {
6823 left: Box::new(left),
6824 op,
6825 right: Box::new(right),
6826 },
6827 line,
6828 };
6829 }
6830 Ok(left)
6831 }
6832
6833 fn parse_addition(&mut self) -> PerlResult<Expr> {
6834 let mut left = self.parse_multiplication()?;
6835 loop {
6836 let op = match self.peek() {
6839 Token::Plus if self.peek_line() == self.prev_line() => BinOp::Add,
6840 Token::Minus if self.peek_line() == self.prev_line() => BinOp::Sub,
6841 Token::Dot => BinOp::Concat,
6842 _ => break,
6843 };
6844 let line = left.line;
6845 self.advance();
6846 let right = self.parse_multiplication()?;
6847 left = Expr {
6848 kind: ExprKind::BinOp {
6849 left: Box::new(left),
6850 op,
6851 right: Box::new(right),
6852 },
6853 line,
6854 };
6855 }
6856 Ok(left)
6857 }
6858
6859 fn parse_multiplication(&mut self) -> PerlResult<Expr> {
6860 let mut left = self.parse_regex_bind()?;
6861 loop {
6862 let op = match self.peek() {
6863 Token::Star => BinOp::Mul,
6864 Token::Slash if self.suppress_slash_as_div == 0 => BinOp::Div,
6865 Token::Percent if self.peek_line() == self.prev_line() => BinOp::Mod,
6868 Token::X => {
6869 let line = left.line;
6870 self.advance();
6871 let right = self.parse_regex_bind()?;
6872 left = Expr {
6873 kind: ExprKind::Repeat {
6874 expr: Box::new(left),
6875 count: Box::new(right),
6876 },
6877 line,
6878 };
6879 continue;
6880 }
6881 _ => break,
6882 };
6883 let line = left.line;
6884 self.advance();
6885 let right = self.parse_regex_bind()?;
6886 left = Expr {
6887 kind: ExprKind::BinOp {
6888 left: Box::new(left),
6889 op,
6890 right: Box::new(right),
6891 },
6892 line,
6893 };
6894 }
6895 Ok(left)
6896 }
6897
6898 fn parse_regex_bind(&mut self) -> PerlResult<Expr> {
6899 let left = self.parse_unary()?;
6900 match self.peek() {
6901 Token::BindMatch => {
6902 let line = left.line;
6903 self.advance();
6904 match self.peek().clone() {
6905 Token::Regex(pattern, flags, delim) => {
6906 self.advance();
6907 Ok(Expr {
6908 kind: ExprKind::Match {
6909 expr: Box::new(left),
6910 pattern,
6911 flags,
6912 scalar_g: false,
6913 delim,
6914 },
6915 line,
6916 })
6917 }
6918 Token::Ident(ref s) if s.starts_with('\x00') => {
6919 let (Token::Ident(encoded), _) = self.advance() else {
6920 unreachable!()
6921 };
6922 let parts: Vec<&str> = encoded.split('\x00').collect();
6923 if parts.len() >= 4 && parts[1] == "s" {
6924 let delim = parts.get(5).and_then(|s| s.chars().next()).unwrap_or('/');
6925 Ok(Expr {
6926 kind: ExprKind::Substitution {
6927 expr: Box::new(left),
6928 pattern: parts[2].to_string(),
6929 replacement: parts[3].to_string(),
6930 flags: parts.get(4).unwrap_or(&"").to_string(),
6931 delim,
6932 },
6933 line,
6934 })
6935 } else if parts.len() >= 4 && parts[1] == "tr" {
6936 let delim = parts.get(5).and_then(|s| s.chars().next()).unwrap_or('/');
6937 Ok(Expr {
6938 kind: ExprKind::Transliterate {
6939 expr: Box::new(left),
6940 from: parts[2].to_string(),
6941 to: parts[3].to_string(),
6942 flags: parts.get(4).unwrap_or(&"").to_string(),
6943 delim,
6944 },
6945 line,
6946 })
6947 } else {
6948 Err(self.syntax_err("Invalid regex binding", line))
6949 }
6950 }
6951 _ => {
6952 let rhs = self.parse_unary()?;
6953 Ok(Expr {
6954 kind: ExprKind::BinOp {
6955 left: Box::new(left),
6956 op: BinOp::BindMatch,
6957 right: Box::new(rhs),
6958 },
6959 line,
6960 })
6961 }
6962 }
6963 }
6964 Token::BindNotMatch => {
6965 let line = left.line;
6966 self.advance();
6967 match self.peek().clone() {
6968 Token::Regex(pattern, flags, delim) => {
6969 self.advance();
6970 Ok(Expr {
6971 kind: ExprKind::UnaryOp {
6972 op: UnaryOp::LogNot,
6973 expr: Box::new(Expr {
6974 kind: ExprKind::Match {
6975 expr: Box::new(left),
6976 pattern,
6977 flags,
6978 scalar_g: false,
6979 delim,
6980 },
6981 line,
6982 }),
6983 },
6984 line,
6985 })
6986 }
6987 Token::Ident(ref s) if s.starts_with('\x00') => {
6988 let (Token::Ident(encoded), _) = self.advance() else {
6989 unreachable!()
6990 };
6991 let parts: Vec<&str> = encoded.split('\x00').collect();
6992 if parts.len() >= 4 && parts[1] == "s" {
6993 let delim = parts.get(5).and_then(|s| s.chars().next()).unwrap_or('/');
6994 Ok(Expr {
6995 kind: ExprKind::UnaryOp {
6996 op: UnaryOp::LogNot,
6997 expr: Box::new(Expr {
6998 kind: ExprKind::Substitution {
6999 expr: Box::new(left),
7000 pattern: parts[2].to_string(),
7001 replacement: parts[3].to_string(),
7002 flags: parts.get(4).unwrap_or(&"").to_string(),
7003 delim,
7004 },
7005 line,
7006 }),
7007 },
7008 line,
7009 })
7010 } else if parts.len() >= 4 && parts[1] == "tr" {
7011 let delim = parts.get(5).and_then(|s| s.chars().next()).unwrap_or('/');
7012 Ok(Expr {
7013 kind: ExprKind::UnaryOp {
7014 op: UnaryOp::LogNot,
7015 expr: Box::new(Expr {
7016 kind: ExprKind::Transliterate {
7017 expr: Box::new(left),
7018 from: parts[2].to_string(),
7019 to: parts[3].to_string(),
7020 flags: parts.get(4).unwrap_or(&"").to_string(),
7021 delim,
7022 },
7023 line,
7024 }),
7025 },
7026 line,
7027 })
7028 } else {
7029 Err(self.syntax_err("Invalid regex binding after !~", line))
7030 }
7031 }
7032 _ => {
7033 let rhs = self.parse_unary()?;
7034 Ok(Expr {
7035 kind: ExprKind::BinOp {
7036 left: Box::new(left),
7037 op: BinOp::BindNotMatch,
7038 right: Box::new(rhs),
7039 },
7040 line,
7041 })
7042 }
7043 }
7044 }
7045 _ => Ok(left),
7046 }
7047 }
7048
7049 fn parse_thread_input(&mut self) -> PerlResult<Expr> {
7052 self.suppress_slash_as_div = self.suppress_slash_as_div.saturating_add(1);
7053 let result = self.parse_range();
7054 self.suppress_slash_as_div = self.suppress_slash_as_div.saturating_sub(1);
7055 result
7056 }
7057
7058 fn parse_range(&mut self) -> PerlResult<Expr> {
7065 let left = self.parse_log_or()?;
7066 let line = left.line;
7067 let (exclusive, _colon_style) = if self.eat(&Token::RangeExclusive) {
7069 (true, false)
7070 } else if self.eat(&Token::Range) {
7071 (false, false)
7072 } else if self.suppress_colon_range == 0 && self.eat(&Token::Colon) {
7073 (false, true)
7076 } else {
7077 return Ok(left);
7078 };
7079 let right = self.parse_log_or()?;
7080 let step = if self.eat(&Token::Colon) {
7082 Some(Box::new(self.parse_unary()?))
7083 } else {
7084 None
7085 };
7086 Ok(Expr {
7087 kind: ExprKind::Range {
7088 from: Box::new(left),
7089 to: Box::new(right),
7090 exclusive,
7091 step,
7092 },
7093 line,
7094 })
7095 }
7096
7097 fn parse_package_qualified_identifier(&mut self) -> PerlResult<String> {
7099 let mut name = match self.advance() {
7100 (Token::Ident(n), _) => n,
7101 (tok, l) => {
7102 return Err(self.syntax_err(format!("Expected identifier, got {:?}", tok), l));
7103 }
7104 };
7105 while self.eat(&Token::PackageSep) {
7106 match self.advance() {
7107 (Token::Ident(part), _) => {
7108 name.push_str("::");
7109 name.push_str(&part);
7110 }
7111 (tok, l) => {
7112 return Err(self
7113 .syntax_err(format!("Expected identifier after `::`, got {:?}", tok), l));
7114 }
7115 }
7116 }
7117 Ok(name)
7118 }
7119
7120 fn parse_qualified_subroutine_name(&mut self) -> PerlResult<String> {
7122 self.parse_package_qualified_identifier()
7123 }
7124
7125 fn parse_unary(&mut self) -> PerlResult<Expr> {
7126 let line = self.peek_line();
7127 match self.peek().clone() {
7128 Token::Minus => {
7129 self.advance();
7130 let expr = self.parse_power()?;
7131 Ok(Expr {
7132 kind: ExprKind::UnaryOp {
7133 op: UnaryOp::Negate,
7134 expr: Box::new(expr),
7135 },
7136 line,
7137 })
7138 }
7139 Token::Plus => {
7142 self.advance();
7143 self.parse_unary()
7144 }
7145 Token::LogNot => {
7146 self.advance();
7147 let expr = self.parse_unary()?;
7148 Ok(Expr {
7149 kind: ExprKind::UnaryOp {
7150 op: UnaryOp::LogNot,
7151 expr: Box::new(expr),
7152 },
7153 line,
7154 })
7155 }
7156 Token::BitNot => {
7157 self.advance();
7158 let expr = self.parse_unary()?;
7159 Ok(Expr {
7160 kind: ExprKind::UnaryOp {
7161 op: UnaryOp::BitNot,
7162 expr: Box::new(expr),
7163 },
7164 line,
7165 })
7166 }
7167 Token::Increment => {
7168 self.advance();
7169 let expr = self.parse_postfix()?;
7170 Ok(Expr {
7171 kind: ExprKind::UnaryOp {
7172 op: UnaryOp::PreIncrement,
7173 expr: Box::new(expr),
7174 },
7175 line,
7176 })
7177 }
7178 Token::Decrement => {
7179 self.advance();
7180 let expr = self.parse_postfix()?;
7181 Ok(Expr {
7182 kind: ExprKind::UnaryOp {
7183 op: UnaryOp::PreDecrement,
7184 expr: Box::new(expr),
7185 },
7186 line,
7187 })
7188 }
7189 Token::BitAnd => {
7190 self.advance();
7193 if matches!(self.peek(), Token::LBrace) {
7194 self.advance();
7195 let inner = self.parse_expression()?;
7196 self.expect(&Token::RBrace)?;
7197 return Ok(Expr {
7198 kind: ExprKind::DynamicSubCodeRef(Box::new(inner)),
7199 line,
7200 });
7201 }
7202 if matches!(self.peek(), Token::Ident(_)) {
7203 let name = self.parse_qualified_subroutine_name()?;
7204 return Ok(Expr {
7205 kind: ExprKind::SubroutineRef(name),
7206 line,
7207 });
7208 }
7209 let target = self.parse_primary()?;
7210 if matches!(self.peek(), Token::LParen) {
7211 self.advance();
7212 let args = self.parse_arg_list()?;
7213 self.expect(&Token::RParen)?;
7214 return Ok(Expr {
7215 kind: ExprKind::IndirectCall {
7216 target: Box::new(target),
7217 args,
7218 ampersand: true,
7219 pass_caller_arglist: false,
7220 },
7221 line,
7222 });
7223 }
7224 Ok(Expr {
7226 kind: ExprKind::IndirectCall {
7227 target: Box::new(target),
7228 args: vec![],
7229 ampersand: true,
7230 pass_caller_arglist: true,
7231 },
7232 line,
7233 })
7234 }
7235 Token::Backslash => {
7236 self.advance();
7237 let expr = self.parse_unary()?;
7238 if let ExprKind::SubroutineRef(name) = expr.kind {
7239 return Ok(Expr {
7240 kind: ExprKind::SubroutineCodeRef(name),
7241 line,
7242 });
7243 }
7244 if matches!(expr.kind, ExprKind::DynamicSubCodeRef(_)) {
7245 return Ok(expr);
7246 }
7247 Ok(Expr {
7249 kind: ExprKind::ScalarRef(Box::new(expr)),
7250 line,
7251 })
7252 }
7253 Token::FileTest(op) => {
7254 self.advance();
7255 let expr = if Self::filetest_allows_implicit_topic(self.peek()) {
7257 Expr {
7258 kind: ExprKind::ScalarVar("_".into()),
7259 line: self.peek_line(),
7260 }
7261 } else {
7262 self.parse_unary()?
7263 };
7264 Ok(Expr {
7265 kind: ExprKind::FileTest {
7266 op,
7267 expr: Box::new(expr),
7268 },
7269 line,
7270 })
7271 }
7272 _ => self.parse_power(),
7273 }
7274 }
7275
7276 fn parse_power(&mut self) -> PerlResult<Expr> {
7277 let left = self.parse_postfix()?;
7278 if matches!(self.peek(), Token::Power) {
7279 let line = left.line;
7280 self.advance();
7281 let right = self.parse_unary()?; return Ok(Expr {
7283 kind: ExprKind::BinOp {
7284 left: Box::new(left),
7285 op: BinOp::Pow,
7286 right: Box::new(right),
7287 },
7288 line,
7289 });
7290 }
7291 Ok(left)
7292 }
7293
7294 fn parse_postfix(&mut self) -> PerlResult<Expr> {
7295 let mut expr = self.parse_primary()?;
7296 loop {
7297 match self.peek().clone() {
7298 Token::Increment => {
7299 if self.peek_line() > self.prev_line() {
7302 break;
7303 }
7304 let line = expr.line;
7305 self.advance();
7306 expr = Expr {
7307 kind: ExprKind::PostfixOp {
7308 expr: Box::new(expr),
7309 op: PostfixOp::Increment,
7310 },
7311 line,
7312 };
7313 }
7314 Token::Decrement => {
7315 if self.peek_line() > self.prev_line() {
7318 break;
7319 }
7320 let line = expr.line;
7321 self.advance();
7322 expr = Expr {
7323 kind: ExprKind::PostfixOp {
7324 expr: Box::new(expr),
7325 op: PostfixOp::Decrement,
7326 },
7327 line,
7328 };
7329 }
7330 Token::LParen => {
7331 if self.suppress_indirect_paren_call > 0 {
7332 break;
7333 }
7334 if self.peek_line() > self.prev_line() {
7338 break;
7339 }
7340 let line = expr.line;
7341 self.advance();
7342 let args = self.parse_arg_list()?;
7343 self.expect(&Token::RParen)?;
7344 expr = Expr {
7345 kind: ExprKind::IndirectCall {
7346 target: Box::new(expr),
7347 args,
7348 ampersand: false,
7349 pass_caller_arglist: false,
7350 },
7351 line,
7352 };
7353 }
7354 Token::Arrow => {
7355 let line = expr.line;
7356 self.advance();
7357 match self.peek().clone() {
7358 Token::LBracket => {
7359 self.advance();
7360 let index = self.parse_expression()?;
7361 self.expect(&Token::RBracket)?;
7362 expr = Expr {
7363 kind: ExprKind::ArrowDeref {
7364 expr: Box::new(expr),
7365 index: Box::new(index),
7366 kind: DerefKind::Array,
7367 },
7368 line,
7369 };
7370 }
7371 Token::LBrace => {
7372 self.advance();
7373 let key = self.parse_hash_subscript_key()?;
7374 self.expect(&Token::RBrace)?;
7375 expr = Expr {
7376 kind: ExprKind::ArrowDeref {
7377 expr: Box::new(expr),
7378 index: Box::new(key),
7379 kind: DerefKind::Hash,
7380 },
7381 line,
7382 };
7383 }
7384 Token::LParen => {
7385 self.advance();
7386 let args = self.parse_arg_list()?;
7387 self.expect(&Token::RParen)?;
7388 expr = Expr {
7389 kind: ExprKind::ArrowDeref {
7390 expr: Box::new(expr),
7391 index: Box::new(Expr {
7392 kind: ExprKind::List(args),
7393 line,
7394 }),
7395 kind: DerefKind::Call,
7396 },
7397 line,
7398 };
7399 }
7400 Token::Ident(method) => {
7401 self.advance();
7402 if method == "SUPER" {
7403 self.expect(&Token::PackageSep)?;
7404 let real_method = match self.advance() {
7405 (Token::Ident(n), _) => n,
7406 (tok, l) => {
7407 return Err(self.syntax_err(
7408 format!(
7409 "Expected method name after SUPER::, got {:?}",
7410 tok
7411 ),
7412 l,
7413 ));
7414 }
7415 };
7416 let args = if self.eat(&Token::LParen) {
7417 let a = self.parse_arg_list()?;
7418 self.expect(&Token::RParen)?;
7419 a
7420 } else {
7421 self.parse_method_arg_list_no_paren()?
7422 };
7423 expr = Expr {
7424 kind: ExprKind::MethodCall {
7425 object: Box::new(expr),
7426 method: real_method,
7427 args,
7428 super_call: true,
7429 },
7430 line,
7431 };
7432 } else {
7433 let mut method_name = method;
7434 while self.eat(&Token::PackageSep) {
7435 match self.advance() {
7436 (Token::Ident(part), _) => {
7437 method_name.push_str("::");
7438 method_name.push_str(&part);
7439 }
7440 (tok, l) => {
7441 return Err(self.syntax_err(
7442 format!(
7443 "Expected identifier after :: in method name, got {:?}",
7444 tok
7445 ),
7446 l,
7447 ));
7448 }
7449 }
7450 }
7451 let args = if self.eat(&Token::LParen) {
7452 let a = self.parse_arg_list()?;
7453 self.expect(&Token::RParen)?;
7454 a
7455 } else {
7456 self.parse_method_arg_list_no_paren()?
7457 };
7458 expr = Expr {
7459 kind: ExprKind::MethodCall {
7460 object: Box::new(expr),
7461 method: method_name,
7462 args,
7463 super_call: false,
7464 },
7465 line,
7466 };
7467 }
7468 }
7469 Token::ArrayAt => {
7475 self.advance(); match self.peek().clone() {
7477 Token::Star => {
7478 self.advance();
7479 expr = Expr {
7480 kind: ExprKind::Deref {
7481 expr: Box::new(expr),
7482 kind: Sigil::Array,
7483 },
7484 line,
7485 };
7486 }
7487 Token::LBracket => {
7488 self.advance();
7489 let indices = self.parse_slice_arg_list(false)?;
7490 self.expect(&Token::RBracket)?;
7491 let source = Expr {
7492 kind: ExprKind::Deref {
7493 expr: Box::new(expr),
7494 kind: Sigil::Array,
7495 },
7496 line,
7497 };
7498 expr = Expr {
7499 kind: ExprKind::AnonymousListSlice {
7500 source: Box::new(source),
7501 indices,
7502 },
7503 line,
7504 };
7505 }
7506 Token::LBrace => {
7507 self.advance();
7508 let keys = self.parse_slice_arg_list(true)?;
7509 self.expect(&Token::RBrace)?;
7510 expr = Expr {
7511 kind: ExprKind::HashSliceDeref {
7512 container: Box::new(expr),
7513 keys,
7514 },
7515 line,
7516 };
7517 }
7518 tok => {
7519 return Err(self.syntax_err(
7520 format!(
7521 "Expected `*`, `[…]`, or `{{…}}` after `->@`, got {:?}",
7522 tok
7523 ),
7524 line,
7525 ));
7526 }
7527 }
7528 }
7529 Token::HashPercent => {
7530 self.advance(); match self.peek().clone() {
7532 Token::Star => {
7533 self.advance();
7534 expr = Expr {
7535 kind: ExprKind::Deref {
7536 expr: Box::new(expr),
7537 kind: Sigil::Hash,
7538 },
7539 line,
7540 };
7541 }
7542 tok => {
7543 return Err(self.syntax_err(
7544 format!("Expected `*` after `->%`, got {:?}", tok),
7545 line,
7546 ));
7547 }
7548 }
7549 }
7550 Token::X => {
7552 self.advance();
7553 let args = if self.eat(&Token::LParen) {
7554 let a = self.parse_arg_list()?;
7555 self.expect(&Token::RParen)?;
7556 a
7557 } else {
7558 self.parse_method_arg_list_no_paren()?
7559 };
7560 expr = Expr {
7561 kind: ExprKind::MethodCall {
7562 object: Box::new(expr),
7563 method: "x".to_string(),
7564 args,
7565 super_call: false,
7566 },
7567 line,
7568 };
7569 }
7570 _ => break,
7571 }
7572 }
7573 Token::LBracket => {
7574 if self.peek_line() > self.prev_line() {
7577 break;
7578 }
7579 let line = expr.line;
7581 if matches!(expr.kind, ExprKind::ScalarVar(_)) {
7582 if let ExprKind::ScalarVar(ref name) = expr.kind {
7583 let name = name.clone();
7584 self.advance();
7585 let index = self.parse_expression()?;
7586 self.expect(&Token::RBracket)?;
7587 expr = Expr {
7588 kind: ExprKind::ArrayElement {
7589 array: name,
7590 index: Box::new(index),
7591 },
7592 line,
7593 };
7594 }
7595 } else if postfix_lbracket_is_arrow_container(&expr) {
7596 self.advance();
7597 let indices = self.parse_arg_list()?;
7598 self.expect(&Token::RBracket)?;
7599 expr = Expr {
7600 kind: ExprKind::ArrowDeref {
7601 expr: Box::new(expr),
7602 index: Box::new(Expr {
7603 kind: ExprKind::List(indices),
7604 line,
7605 }),
7606 kind: DerefKind::Array,
7607 },
7608 line,
7609 };
7610 } else {
7611 self.advance();
7612 let indices = self.parse_arg_list()?;
7613 self.expect(&Token::RBracket)?;
7614 expr = Expr {
7615 kind: ExprKind::AnonymousListSlice {
7616 source: Box::new(expr),
7617 indices,
7618 },
7619 line,
7620 };
7621 }
7622 }
7623 Token::LBrace => {
7624 if self.suppress_scalar_hash_brace > 0 {
7625 break;
7626 }
7627 if self.peek_line() > self.prev_line() {
7630 break;
7631 }
7632 let line = expr.line;
7635 let is_scalar_named_hash = matches!(expr.kind, ExprKind::ScalarVar(_));
7636 let is_chainable_hash_subscript = is_scalar_named_hash
7637 || matches!(
7638 expr.kind,
7639 ExprKind::HashElement { .. }
7640 | ExprKind::ArrayElement { .. }
7641 | ExprKind::ArrowDeref { .. }
7642 | ExprKind::Deref {
7643 kind: Sigil::Scalar,
7644 ..
7645 }
7646 );
7647 if !is_chainable_hash_subscript {
7648 break;
7649 }
7650 self.advance();
7651 let key = self.parse_hash_subscript_key()?;
7652 self.expect(&Token::RBrace)?;
7653 expr = if is_scalar_named_hash {
7654 if let ExprKind::ScalarVar(ref name) = expr.kind {
7655 let name = name.clone();
7656 if name == "_" {
7658 Expr {
7659 kind: ExprKind::ArrowDeref {
7660 expr: Box::new(Expr {
7661 kind: ExprKind::ScalarVar("_".into()),
7662 line,
7663 }),
7664 index: Box::new(key),
7665 kind: DerefKind::Hash,
7666 },
7667 line,
7668 }
7669 } else {
7670 Expr {
7671 kind: ExprKind::HashElement {
7672 hash: name,
7673 key: Box::new(key),
7674 },
7675 line,
7676 }
7677 }
7678 } else {
7679 unreachable!("is_scalar_named_hash implies ScalarVar");
7680 }
7681 } else {
7682 Expr {
7683 kind: ExprKind::ArrowDeref {
7684 expr: Box::new(expr),
7685 index: Box::new(key),
7686 kind: DerefKind::Hash,
7687 },
7688 line,
7689 }
7690 };
7691 }
7692 _ => break,
7693 }
7694 }
7695 Ok(expr)
7696 }
7697
7698 fn parse_primary(&mut self) -> PerlResult<Expr> {
7699 let line = self.peek_line();
7700 if let Token::Ident(ref kw) = self.peek().clone() {
7705 if matches!(kw.as_str(), "my" | "our" | "state" | "local") {
7706 let kw_owned = kw.clone();
7707 let saved_pos = self.pos;
7712 let stmt = self.parse_my_our_local(&kw_owned, false)?;
7713 let decls = match stmt.kind {
7714 StmtKind::My(d)
7715 | StmtKind::Our(d)
7716 | StmtKind::State(d)
7717 | StmtKind::Local(d) => d,
7718 _ => {
7719 self.pos = saved_pos;
7724 return Err(self.syntax_err(
7725 "`my`/`our`/`local` in expression must declare variables",
7726 line,
7727 ));
7728 }
7729 };
7730 return Ok(Expr {
7731 kind: ExprKind::MyExpr {
7732 keyword: kw_owned,
7733 decls,
7734 },
7735 line,
7736 });
7737 }
7738 }
7739 match self.peek().clone() {
7740 Token::Integer(n) => {
7741 self.advance();
7742 Ok(Expr {
7743 kind: ExprKind::Integer(n),
7744 line,
7745 })
7746 }
7747 Token::Float(f) => {
7748 self.advance();
7749 Ok(Expr {
7750 kind: ExprKind::Float(f),
7751 line,
7752 })
7753 }
7754 Token::ArrowBrace => {
7760 self.advance();
7761 let mut stmts = Vec::new();
7762 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
7763 if self.eat(&Token::Semicolon) {
7764 continue;
7765 }
7766 stmts.push(self.parse_statement()?);
7767 }
7768 self.expect(&Token::RBrace)?;
7769 let inner_line = stmts.first().map(|s| s.line).unwrap_or(line);
7770 let inner = Expr {
7771 kind: ExprKind::CodeRef {
7772 params: vec![],
7773 body: stmts,
7774 },
7775 line: inner_line,
7776 };
7777 Ok(Expr {
7778 kind: ExprKind::Do(Box::new(inner)),
7779 line,
7780 })
7781 }
7782 Token::Star => {
7783 self.advance();
7784 if matches!(self.peek(), Token::LBrace) {
7785 self.advance();
7786 let inner = self.parse_expression()?;
7787 self.expect(&Token::RBrace)?;
7788 return Ok(Expr {
7789 kind: ExprKind::Deref {
7790 expr: Box::new(inner),
7791 kind: Sigil::Typeglob,
7792 },
7793 line,
7794 });
7795 }
7796 if matches!(
7798 self.peek(),
7799 Token::ScalarVar(_)
7800 | Token::ArrayVar(_)
7801 | Token::HashVar(_)
7802 | Token::DerefScalarVar(_)
7803 | Token::HashPercent
7804 ) {
7805 let inner = self.parse_postfix()?;
7806 return Ok(Expr {
7807 kind: ExprKind::TypeglobExpr(Box::new(inner)),
7808 line,
7809 });
7810 }
7811 let mut full_name = match self.advance() {
7813 (Token::Ident(n), _) => n,
7814 (Token::X, _) => "x".to_string(),
7815 (tok, l) => {
7816 return Err(self
7817 .syntax_err(format!("Expected identifier after *, got {:?}", tok), l));
7818 }
7819 };
7820 while self.eat(&Token::PackageSep) {
7821 match self.advance() {
7822 (Token::Ident(part), _) => {
7823 full_name = format!("{}::{}", full_name, part);
7824 }
7825 (Token::X, _) => {
7826 full_name = format!("{}::x", full_name);
7827 }
7828 (tok, l) => {
7829 return Err(self.syntax_err(
7830 format!("Expected identifier after :: in typeglob, got {:?}", tok),
7831 l,
7832 ));
7833 }
7834 }
7835 }
7836 Ok(Expr {
7837 kind: ExprKind::Typeglob(full_name),
7838 line,
7839 })
7840 }
7841 Token::SingleString(s) => {
7842 self.advance();
7843 Ok(Expr {
7844 kind: ExprKind::String(s),
7845 line,
7846 })
7847 }
7848 Token::DoubleString(s) => {
7849 self.advance();
7850 self.parse_interpolated_string(&s, line)
7851 }
7852 Token::BacktickString(s) => {
7853 self.advance();
7854 let inner = self.parse_interpolated_string(&s, line)?;
7855 Ok(Expr {
7856 kind: ExprKind::Qx(Box::new(inner)),
7857 line,
7858 })
7859 }
7860 Token::HereDoc(_, body, interpolate) => {
7861 self.advance();
7862 if interpolate {
7863 self.parse_interpolated_string(&body, line)
7864 } else {
7865 Ok(Expr {
7866 kind: ExprKind::String(body),
7867 line,
7868 })
7869 }
7870 }
7871 Token::Regex(pattern, flags, _delim) => {
7872 self.advance();
7873 Ok(Expr {
7874 kind: ExprKind::Regex(pattern, flags),
7875 line,
7876 })
7877 }
7878 Token::QW(words) => {
7879 self.advance();
7880 Ok(Expr {
7881 kind: ExprKind::QW(words),
7882 line,
7883 })
7884 }
7885 Token::DerefScalarVar(name) => {
7886 self.advance();
7887 Ok(Expr {
7888 kind: ExprKind::Deref {
7889 expr: Box::new(Expr {
7890 kind: ExprKind::ScalarVar(name),
7891 line,
7892 }),
7893 kind: Sigil::Scalar,
7894 },
7895 line,
7896 })
7897 }
7898 Token::ScalarVar(name) => {
7899 self.advance();
7900 Ok(Expr {
7901 kind: ExprKind::ScalarVar(name),
7902 line,
7903 })
7904 }
7905 Token::ArrayVar(name) => {
7906 self.advance();
7907 match self.peek() {
7909 Token::LBracket => {
7910 self.advance();
7911 let indices = self.parse_slice_arg_list(false)?;
7912 self.expect(&Token::RBracket)?;
7913 Ok(Expr {
7914 kind: ExprKind::ArraySlice {
7915 array: name,
7916 indices,
7917 },
7918 line,
7919 })
7920 }
7921 Token::LBrace if self.suppress_scalar_hash_brace == 0 => {
7922 self.advance();
7923 let keys = self.parse_slice_arg_list(true)?;
7924 self.expect(&Token::RBrace)?;
7925 Ok(Expr {
7926 kind: ExprKind::HashSlice { hash: name, keys },
7927 line,
7928 })
7929 }
7930 _ => Ok(Expr {
7931 kind: ExprKind::ArrayVar(name),
7932 line,
7933 }),
7934 }
7935 }
7936 Token::HashVar(name) => {
7937 self.advance();
7938 Ok(Expr {
7939 kind: ExprKind::HashVar(name),
7940 line,
7941 })
7942 }
7943 Token::HashPercent => {
7944 self.advance();
7946 if matches!(self.peek(), Token::ScalarVar(_)) {
7947 let n = match self.advance() {
7948 (Token::ScalarVar(n), _) => n,
7949 (tok, l) => {
7950 return Err(self.syntax_err(
7951 format!("Expected scalar variable after %%, got {:?}", tok),
7952 l,
7953 ));
7954 }
7955 };
7956 return Ok(Expr {
7957 kind: ExprKind::Deref {
7958 expr: Box::new(Expr {
7959 kind: ExprKind::ScalarVar(n),
7960 line,
7961 }),
7962 kind: Sigil::Hash,
7963 },
7964 line,
7965 });
7966 }
7967 if matches!(self.peek(), Token::LBracket) {
7972 self.advance();
7973 let pairs = self.parse_hashref_pairs_until(&Token::RBracket)?;
7974 self.expect(&Token::RBracket)?;
7975 let href = Expr {
7976 kind: ExprKind::HashRef(pairs),
7977 line,
7978 };
7979 return Ok(Expr {
7980 kind: ExprKind::Deref {
7981 expr: Box::new(href),
7982 kind: Sigil::Hash,
7983 },
7984 line,
7985 });
7986 }
7987 self.expect(&Token::LBrace)?;
7988 let looks_like_pair = matches!(
7994 self.peek(),
7995 Token::Ident(_) | Token::SingleString(_) | Token::DoubleString(_)
7996 ) && matches!(self.peek_at(1), Token::FatArrow);
7997 let inner = if looks_like_pair {
7998 let pairs = self.parse_hashref_pairs_until(&Token::RBrace)?;
7999 Expr {
8000 kind: ExprKind::HashRef(pairs),
8001 line,
8002 }
8003 } else {
8004 self.parse_expression()?
8005 };
8006 self.expect(&Token::RBrace)?;
8007 Ok(Expr {
8008 kind: ExprKind::Deref {
8009 expr: Box::new(inner),
8010 kind: Sigil::Hash,
8011 },
8012 line,
8013 })
8014 }
8015 Token::ArrayAt => {
8016 self.advance();
8017 if matches!(self.peek(), Token::LBrace) {
8019 self.advance();
8020 let inner = self.parse_expression()?;
8021 self.expect(&Token::RBrace)?;
8022 return Ok(Expr {
8023 kind: ExprKind::Deref {
8024 expr: Box::new(inner),
8025 kind: Sigil::Array,
8026 },
8027 line,
8028 });
8029 }
8030 if matches!(self.peek(), Token::LBracket) {
8034 self.advance();
8035 let mut elems = Vec::new();
8036 if !matches!(self.peek(), Token::RBracket) {
8037 elems.push(self.parse_assign_expr()?);
8038 while self.eat(&Token::Comma) {
8039 if matches!(self.peek(), Token::RBracket) {
8040 break;
8041 }
8042 elems.push(self.parse_assign_expr()?);
8043 }
8044 }
8045 self.expect(&Token::RBracket)?;
8046 let aref = Expr {
8047 kind: ExprKind::ArrayRef(elems),
8048 line,
8049 };
8050 return Ok(Expr {
8051 kind: ExprKind::Deref {
8052 expr: Box::new(aref),
8053 kind: Sigil::Array,
8054 },
8055 line,
8056 });
8057 }
8058 let container = match self.peek().clone() {
8060 Token::ScalarVar(n) => {
8061 self.advance();
8062 Expr {
8063 kind: ExprKind::ScalarVar(n),
8064 line,
8065 }
8066 }
8067 _ => {
8068 return Err(self.syntax_err(
8069 "Expected `$name`, `{`, or `[` after `@` (e.g. `@$aref`, `@{expr}`, `@[1,2,3]`, or `@$href{keys}`)",
8070 line,
8071 ));
8072 }
8073 };
8074 if matches!(self.peek(), Token::LBrace) {
8075 self.advance();
8076 let keys = self.parse_slice_arg_list(true)?;
8077 self.expect(&Token::RBrace)?;
8078 return Ok(Expr {
8079 kind: ExprKind::HashSliceDeref {
8080 container: Box::new(container),
8081 keys,
8082 },
8083 line,
8084 });
8085 }
8086 Ok(Expr {
8087 kind: ExprKind::Deref {
8088 expr: Box::new(container),
8089 kind: Sigil::Array,
8090 },
8091 line,
8092 })
8093 }
8094 Token::LParen => {
8095 self.advance();
8096 if matches!(self.peek(), Token::RParen) {
8097 self.advance();
8098 return Ok(Expr {
8099 kind: ExprKind::List(vec![]),
8100 line,
8101 });
8102 }
8103 let saved_no_pipe = self.no_pipe_forward_depth;
8106 self.no_pipe_forward_depth = 0;
8107 let expr = self.parse_expression();
8108 self.no_pipe_forward_depth = saved_no_pipe;
8109 let expr = expr?;
8110 self.expect(&Token::RParen)?;
8111 Ok(expr)
8112 }
8113 Token::LBracket => {
8114 self.advance();
8115 let elems = self.parse_arg_list()?;
8116 self.expect(&Token::RBracket)?;
8117 Ok(Expr {
8118 kind: ExprKind::ArrayRef(elems),
8119 line,
8120 })
8121 }
8122 Token::LBrace => {
8123 self.advance();
8125 let saved = self.pos;
8127 match self.try_parse_hash_ref() {
8128 Ok(pairs) => Ok(Expr {
8129 kind: ExprKind::HashRef(pairs),
8130 line,
8131 }),
8132 Err(_) => {
8133 self.pos = saved;
8134 let mut stmts = Vec::new();
8136 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
8137 if self.eat(&Token::Semicolon) {
8138 continue;
8139 }
8140 stmts.push(self.parse_statement()?);
8141 }
8142 self.expect(&Token::RBrace)?;
8143 Ok(Expr {
8144 kind: ExprKind::CodeRef {
8145 params: vec![],
8146 body: stmts,
8147 },
8148 line,
8149 })
8150 }
8151 }
8152 }
8153 Token::Diamond => {
8154 self.advance();
8155 Ok(Expr {
8156 kind: ExprKind::ReadLine(None),
8157 line,
8158 })
8159 }
8160 Token::ReadLine(handle) => {
8161 self.advance();
8162 Ok(Expr {
8163 kind: ExprKind::ReadLine(Some(handle)),
8164 line,
8165 })
8166 }
8167
8168 Token::ThreadArrow => {
8170 self.advance();
8171 self.parse_thread_macro(line, false)
8172 }
8173 Token::ThreadArrowLast => {
8174 self.advance();
8175 self.parse_thread_macro(line, true)
8176 }
8177 Token::Ident(ref name) => {
8178 let name = name.clone();
8179 if name.starts_with('\x00') {
8181 self.advance();
8182 let parts: Vec<&str> = name.split('\x00').collect();
8183 if parts.len() >= 4 && parts[1] == "s" {
8184 let delim = parts.get(5).and_then(|s| s.chars().next()).unwrap_or('/');
8185 return Ok(Expr {
8186 kind: ExprKind::Substitution {
8187 expr: Box::new(Expr {
8188 kind: ExprKind::ScalarVar("_".into()),
8189 line,
8190 }),
8191 pattern: parts[2].to_string(),
8192 replacement: parts[3].to_string(),
8193 flags: parts.get(4).unwrap_or(&"").to_string(),
8194 delim,
8195 },
8196 line,
8197 });
8198 }
8199 if parts.len() >= 4 && parts[1] == "tr" {
8200 let delim = parts.get(5).and_then(|s| s.chars().next()).unwrap_or('/');
8201 return Ok(Expr {
8202 kind: ExprKind::Transliterate {
8203 expr: Box::new(Expr {
8204 kind: ExprKind::ScalarVar("_".into()),
8205 line,
8206 }),
8207 from: parts[2].to_string(),
8208 to: parts[3].to_string(),
8209 flags: parts.get(4).unwrap_or(&"").to_string(),
8210 delim,
8211 },
8212 line,
8213 });
8214 }
8215 return Err(self.syntax_err("Unexpected encoded token", line));
8216 }
8217 self.parse_named_expr(name)
8218 }
8219
8220 Token::Percent => {
8223 self.advance();
8224 match self.peek().clone() {
8225 Token::Ident(name) => {
8226 self.advance();
8227 Ok(Expr {
8228 kind: ExprKind::HashVar(name),
8229 line,
8230 })
8231 }
8232 Token::ScalarVar(n) => {
8233 self.advance();
8234 Ok(Expr {
8235 kind: ExprKind::Deref {
8236 expr: Box::new(Expr {
8237 kind: ExprKind::ScalarVar(n),
8238 line,
8239 }),
8240 kind: Sigil::Hash,
8241 },
8242 line,
8243 })
8244 }
8245 Token::LBrace => {
8246 self.advance();
8247 let looks_like_pair = matches!(
8248 self.peek(),
8249 Token::Ident(_) | Token::SingleString(_) | Token::DoubleString(_)
8250 ) && matches!(self.peek_at(1), Token::FatArrow);
8251 let inner = if looks_like_pair {
8252 let pairs = self.parse_hashref_pairs_until(&Token::RBrace)?;
8253 Expr {
8254 kind: ExprKind::HashRef(pairs),
8255 line,
8256 }
8257 } else {
8258 self.parse_expression()?
8259 };
8260 self.expect(&Token::RBrace)?;
8261 Ok(Expr {
8262 kind: ExprKind::Deref {
8263 expr: Box::new(inner),
8264 kind: Sigil::Hash,
8265 },
8266 line,
8267 })
8268 }
8269 Token::LBracket => {
8270 self.advance();
8271 let pairs = self.parse_hashref_pairs_until(&Token::RBracket)?;
8272 self.expect(&Token::RBracket)?;
8273 let href = Expr {
8274 kind: ExprKind::HashRef(pairs),
8275 line,
8276 };
8277 Ok(Expr {
8278 kind: ExprKind::Deref {
8279 expr: Box::new(href),
8280 kind: Sigil::Hash,
8281 },
8282 line,
8283 })
8284 }
8285 tok => Err(self.syntax_err(
8286 format!(
8287 "Expected identifier, `$`, `{{`, or `[` after `%`, got {:?}",
8288 tok
8289 ),
8290 line,
8291 )),
8292 }
8293 }
8294
8295 tok => Err(self.syntax_err(format!("Unexpected token {:?}", tok), line)),
8296 }
8297 }
8298
8299 fn parse_named_expr(&mut self, mut name: String) -> PerlResult<Expr> {
8300 let line = self.peek_line();
8301 self.advance(); while self.eat(&Token::PackageSep) {
8303 match self.advance() {
8304 (Token::Ident(part), _) => {
8305 name = format!("{}::{}", name, part);
8306 }
8307 (tok, err_line) => {
8308 return Err(self.syntax_err(
8309 format!("Expected identifier after `::`, got {:?}", tok),
8310 err_line,
8311 ));
8312 }
8313 }
8314 }
8315
8316 if matches!(self.peek(), Token::FatArrow) {
8320 return Ok(Expr {
8321 kind: ExprKind::String(name),
8322 line,
8323 });
8324 }
8325
8326 if crate::compat_mode() {
8327 if let Some(ext) = Self::stryke_extension_name(&name) {
8328 if !self.declared_subs.contains(&name) {
8329 return Err(self.syntax_err(
8330 format!("`{ext}` is a stryke extension (disabled by --compat)"),
8331 line,
8332 ));
8333 }
8334 }
8335 }
8336
8337 match name.as_str() {
8338 "__FILE__" => Ok(Expr {
8339 kind: ExprKind::MagicConst(MagicConstKind::File),
8340 line,
8341 }),
8342 "__LINE__" => Ok(Expr {
8343 kind: ExprKind::MagicConst(MagicConstKind::Line),
8344 line,
8345 }),
8346 "__SUB__" => Ok(Expr {
8347 kind: ExprKind::MagicConst(MagicConstKind::Sub),
8348 line,
8349 }),
8350 "stdin" => Ok(Expr {
8351 kind: ExprKind::FuncCall {
8352 name: "stdin".into(),
8353 args: vec![],
8354 },
8355 line,
8356 }),
8357 "range" => {
8358 let args = self.parse_builtin_args()?;
8359 Ok(Expr {
8360 kind: ExprKind::FuncCall {
8361 name: "range".into(),
8362 args,
8363 },
8364 line,
8365 })
8366 }
8367 "print" | "pr" => self.parse_print_like(|h, a| ExprKind::Print { handle: h, args: a }),
8368 "say" => {
8369 if crate::no_interop_mode() {
8370 return Err(
8371 self.syntax_err("stryke uses `p` instead of `say` (--no-interop)", line)
8372 );
8373 }
8374 self.parse_print_like(|h, a| ExprKind::Say { handle: h, args: a })
8375 }
8376 "p" => self.parse_print_like(|h, a| ExprKind::Say { handle: h, args: a }),
8377 "printf" => self.parse_print_like(|h, a| ExprKind::Printf { handle: h, args: a }),
8378 "die" => {
8379 let args = self.parse_list_until_terminator()?;
8380 Ok(Expr {
8381 kind: ExprKind::Die(args),
8382 line,
8383 })
8384 }
8385 "warn" => {
8386 let args = self.parse_list_until_terminator()?;
8387 Ok(Expr {
8388 kind: ExprKind::Warn(args),
8389 line,
8390 })
8391 }
8392 "croak" | "confess" => {
8397 let args = self.parse_list_until_terminator()?;
8398 Ok(Expr {
8399 kind: ExprKind::Die(args),
8400 line,
8401 })
8402 }
8403 "carp" | "cluck" => {
8405 let args = self.parse_list_until_terminator()?;
8406 Ok(Expr {
8407 kind: ExprKind::Warn(args),
8408 line,
8409 })
8410 }
8411 "chomp" => {
8412 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8413 return Ok(e);
8414 }
8415 let a = self.parse_one_arg_or_default()?;
8416 Ok(Expr {
8417 kind: ExprKind::Chomp(Box::new(a)),
8418 line,
8419 })
8420 }
8421 "chop" => {
8422 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8423 return Ok(e);
8424 }
8425 let a = self.parse_one_arg_or_default()?;
8426 Ok(Expr {
8427 kind: ExprKind::Chop(Box::new(a)),
8428 line,
8429 })
8430 }
8431 "length" => {
8432 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8433 return Ok(e);
8434 }
8435 let a = self.parse_one_arg_or_default()?;
8436 Ok(Expr {
8437 kind: ExprKind::Length(Box::new(a)),
8438 line,
8439 })
8440 }
8441 "defined" => {
8442 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8443 return Ok(e);
8444 }
8445 let a = self.parse_one_arg_or_default()?;
8446 Ok(Expr {
8447 kind: ExprKind::Defined(Box::new(a)),
8448 line,
8449 })
8450 }
8451 "ref" => {
8452 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8453 return Ok(e);
8454 }
8455 let a = self.parse_one_arg_or_default()?;
8456 Ok(Expr {
8457 kind: ExprKind::Ref(Box::new(a)),
8458 line,
8459 })
8460 }
8461 "undef" => {
8462 if self.peek_line() == self.prev_line()
8465 && matches!(
8466 self.peek(),
8467 Token::ScalarVar(_) | Token::ArrayVar(_) | Token::HashVar(_)
8468 )
8469 {
8470 let target = self.parse_primary()?;
8471 return Ok(Expr {
8472 kind: ExprKind::Assign {
8473 target: Box::new(target),
8474 value: Box::new(Expr {
8475 kind: ExprKind::Undef,
8476 line,
8477 }),
8478 },
8479 line,
8480 });
8481 }
8482 Ok(Expr {
8483 kind: ExprKind::Undef,
8484 line,
8485 })
8486 }
8487 "scalar" => {
8488 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8489 return Ok(e);
8490 }
8491 let a = self.parse_one_arg_or_default()?;
8492 Ok(Expr {
8493 kind: ExprKind::ScalarContext(Box::new(a)),
8494 line,
8495 })
8496 }
8497 "abs" => {
8498 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8499 return Ok(e);
8500 }
8501 let a = self.parse_one_arg_or_default()?;
8502 Ok(Expr {
8503 kind: ExprKind::Abs(Box::new(a)),
8504 line,
8505 })
8506 }
8507 "inc" | "dec" => {
8512 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8513 return Ok(e);
8514 }
8515 let a = self.parse_one_arg_or_default()?;
8516 Ok(Expr {
8517 kind: ExprKind::FuncCall {
8518 name,
8519 args: vec![a],
8520 },
8521 line,
8522 })
8523 }
8524 "int" => {
8525 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8526 return Ok(e);
8527 }
8528 let a = self.parse_one_arg_or_default()?;
8529 Ok(Expr {
8530 kind: ExprKind::Int(Box::new(a)),
8531 line,
8532 })
8533 }
8534 "sqrt" => {
8535 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8536 return Ok(e);
8537 }
8538 let a = self.parse_one_arg_or_default()?;
8539 Ok(Expr {
8540 kind: ExprKind::Sqrt(Box::new(a)),
8541 line,
8542 })
8543 }
8544 "sin" => {
8545 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8546 return Ok(e);
8547 }
8548 let a = self.parse_one_arg_or_default()?;
8549 Ok(Expr {
8550 kind: ExprKind::Sin(Box::new(a)),
8551 line,
8552 })
8553 }
8554 "cos" => {
8555 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8556 return Ok(e);
8557 }
8558 let a = self.parse_one_arg_or_default()?;
8559 Ok(Expr {
8560 kind: ExprKind::Cos(Box::new(a)),
8561 line,
8562 })
8563 }
8564 "atan2" => {
8565 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8566 return Ok(e);
8567 }
8568 let args = self.parse_builtin_args()?;
8569 if args.len() != 2 {
8570 return Err(self.syntax_err("atan2 requires two arguments", line));
8571 }
8572 Ok(Expr {
8573 kind: ExprKind::Atan2 {
8574 y: Box::new(args[0].clone()),
8575 x: Box::new(args[1].clone()),
8576 },
8577 line,
8578 })
8579 }
8580 "exp" => {
8581 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8582 return Ok(e);
8583 }
8584 let a = self.parse_one_arg_or_default()?;
8585 Ok(Expr {
8586 kind: ExprKind::Exp(Box::new(a)),
8587 line,
8588 })
8589 }
8590 "log" => {
8591 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8592 return Ok(e);
8593 }
8594 let a = self.parse_one_arg_or_default()?;
8595 Ok(Expr {
8596 kind: ExprKind::Log(Box::new(a)),
8597 line,
8598 })
8599 }
8600 "input" => {
8601 let args = if matches!(
8602 self.peek(),
8603 Token::Semicolon
8604 | Token::RBrace
8605 | Token::RParen
8606 | Token::Eof
8607 | Token::Comma
8608 | Token::PipeForward
8609 ) {
8610 vec![]
8611 } else if matches!(self.peek(), Token::LParen) {
8612 self.advance();
8613 if matches!(self.peek(), Token::RParen) {
8614 self.advance();
8615 vec![]
8616 } else {
8617 let a = self.parse_expression()?;
8618 self.expect(&Token::RParen)?;
8619 vec![a]
8620 }
8621 } else {
8622 let a = self.parse_one_arg()?;
8623 vec![a]
8624 };
8625 Ok(Expr {
8626 kind: ExprKind::FuncCall {
8627 name: "input".to_string(),
8628 args,
8629 },
8630 line,
8631 })
8632 }
8633 "rand" => {
8634 if matches!(
8635 self.peek(),
8636 Token::Semicolon
8637 | Token::RBrace
8638 | Token::RParen
8639 | Token::Eof
8640 | Token::Comma
8641 | Token::PipeForward
8642 ) {
8643 Ok(Expr {
8644 kind: ExprKind::Rand(None),
8645 line,
8646 })
8647 } else if matches!(self.peek(), Token::LParen) {
8648 self.advance();
8649 if matches!(self.peek(), Token::RParen) {
8650 self.advance();
8651 Ok(Expr {
8652 kind: ExprKind::Rand(None),
8653 line,
8654 })
8655 } else {
8656 let a = self.parse_expression()?;
8657 self.expect(&Token::RParen)?;
8658 Ok(Expr {
8659 kind: ExprKind::Rand(Some(Box::new(a))),
8660 line,
8661 })
8662 }
8663 } else {
8664 let a = self.parse_one_arg()?;
8665 Ok(Expr {
8666 kind: ExprKind::Rand(Some(Box::new(a))),
8667 line,
8668 })
8669 }
8670 }
8671 "srand" => {
8672 if matches!(
8673 self.peek(),
8674 Token::Semicolon
8675 | Token::RBrace
8676 | Token::RParen
8677 | Token::Eof
8678 | Token::Comma
8679 | Token::PipeForward
8680 ) {
8681 Ok(Expr {
8682 kind: ExprKind::Srand(None),
8683 line,
8684 })
8685 } else if matches!(self.peek(), Token::LParen) {
8686 self.advance();
8687 if matches!(self.peek(), Token::RParen) {
8688 self.advance();
8689 Ok(Expr {
8690 kind: ExprKind::Srand(None),
8691 line,
8692 })
8693 } else {
8694 let a = self.parse_expression()?;
8695 self.expect(&Token::RParen)?;
8696 Ok(Expr {
8697 kind: ExprKind::Srand(Some(Box::new(a))),
8698 line,
8699 })
8700 }
8701 } else {
8702 let a = self.parse_one_arg()?;
8703 Ok(Expr {
8704 kind: ExprKind::Srand(Some(Box::new(a))),
8705 line,
8706 })
8707 }
8708 }
8709 "hex" => {
8710 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8711 return Ok(e);
8712 }
8713 let a = self.parse_one_arg_or_default()?;
8714 Ok(Expr {
8715 kind: ExprKind::Hex(Box::new(a)),
8716 line,
8717 })
8718 }
8719 "oct" => {
8720 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8721 return Ok(e);
8722 }
8723 let a = self.parse_one_arg_or_default()?;
8724 Ok(Expr {
8725 kind: ExprKind::Oct(Box::new(a)),
8726 line,
8727 })
8728 }
8729 "chr" => {
8730 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8731 return Ok(e);
8732 }
8733 let a = self.parse_one_arg_or_default()?;
8734 Ok(Expr {
8735 kind: ExprKind::Chr(Box::new(a)),
8736 line,
8737 })
8738 }
8739 "ord" => {
8740 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8741 return Ok(e);
8742 }
8743 let a = self.parse_one_arg_or_default()?;
8744 Ok(Expr {
8745 kind: ExprKind::Ord(Box::new(a)),
8746 line,
8747 })
8748 }
8749 "lc" => {
8750 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8751 return Ok(e);
8752 }
8753 let a = self.parse_one_arg_or_default()?;
8754 Ok(Expr {
8755 kind: ExprKind::Lc(Box::new(a)),
8756 line,
8757 })
8758 }
8759 "uc" => {
8760 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8761 return Ok(e);
8762 }
8763 let a = self.parse_one_arg_or_default()?;
8764 Ok(Expr {
8765 kind: ExprKind::Uc(Box::new(a)),
8766 line,
8767 })
8768 }
8769 "lcfirst" => {
8770 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8771 return Ok(e);
8772 }
8773 let a = self.parse_one_arg_or_default()?;
8774 Ok(Expr {
8775 kind: ExprKind::Lcfirst(Box::new(a)),
8776 line,
8777 })
8778 }
8779 "ucfirst" => {
8780 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8781 return Ok(e);
8782 }
8783 let a = self.parse_one_arg_or_default()?;
8784 Ok(Expr {
8785 kind: ExprKind::Ucfirst(Box::new(a)),
8786 line,
8787 })
8788 }
8789 "fc" => {
8790 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8791 return Ok(e);
8792 }
8793 let a = self.parse_one_arg_or_default()?;
8794 Ok(Expr {
8795 kind: ExprKind::Fc(Box::new(a)),
8796 line,
8797 })
8798 }
8799 "crypt" => {
8800 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8801 return Ok(e);
8802 }
8803 let args = self.parse_builtin_args()?;
8804 if args.len() != 2 {
8805 return Err(self.syntax_err("crypt requires two arguments", line));
8806 }
8807 Ok(Expr {
8808 kind: ExprKind::Crypt {
8809 plaintext: Box::new(args[0].clone()),
8810 salt: Box::new(args[1].clone()),
8811 },
8812 line,
8813 })
8814 }
8815 "pos" => {
8816 if matches!(
8817 self.peek(),
8818 Token::Semicolon
8819 | Token::RBrace
8820 | Token::RParen
8821 | Token::Eof
8822 | Token::Comma
8823 | Token::PipeForward
8824 ) {
8825 Ok(Expr {
8826 kind: ExprKind::Pos(None),
8827 line,
8828 })
8829 } else if matches!(self.peek(), Token::Assign) {
8830 self.advance();
8832 let rhs = self.parse_assign_expr()?;
8833 Ok(Expr {
8834 kind: ExprKind::Assign {
8835 target: Box::new(Expr {
8836 kind: ExprKind::Pos(Some(Box::new(Expr {
8837 kind: ExprKind::ScalarVar("_".into()),
8838 line,
8839 }))),
8840 line,
8841 }),
8842 value: Box::new(rhs),
8843 },
8844 line,
8845 })
8846 } else if matches!(self.peek(), Token::LParen) {
8847 self.advance();
8848 if matches!(self.peek(), Token::RParen) {
8849 self.advance();
8850 Ok(Expr {
8851 kind: ExprKind::Pos(None),
8852 line,
8853 })
8854 } else {
8855 let a = self.parse_expression()?;
8856 self.expect(&Token::RParen)?;
8857 Ok(Expr {
8858 kind: ExprKind::Pos(Some(Box::new(a))),
8859 line,
8860 })
8861 }
8862 } else {
8863 let saved = self.pos;
8864 let subj = self.parse_unary()?;
8865 if matches!(self.peek(), Token::Assign) {
8866 self.advance();
8867 let rhs = self.parse_assign_expr()?;
8868 Ok(Expr {
8869 kind: ExprKind::Assign {
8870 target: Box::new(Expr {
8871 kind: ExprKind::Pos(Some(Box::new(subj))),
8872 line,
8873 }),
8874 value: Box::new(rhs),
8875 },
8876 line,
8877 })
8878 } else {
8879 self.pos = saved;
8880 let a = self.parse_one_arg()?;
8881 Ok(Expr {
8882 kind: ExprKind::Pos(Some(Box::new(a))),
8883 line,
8884 })
8885 }
8886 }
8887 }
8888 "study" => {
8889 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8890 return Ok(e);
8891 }
8892 let a = self.parse_one_arg_or_default()?;
8893 Ok(Expr {
8894 kind: ExprKind::Study(Box::new(a)),
8895 line,
8896 })
8897 }
8898 "push" => {
8899 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8900 return Ok(e);
8901 }
8902 let args = self.parse_builtin_args()?;
8903 let (first, rest) = args
8904 .split_first()
8905 .ok_or_else(|| self.syntax_err("push requires arguments", line))?;
8906 Ok(Expr {
8907 kind: ExprKind::Push {
8908 array: Box::new(first.clone()),
8909 values: rest.to_vec(),
8910 },
8911 line,
8912 })
8913 }
8914 "pop" => {
8915 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8916 return Ok(e);
8917 }
8918 let a = self.parse_one_arg_or_argv()?;
8919 Ok(Expr {
8920 kind: ExprKind::Pop(Box::new(a)),
8921 line,
8922 })
8923 }
8924 "shift" => {
8925 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8926 return Ok(e);
8927 }
8928 let a = self.parse_one_arg_or_argv()?;
8929 Ok(Expr {
8930 kind: ExprKind::Shift(Box::new(a)),
8931 line,
8932 })
8933 }
8934 "unshift" => {
8935 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8936 return Ok(e);
8937 }
8938 let args = self.parse_builtin_args()?;
8939 let (first, rest) = args
8940 .split_first()
8941 .ok_or_else(|| self.syntax_err("unshift requires arguments", line))?;
8942 Ok(Expr {
8943 kind: ExprKind::Unshift {
8944 array: Box::new(first.clone()),
8945 values: rest.to_vec(),
8946 },
8947 line,
8948 })
8949 }
8950 "splice" => {
8951 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8952 return Ok(e);
8953 }
8954 let args = self.parse_builtin_args()?;
8955 let mut iter = args.into_iter();
8956 let array = Box::new(
8957 iter.next()
8958 .ok_or_else(|| self.syntax_err("splice requires arguments", line))?,
8959 );
8960 let offset = iter.next().map(Box::new);
8961 let length = iter.next().map(Box::new);
8962 let replacement: Vec<Expr> = iter.collect();
8963 Ok(Expr {
8964 kind: ExprKind::Splice {
8965 array,
8966 offset,
8967 length,
8968 replacement,
8969 },
8970 line,
8971 })
8972 }
8973 "delete" => {
8974 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8975 return Ok(e);
8976 }
8977 let a = self.parse_postfix()?;
8978 Ok(Expr {
8979 kind: ExprKind::Delete(Box::new(a)),
8980 line,
8981 })
8982 }
8983 "exists" => {
8984 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8985 return Ok(e);
8986 }
8987 let a = self.parse_postfix()?;
8988 Ok(Expr {
8989 kind: ExprKind::Exists(Box::new(a)),
8990 line,
8991 })
8992 }
8993 "keys" => {
8994 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
8995 return Ok(e);
8996 }
8997 let a = self.parse_one_arg_or_default()?;
8998 Ok(Expr {
8999 kind: ExprKind::Keys(Box::new(a)),
9000 line,
9001 })
9002 }
9003 "values" => {
9004 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9005 return Ok(e);
9006 }
9007 let a = self.parse_one_arg_or_default()?;
9008 Ok(Expr {
9009 kind: ExprKind::Values(Box::new(a)),
9010 line,
9011 })
9012 }
9013 "each" => {
9014 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9015 return Ok(e);
9016 }
9017 let a = self.parse_one_arg_or_default()?;
9018 Ok(Expr {
9019 kind: ExprKind::Each(Box::new(a)),
9020 line,
9021 })
9022 }
9023 "fore" | "e" | "ep" => {
9024 if matches!(self.peek(), Token::LBrace) {
9026 let (block, list) = self.parse_block_list()?;
9027 Ok(Expr {
9028 kind: ExprKind::ForEachExpr {
9029 block,
9030 list: Box::new(list),
9031 },
9032 line,
9033 })
9034 } else if self.in_pipe_rhs() {
9035 let is_terminal = matches!(
9038 self.peek(),
9039 Token::Semicolon
9040 | Token::RParen
9041 | Token::Eof
9042 | Token::PipeForward
9043 | Token::RBrace
9044 );
9045 let block = if name == "ep" && is_terminal {
9046 vec![Statement {
9047 label: None,
9048 kind: StmtKind::Expression(Expr {
9049 kind: ExprKind::Say {
9050 handle: None,
9051 args: vec![Expr {
9052 kind: ExprKind::ScalarVar("_".into()),
9053 line,
9054 }],
9055 },
9056 line,
9057 }),
9058 line,
9059 }]
9060 } else {
9061 let expr = self.parse_assign_expr_stop_at_pipe()?;
9062 let expr = Self::lift_bareword_to_topic_call(expr);
9063 vec![Statement {
9064 label: None,
9065 kind: StmtKind::Expression(expr),
9066 line,
9067 }]
9068 };
9069 let list = self.pipe_placeholder_list(line);
9070 Ok(Expr {
9071 kind: ExprKind::ForEachExpr {
9072 block,
9073 list: Box::new(list),
9074 },
9075 line,
9076 })
9077 } else {
9078 let expr = self.parse_assign_expr()?;
9080 let expr = Self::lift_bareword_to_topic_call(expr);
9081 self.expect(&Token::Comma)?;
9082 let list_parts = self.parse_list_until_terminator()?;
9083 let list_expr = if list_parts.len() == 1 {
9084 list_parts.into_iter().next().unwrap()
9085 } else {
9086 Expr {
9087 kind: ExprKind::List(list_parts),
9088 line,
9089 }
9090 };
9091 let block = vec![Statement {
9092 label: None,
9093 kind: StmtKind::Expression(expr),
9094 line,
9095 }];
9096 Ok(Expr {
9097 kind: ExprKind::ForEachExpr {
9098 block,
9099 list: Box::new(list_expr),
9100 },
9101 line,
9102 })
9103 }
9104 }
9105 "rev" => {
9106 let a = if self.in_pipe_rhs()
9111 && matches!(
9112 self.peek(),
9113 Token::Semicolon | Token::RParen | Token::Eof | Token::PipeForward
9114 ) {
9115 self.pipe_placeholder_list(line)
9116 } else {
9117 self.parse_one_arg_or_default()?
9118 };
9119 Ok(Expr {
9120 kind: ExprKind::Rev(Box::new(a)),
9121 line,
9122 })
9123 }
9124 "reverse" => {
9125 if crate::no_interop_mode() {
9126 return Err(self.syntax_err(
9127 "stryke uses `rev` instead of `reverse` (--no-interop)",
9128 line,
9129 ));
9130 }
9131 let a = if self.in_pipe_rhs()
9133 && matches!(
9134 self.peek(),
9135 Token::Semicolon
9136 | Token::RBrace
9137 | Token::RParen
9138 | Token::Eof
9139 | Token::PipeForward
9140 ) {
9141 self.pipe_placeholder_list(line)
9142 } else {
9143 self.parse_one_arg()?
9144 };
9145 Ok(Expr {
9146 kind: ExprKind::ReverseExpr(Box::new(a)),
9147 line,
9148 })
9149 }
9150 "reversed" | "rv" => {
9151 let a = if self.in_pipe_rhs()
9153 && matches!(
9154 self.peek(),
9155 Token::Semicolon
9156 | Token::RBrace
9157 | Token::RParen
9158 | Token::Eof
9159 | Token::PipeForward
9160 ) {
9161 self.pipe_placeholder_list(line)
9162 } else {
9163 self.parse_one_arg()?
9164 };
9165 Ok(Expr {
9166 kind: ExprKind::Rev(Box::new(a)),
9167 line,
9168 })
9169 }
9170 "join" => {
9171 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9172 return Ok(e);
9173 }
9174 let args = self.parse_builtin_args()?;
9175 if args.is_empty() {
9176 return Err(self.syntax_err("join requires separator and list", line));
9177 }
9178 if args.len() < 2 && !self.in_pipe_rhs() {
9180 return Err(self.syntax_err("join requires separator and list", line));
9181 }
9182 Ok(Expr {
9183 kind: ExprKind::JoinExpr {
9184 separator: Box::new(args[0].clone()),
9185 list: Box::new(Expr {
9186 kind: ExprKind::List(args[1..].to_vec()),
9187 line,
9188 }),
9189 },
9190 line,
9191 })
9192 }
9193 "split" => {
9194 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9195 return Ok(e);
9196 }
9197 let args = self.parse_builtin_args()?;
9198 let pattern = args.first().cloned().unwrap_or(Expr {
9199 kind: ExprKind::String(" ".into()),
9200 line,
9201 });
9202 let string = args.get(1).cloned().unwrap_or(Expr {
9203 kind: ExprKind::ScalarVar("_".into()),
9204 line,
9205 });
9206 let limit = args.get(2).cloned().map(Box::new);
9207 Ok(Expr {
9208 kind: ExprKind::SplitExpr {
9209 pattern: Box::new(pattern),
9210 string: Box::new(string),
9211 limit,
9212 },
9213 line,
9214 })
9215 }
9216 "substr" => {
9217 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9218 return Ok(e);
9219 }
9220 let args = self.parse_builtin_args()?;
9221 Ok(Expr {
9222 kind: ExprKind::Substr {
9223 string: Box::new(args[0].clone()),
9224 offset: Box::new(args[1].clone()),
9225 length: args.get(2).cloned().map(Box::new),
9226 replacement: args.get(3).cloned().map(Box::new),
9227 },
9228 line,
9229 })
9230 }
9231 "index" => {
9232 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9233 return Ok(e);
9234 }
9235 let args = self.parse_builtin_args()?;
9236 Ok(Expr {
9237 kind: ExprKind::Index {
9238 string: Box::new(args[0].clone()),
9239 substr: Box::new(args[1].clone()),
9240 position: args.get(2).cloned().map(Box::new),
9241 },
9242 line,
9243 })
9244 }
9245 "rindex" => {
9246 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9247 return Ok(e);
9248 }
9249 let args = self.parse_builtin_args()?;
9250 Ok(Expr {
9251 kind: ExprKind::Rindex {
9252 string: Box::new(args[0].clone()),
9253 substr: Box::new(args[1].clone()),
9254 position: args.get(2).cloned().map(Box::new),
9255 },
9256 line,
9257 })
9258 }
9259 "sprintf" => {
9260 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9261 return Ok(e);
9262 }
9263 let args = self.parse_builtin_args()?;
9264 let (first, rest) = args
9265 .split_first()
9266 .ok_or_else(|| self.syntax_err("sprintf requires format", line))?;
9267 Ok(Expr {
9268 kind: ExprKind::Sprintf {
9269 format: Box::new(first.clone()),
9270 args: rest.to_vec(),
9271 },
9272 line,
9273 })
9274 }
9275 "map" | "flat_map" | "maps" | "flat_maps" => {
9276 let flatten_array_refs = matches!(name.as_str(), "flat_map" | "flat_maps");
9277 let stream = matches!(name.as_str(), "maps" | "flat_maps");
9278 if matches!(self.peek(), Token::LBrace) {
9279 let (block, list) = self.parse_block_list()?;
9280 Ok(Expr {
9281 kind: ExprKind::MapExpr {
9282 block,
9283 list: Box::new(list),
9284 flatten_array_refs,
9285 stream,
9286 },
9287 line,
9288 })
9289 } else {
9290 let expr = self.parse_assign_expr_stop_at_pipe()?;
9291 let expr = Self::lift_bareword_to_topic_call(expr);
9294 let list_expr = if self.in_pipe_rhs()
9295 && matches!(
9296 self.peek(),
9297 Token::Semicolon
9298 | Token::RBrace
9299 | Token::RParen
9300 | Token::Eof
9301 | Token::PipeForward
9302 ) {
9303 self.pipe_placeholder_list(line)
9304 } else {
9305 self.expect(&Token::Comma)?;
9306 let list_parts = self.parse_list_until_terminator()?;
9307 if list_parts.len() == 1 {
9308 list_parts.into_iter().next().unwrap()
9309 } else {
9310 Expr {
9311 kind: ExprKind::List(list_parts),
9312 line,
9313 }
9314 }
9315 };
9316 Ok(Expr {
9317 kind: ExprKind::MapExprComma {
9318 expr: Box::new(expr),
9319 list: Box::new(list_expr),
9320 flatten_array_refs,
9321 stream,
9322 },
9323 line,
9324 })
9325 }
9326 }
9327 "cond" => {
9328 if crate::compat_mode() {
9329 return Err(self
9330 .syntax_err("`cond` is a stryke extension (disabled by --compat)", line));
9331 }
9332 self.parse_cond_expr(line)
9333 }
9334 "match" => {
9335 if crate::compat_mode() {
9336 return Err(self.syntax_err(
9337 "algebraic `match` is a stryke extension (disabled by --compat)",
9338 line,
9339 ));
9340 }
9341 self.parse_algebraic_match_expr(line)
9342 }
9343 "grep" | "greps" | "filter" | "fi" | "find_all" => {
9344 let keyword = match name.as_str() {
9345 "grep" => crate::ast::GrepBuiltinKeyword::Grep,
9346 "greps" => crate::ast::GrepBuiltinKeyword::Greps,
9347 "filter" | "fi" => crate::ast::GrepBuiltinKeyword::Filter,
9348 "find_all" => crate::ast::GrepBuiltinKeyword::FindAll,
9349 _ => unreachable!(),
9350 };
9351 if matches!(self.peek(), Token::LBrace) {
9352 let (block, list) = self.parse_block_list()?;
9353 Ok(Expr {
9354 kind: ExprKind::GrepExpr {
9355 block,
9356 list: Box::new(list),
9357 keyword,
9358 },
9359 line,
9360 })
9361 } else {
9362 let expr = self.parse_assign_expr_stop_at_pipe()?;
9363 if self.in_pipe_rhs()
9364 && matches!(
9365 self.peek(),
9366 Token::Semicolon
9367 | Token::RBrace
9368 | Token::RParen
9369 | Token::Eof
9370 | Token::PipeForward
9371 )
9372 {
9373 let list = self.pipe_placeholder_list(line);
9378 let topic = Expr {
9379 kind: ExprKind::ScalarVar("_".into()),
9380 line,
9381 };
9382 let test = match &expr.kind {
9383 ExprKind::Integer(_) | ExprKind::Float(_) => Expr {
9384 kind: ExprKind::BinOp {
9385 op: BinOp::NumEq,
9386 left: Box::new(topic),
9387 right: Box::new(expr),
9388 },
9389 line,
9390 },
9391 ExprKind::String(_) | ExprKind::InterpolatedString(_) => Expr {
9392 kind: ExprKind::BinOp {
9393 op: BinOp::StrEq,
9394 left: Box::new(topic),
9395 right: Box::new(expr),
9396 },
9397 line,
9398 },
9399 ExprKind::Regex { .. } => Expr {
9400 kind: ExprKind::BinOp {
9401 op: BinOp::BindMatch,
9402 left: Box::new(topic),
9403 right: Box::new(expr),
9404 },
9405 line,
9406 },
9407 _ => {
9408 Self::lift_bareword_to_topic_call(expr)
9410 }
9411 };
9412 let block = vec![Statement {
9413 label: None,
9414 kind: StmtKind::Expression(test),
9415 line,
9416 }];
9417 Ok(Expr {
9418 kind: ExprKind::GrepExpr {
9419 block,
9420 list: Box::new(list),
9421 keyword,
9422 },
9423 line,
9424 })
9425 } else {
9426 let expr = Self::lift_bareword_to_topic_call(expr);
9427 self.expect(&Token::Comma)?;
9428 let list_parts = self.parse_list_until_terminator()?;
9429 let list_expr = if list_parts.len() == 1 {
9430 list_parts.into_iter().next().unwrap()
9431 } else {
9432 Expr {
9433 kind: ExprKind::List(list_parts),
9434 line,
9435 }
9436 };
9437 Ok(Expr {
9438 kind: ExprKind::GrepExprComma {
9439 expr: Box::new(expr),
9440 list: Box::new(list_expr),
9441 keyword,
9442 },
9443 line,
9444 })
9445 }
9446 }
9447 }
9448 "sort" => {
9449 use crate::ast::SortComparator;
9450 if matches!(self.peek(), Token::LBrace) {
9451 let block = self.parse_block()?;
9452 let _ = self.eat(&Token::Comma);
9453 let list = if self.in_pipe_rhs()
9454 && matches!(
9455 self.peek(),
9456 Token::Semicolon
9457 | Token::RBrace
9458 | Token::RParen
9459 | Token::Eof
9460 | Token::PipeForward
9461 ) {
9462 self.pipe_placeholder_list(line)
9463 } else {
9464 self.parse_expression()?
9465 };
9466 Ok(Expr {
9467 kind: ExprKind::SortExpr {
9468 cmp: Some(SortComparator::Block(block)),
9469 list: Box::new(list),
9470 },
9471 line,
9472 })
9473 } else if matches!(self.peek(), Token::ScalarVar(ref v) if v == "a" || v == "b") {
9474 let block = self.parse_block_or_bareword_cmp_block()?;
9476 let _ = self.eat(&Token::Comma);
9477 let list = if self.in_pipe_rhs()
9478 && matches!(
9479 self.peek(),
9480 Token::Semicolon
9481 | Token::RBrace
9482 | Token::RParen
9483 | Token::Eof
9484 | Token::PipeForward
9485 ) {
9486 self.pipe_placeholder_list(line)
9487 } else {
9488 self.parse_expression()?
9489 };
9490 Ok(Expr {
9491 kind: ExprKind::SortExpr {
9492 cmp: Some(SortComparator::Block(block)),
9493 list: Box::new(list),
9494 },
9495 line,
9496 })
9497 } else if matches!(self.peek(), Token::ScalarVar(_)) {
9498 self.suppress_indirect_paren_call =
9500 self.suppress_indirect_paren_call.saturating_add(1);
9501 let code = self.parse_assign_expr()?;
9502 self.suppress_indirect_paren_call =
9503 self.suppress_indirect_paren_call.saturating_sub(1);
9504 let list = if matches!(self.peek(), Token::LParen) {
9505 self.advance();
9506 let e = self.parse_expression()?;
9507 self.expect(&Token::RParen)?;
9508 e
9509 } else {
9510 self.parse_expression()?
9511 };
9512 Ok(Expr {
9513 kind: ExprKind::SortExpr {
9514 cmp: Some(SortComparator::Code(Box::new(code))),
9515 list: Box::new(list),
9516 },
9517 line,
9518 })
9519 } else if matches!(self.peek(), Token::Ident(ref name) if !Self::is_known_bareword(name))
9520 {
9521 let block = self.parse_block_or_bareword_cmp_block()?;
9523 let _ = self.eat(&Token::Comma);
9524 let list = if self.in_pipe_rhs()
9525 && matches!(
9526 self.peek(),
9527 Token::Semicolon
9528 | Token::RBrace
9529 | Token::RParen
9530 | Token::Eof
9531 | Token::PipeForward
9532 ) {
9533 self.pipe_placeholder_list(line)
9534 } else {
9535 self.parse_expression()?
9536 };
9537 Ok(Expr {
9538 kind: ExprKind::SortExpr {
9539 cmp: Some(SortComparator::Block(block)),
9540 list: Box::new(list),
9541 },
9542 line,
9543 })
9544 } else {
9545 let list = if self.in_pipe_rhs()
9548 && matches!(
9549 self.peek(),
9550 Token::Semicolon
9551 | Token::RBrace
9552 | Token::RParen
9553 | Token::Eof
9554 | Token::PipeForward
9555 ) {
9556 self.pipe_placeholder_list(line)
9557 } else {
9558 self.parse_expression()?
9559 };
9560 Ok(Expr {
9561 kind: ExprKind::SortExpr {
9562 cmp: None,
9563 list: Box::new(list),
9564 },
9565 line,
9566 })
9567 }
9568 }
9569 "reduce" | "fold" | "inject" => {
9570 let (block, list) = self.parse_block_list()?;
9571 Ok(Expr {
9572 kind: ExprKind::ReduceExpr {
9573 block,
9574 list: Box::new(list),
9575 },
9576 line,
9577 })
9578 }
9579 "pmap" => {
9581 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
9582 Ok(Expr {
9583 kind: ExprKind::PMapExpr {
9584 block,
9585 list: Box::new(list),
9586 progress: progress.map(Box::new),
9587 flat_outputs: false,
9588 on_cluster: None,
9589 stream: false,
9590 },
9591 line,
9592 })
9593 }
9594 "pmap_on" => {
9595 let (cluster, block, list, progress) =
9596 self.parse_cluster_block_then_list_optional_progress()?;
9597 Ok(Expr {
9598 kind: ExprKind::PMapExpr {
9599 block,
9600 list: Box::new(list),
9601 progress: progress.map(Box::new),
9602 flat_outputs: false,
9603 on_cluster: Some(Box::new(cluster)),
9604 stream: false,
9605 },
9606 line,
9607 })
9608 }
9609 "pflat_map" => {
9610 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
9611 Ok(Expr {
9612 kind: ExprKind::PMapExpr {
9613 block,
9614 list: Box::new(list),
9615 progress: progress.map(Box::new),
9616 flat_outputs: true,
9617 on_cluster: None,
9618 stream: false,
9619 },
9620 line,
9621 })
9622 }
9623 "pflat_map_on" => {
9624 let (cluster, block, list, progress) =
9625 self.parse_cluster_block_then_list_optional_progress()?;
9626 Ok(Expr {
9627 kind: ExprKind::PMapExpr {
9628 block,
9629 list: Box::new(list),
9630 progress: progress.map(Box::new),
9631 flat_outputs: true,
9632 on_cluster: Some(Box::new(cluster)),
9633 stream: false,
9634 },
9635 line,
9636 })
9637 }
9638 "pmaps" => {
9639 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
9640 Ok(Expr {
9641 kind: ExprKind::PMapExpr {
9642 block,
9643 list: Box::new(list),
9644 progress: progress.map(Box::new),
9645 flat_outputs: false,
9646 on_cluster: None,
9647 stream: true,
9648 },
9649 line,
9650 })
9651 }
9652 "pflat_maps" => {
9653 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
9654 Ok(Expr {
9655 kind: ExprKind::PMapExpr {
9656 block,
9657 list: Box::new(list),
9658 progress: progress.map(Box::new),
9659 flat_outputs: true,
9660 on_cluster: None,
9661 stream: true,
9662 },
9663 line,
9664 })
9665 }
9666 "pgreps" => {
9667 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
9668 Ok(Expr {
9669 kind: ExprKind::PGrepExpr {
9670 block,
9671 list: Box::new(list),
9672 progress: progress.map(Box::new),
9673 stream: true,
9674 },
9675 line,
9676 })
9677 }
9678 "pmap_chunked" => {
9679 let chunk_size = self.parse_assign_expr()?;
9680 let block = self.parse_block_or_bareword_block()?;
9681 self.eat(&Token::Comma);
9682 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
9683 Ok(Expr {
9684 kind: ExprKind::PMapChunkedExpr {
9685 chunk_size: Box::new(chunk_size),
9686 block,
9687 list: Box::new(list),
9688 progress: progress.map(Box::new),
9689 },
9690 line,
9691 })
9692 }
9693 "pgrep" => {
9694 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
9695 Ok(Expr {
9696 kind: ExprKind::PGrepExpr {
9697 block,
9698 list: Box::new(list),
9699 progress: progress.map(Box::new),
9700 stream: false,
9701 },
9702 line,
9703 })
9704 }
9705 "pfor" => {
9706 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
9707 Ok(Expr {
9708 kind: ExprKind::PForExpr {
9709 block,
9710 list: Box::new(list),
9711 progress: progress.map(Box::new),
9712 },
9713 line,
9714 })
9715 }
9716 "par_lines" | "par_walk" => {
9717 let args = self.parse_builtin_args()?;
9718 if args.len() < 2 {
9719 return Err(
9720 self.syntax_err(format!("{} requires at least two arguments", name), line)
9721 );
9722 }
9723
9724 if name == "par_lines" {
9725 Ok(Expr {
9726 kind: ExprKind::ParLinesExpr {
9727 path: Box::new(args[0].clone()),
9728 callback: Box::new(args[1].clone()),
9729 progress: None,
9730 },
9731 line,
9732 })
9733 } else {
9734 Ok(Expr {
9735 kind: ExprKind::ParWalkExpr {
9736 path: Box::new(args[0].clone()),
9737 callback: Box::new(args[1].clone()),
9738 progress: None,
9739 },
9740 line,
9741 })
9742 }
9743 }
9744 "pwatch" | "watch" => {
9745 let args = self.parse_builtin_args()?;
9746 if args.len() < 2 {
9747 return Err(
9748 self.syntax_err(format!("{} requires at least two arguments", name), line)
9749 );
9750 }
9751 Ok(Expr {
9752 kind: ExprKind::PwatchExpr {
9753 path: Box::new(args[0].clone()),
9754 callback: Box::new(args[1].clone()),
9755 },
9756 line,
9757 })
9758 }
9759 "fan" => {
9760 let (count, block) = self.parse_fan_count_and_block(line)?;
9766 let progress = self.parse_fan_optional_progress("fan")?;
9767 Ok(Expr {
9768 kind: ExprKind::FanExpr {
9769 count,
9770 block,
9771 progress,
9772 capture: false,
9773 },
9774 line,
9775 })
9776 }
9777 "fan_cap" => {
9778 let (count, block) = self.parse_fan_count_and_block(line)?;
9779 let progress = self.parse_fan_optional_progress("fan_cap")?;
9780 Ok(Expr {
9781 kind: ExprKind::FanExpr {
9782 count,
9783 block,
9784 progress,
9785 capture: true,
9786 },
9787 line,
9788 })
9789 }
9790 "async" => {
9791 if !matches!(self.peek(), Token::LBrace) {
9792 return Err(self.syntax_err("async must be followed by { BLOCK }", line));
9793 }
9794 let block = self.parse_block()?;
9795 Ok(Expr {
9796 kind: ExprKind::AsyncBlock { body: block },
9797 line,
9798 })
9799 }
9800 "spawn" => {
9801 if !matches!(self.peek(), Token::LBrace) {
9802 return Err(self.syntax_err("spawn must be followed by { BLOCK }", line));
9803 }
9804 let block = self.parse_block()?;
9805 Ok(Expr {
9806 kind: ExprKind::SpawnBlock { body: block },
9807 line,
9808 })
9809 }
9810 "trace" => {
9811 if !matches!(self.peek(), Token::LBrace) {
9812 return Err(self.syntax_err("trace must be followed by { BLOCK }", line));
9813 }
9814 let block = self.parse_block()?;
9815 Ok(Expr {
9816 kind: ExprKind::Trace { body: block },
9817 line,
9818 })
9819 }
9820 "timer" => {
9821 let block = self.parse_block_or_bareword_block_no_args()?;
9822 Ok(Expr {
9823 kind: ExprKind::Timer { body: block },
9824 line,
9825 })
9826 }
9827 "bench" => {
9828 let block = self.parse_block_or_bareword_block_no_args()?;
9829 let times = Box::new(self.parse_expression()?);
9830 Ok(Expr {
9831 kind: ExprKind::Bench { body: block, times },
9832 line,
9833 })
9834 }
9835 "spinner" => {
9836 let (message, body) = if matches!(self.peek(), Token::LBrace) {
9838 let body = self.parse_block()?;
9839 (
9840 Box::new(Expr {
9841 kind: ExprKind::String("working".to_string()),
9842 line,
9843 }),
9844 body,
9845 )
9846 } else {
9847 let msg = self.parse_assign_expr()?;
9848 let body = self.parse_block()?;
9849 (Box::new(msg), body)
9850 };
9851 Ok(Expr {
9852 kind: ExprKind::Spinner { message, body },
9853 line,
9854 })
9855 }
9856 "thread" | "t" => {
9857 self.parse_thread_macro(line, false)
9867 }
9868 "retry" => {
9869 let body = if matches!(self.peek(), Token::LBrace) {
9872 self.parse_block()?
9873 } else {
9874 let bw_line = self.peek_line();
9875 let Token::Ident(ref name) = self.peek().clone() else {
9876 return Err(self
9877 .syntax_err("retry: expected block or bareword function name", line));
9878 };
9879 let name = name.clone();
9880 self.advance();
9881 vec![Statement::new(
9882 StmtKind::Expression(Expr {
9883 kind: ExprKind::FuncCall { name, args: vec![] },
9884 line: bw_line,
9885 }),
9886 bw_line,
9887 )]
9888 };
9889 self.eat(&Token::Comma);
9890 match self.peek() {
9891 Token::Ident(ref s) if s == "times" => {
9892 self.advance();
9893 }
9894 _ => {
9895 return Err(self.syntax_err("retry: expected `times =>` after block", line));
9896 }
9897 }
9898 self.expect(&Token::FatArrow)?;
9899 let times = Box::new(self.parse_assign_expr()?);
9900 let mut backoff = RetryBackoff::None;
9901 if self.eat(&Token::Comma) {
9902 match self.peek() {
9903 Token::Ident(ref s) if s == "backoff" => {
9904 self.advance();
9905 }
9906 _ => {
9907 return Err(
9908 self.syntax_err("retry: expected `backoff =>` after comma", line)
9909 );
9910 }
9911 }
9912 self.expect(&Token::FatArrow)?;
9913 let Token::Ident(mode) = self.peek().clone() else {
9914 return Err(self.syntax_err(
9915 "retry: expected backoff mode (none, linear, exponential)",
9916 line,
9917 ));
9918 };
9919 backoff = match mode.as_str() {
9920 "none" => RetryBackoff::None,
9921 "linear" => RetryBackoff::Linear,
9922 "exponential" => RetryBackoff::Exponential,
9923 _ => {
9924 return Err(
9925 self.syntax_err(format!("retry: invalid backoff `{mode}`"), line)
9926 );
9927 }
9928 };
9929 self.advance();
9930 }
9931 Ok(Expr {
9932 kind: ExprKind::RetryBlock {
9933 body,
9934 times,
9935 backoff,
9936 },
9937 line,
9938 })
9939 }
9940 "rate_limit" => {
9941 self.expect(&Token::LParen)?;
9942 let max = Box::new(self.parse_assign_expr()?);
9943 self.expect(&Token::Comma)?;
9944 let window = Box::new(self.parse_assign_expr()?);
9945 self.expect(&Token::RParen)?;
9946 let body = self.parse_block_or_bareword_block_no_args()?;
9947 let slot = self.alloc_rate_limit_slot();
9948 Ok(Expr {
9949 kind: ExprKind::RateLimitBlock {
9950 slot,
9951 max,
9952 window,
9953 body,
9954 },
9955 line,
9956 })
9957 }
9958 "every" => {
9959 let has_paren = self.eat(&Token::LParen);
9962 let interval = Box::new(self.parse_assign_expr()?);
9963 if has_paren {
9964 self.expect(&Token::RParen)?;
9965 }
9966 let body = if matches!(self.peek(), Token::LBrace) {
9967 self.parse_block()?
9968 } else {
9969 let bline = self.peek_line();
9970 let expr = self.parse_assign_expr()?;
9971 vec![Statement::new(StmtKind::Expression(expr), bline)]
9972 };
9973 Ok(Expr {
9974 kind: ExprKind::EveryBlock { interval, body },
9975 line,
9976 })
9977 }
9978 "gen" => {
9979 if !matches!(self.peek(), Token::LBrace) {
9980 return Err(self.syntax_err("gen must be followed by { BLOCK }", line));
9981 }
9982 let body = self.parse_block()?;
9983 Ok(Expr {
9984 kind: ExprKind::GenBlock { body },
9985 line,
9986 })
9987 }
9988 "yield" => {
9989 let e = self.parse_assign_expr()?;
9990 Ok(Expr {
9991 kind: ExprKind::Yield(Box::new(e)),
9992 line,
9993 })
9994 }
9995 "await" => {
9996 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
9997 return Ok(e);
9998 }
9999 let a = self.parse_one_arg_or_default()?;
10002 Ok(Expr {
10003 kind: ExprKind::Await(Box::new(a)),
10004 line,
10005 })
10006 }
10007 "slurp" | "cat" | "c" => {
10008 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10009 return Ok(e);
10010 }
10011 let a = self.parse_one_arg_or_default()?;
10012 Ok(Expr {
10013 kind: ExprKind::Slurp(Box::new(a)),
10014 line,
10015 })
10016 }
10017 "capture" => {
10018 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10019 return Ok(e);
10020 }
10021 let a = self.parse_one_arg()?;
10022 Ok(Expr {
10023 kind: ExprKind::Capture(Box::new(a)),
10024 line,
10025 })
10026 }
10027 "fetch_url" => {
10028 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10029 return Ok(e);
10030 }
10031 let a = self.parse_one_arg()?;
10032 Ok(Expr {
10033 kind: ExprKind::FetchUrl(Box::new(a)),
10034 line,
10035 })
10036 }
10037 "pchannel" => {
10038 let capacity = if self.eat(&Token::LParen) {
10039 if matches!(self.peek(), Token::RParen) {
10040 self.advance();
10041 None
10042 } else {
10043 let e = self.parse_expression()?;
10044 self.expect(&Token::RParen)?;
10045 Some(Box::new(e))
10046 }
10047 } else {
10048 None
10049 };
10050 Ok(Expr {
10051 kind: ExprKind::Pchannel { capacity },
10052 line,
10053 })
10054 }
10055 "psort" => {
10056 if matches!(self.peek(), Token::LBrace)
10057 || matches!(self.peek(), Token::ScalarVar(ref v) if v == "a" || v == "b")
10058 || matches!(self.peek(), Token::Ident(ref name) if !Self::is_known_bareword(name))
10059 {
10060 let block = self.parse_block_or_bareword_cmp_block()?;
10061 self.eat(&Token::Comma);
10062 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
10063 Ok(Expr {
10064 kind: ExprKind::PSortExpr {
10065 cmp: Some(block),
10066 list: Box::new(list),
10067 progress: progress.map(Box::new),
10068 },
10069 line,
10070 })
10071 } else {
10072 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
10073 Ok(Expr {
10074 kind: ExprKind::PSortExpr {
10075 cmp: None,
10076 list: Box::new(list),
10077 progress: progress.map(Box::new),
10078 },
10079 line,
10080 })
10081 }
10082 }
10083 "preduce" => {
10084 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
10085 Ok(Expr {
10086 kind: ExprKind::PReduceExpr {
10087 block,
10088 list: Box::new(list),
10089 progress: progress.map(Box::new),
10090 },
10091 line,
10092 })
10093 }
10094 "preduce_init" => {
10095 let (init, block, list, progress) =
10096 self.parse_init_block_then_list_optional_progress()?;
10097 Ok(Expr {
10098 kind: ExprKind::PReduceInitExpr {
10099 init: Box::new(init),
10100 block,
10101 list: Box::new(list),
10102 progress: progress.map(Box::new),
10103 },
10104 line,
10105 })
10106 }
10107 "pmap_reduce" => {
10108 let map_block = self.parse_block_or_bareword_block()?;
10109 let reduce_block = if matches!(self.peek(), Token::LBrace) {
10112 self.parse_block()?
10113 } else {
10114 self.expect(&Token::Comma)?;
10116 self.parse_block_or_bareword_cmp_block()?
10117 };
10118 self.eat(&Token::Comma);
10119 let line = self.peek_line();
10120 if let Token::Ident(ref kw) = self.peek().clone() {
10121 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
10122 self.advance();
10123 self.expect(&Token::FatArrow)?;
10124 let prog = self.parse_assign_expr()?;
10125 return Ok(Expr {
10126 kind: ExprKind::PMapReduceExpr {
10127 map_block,
10128 reduce_block,
10129 list: Box::new(Expr {
10130 kind: ExprKind::List(vec![]),
10131 line,
10132 }),
10133 progress: Some(Box::new(prog)),
10134 },
10135 line,
10136 });
10137 }
10138 }
10139 if matches!(
10140 self.peek(),
10141 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof
10142 ) {
10143 return Ok(Expr {
10144 kind: ExprKind::PMapReduceExpr {
10145 map_block,
10146 reduce_block,
10147 list: Box::new(Expr {
10148 kind: ExprKind::List(vec![]),
10149 line,
10150 }),
10151 progress: None,
10152 },
10153 line,
10154 });
10155 }
10156 let mut parts = vec![self.parse_assign_expr()?];
10157 loop {
10158 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
10159 break;
10160 }
10161 if matches!(
10162 self.peek(),
10163 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof
10164 ) {
10165 break;
10166 }
10167 if let Token::Ident(ref kw) = self.peek().clone() {
10168 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
10169 self.advance();
10170 self.expect(&Token::FatArrow)?;
10171 let prog = self.parse_assign_expr()?;
10172 return Ok(Expr {
10173 kind: ExprKind::PMapReduceExpr {
10174 map_block,
10175 reduce_block,
10176 list: Box::new(merge_expr_list(parts)),
10177 progress: Some(Box::new(prog)),
10178 },
10179 line,
10180 });
10181 }
10182 }
10183 parts.push(self.parse_assign_expr()?);
10184 }
10185 Ok(Expr {
10186 kind: ExprKind::PMapReduceExpr {
10187 map_block,
10188 reduce_block,
10189 list: Box::new(merge_expr_list(parts)),
10190 progress: None,
10191 },
10192 line,
10193 })
10194 }
10195 "puniq" => {
10196 if self.pipe_supplies_slurped_list_operand() {
10197 return Ok(Expr {
10198 kind: ExprKind::FuncCall {
10199 name: "puniq".to_string(),
10200 args: vec![],
10201 },
10202 line,
10203 });
10204 }
10205 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
10206 let mut args = vec![list];
10207 if let Some(p) = progress {
10208 args.push(p);
10209 }
10210 Ok(Expr {
10211 kind: ExprKind::FuncCall {
10212 name: "puniq".to_string(),
10213 args,
10214 },
10215 line,
10216 })
10217 }
10218 "pfirst" => {
10219 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
10220 let cr = Expr {
10221 kind: ExprKind::CodeRef {
10222 params: vec![],
10223 body: block,
10224 },
10225 line,
10226 };
10227 let mut args = vec![cr, list];
10228 if let Some(p) = progress {
10229 args.push(p);
10230 }
10231 Ok(Expr {
10232 kind: ExprKind::FuncCall {
10233 name: "pfirst".to_string(),
10234 args,
10235 },
10236 line,
10237 })
10238 }
10239 "pany" => {
10240 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
10241 let cr = Expr {
10242 kind: ExprKind::CodeRef {
10243 params: vec![],
10244 body: block,
10245 },
10246 line,
10247 };
10248 let mut args = vec![cr, list];
10249 if let Some(p) = progress {
10250 args.push(p);
10251 }
10252 Ok(Expr {
10253 kind: ExprKind::FuncCall {
10254 name: "pany".to_string(),
10255 args,
10256 },
10257 line,
10258 })
10259 }
10260 "uniq" | "distinct" => {
10261 if self.pipe_supplies_slurped_list_operand() {
10262 return Ok(Expr {
10263 kind: ExprKind::FuncCall {
10264 name: name.clone(),
10265 args: vec![],
10266 },
10267 line,
10268 });
10269 }
10270 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
10271 if progress.is_some() {
10272 return Err(self.syntax_err(
10273 "`progress =>` is not supported for uniq (use puniq for parallel + progress)",
10274 line,
10275 ));
10276 }
10277 Ok(Expr {
10278 kind: ExprKind::FuncCall {
10279 name: name.clone(),
10280 args: vec![list],
10281 },
10282 line,
10283 })
10284 }
10285 "flatten" => {
10286 if self.pipe_supplies_slurped_list_operand() {
10287 return Ok(Expr {
10288 kind: ExprKind::FuncCall {
10289 name: "flatten".to_string(),
10290 args: vec![],
10291 },
10292 line,
10293 });
10294 }
10295 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
10296 if progress.is_some() {
10297 return Err(self.syntax_err("`progress =>` is not supported for flatten", line));
10298 }
10299 Ok(Expr {
10300 kind: ExprKind::FuncCall {
10301 name: "flatten".to_string(),
10302 args: vec![list],
10303 },
10304 line,
10305 })
10306 }
10307 "set" => {
10308 if self.pipe_supplies_slurped_list_operand() {
10309 return Ok(Expr {
10310 kind: ExprKind::FuncCall {
10311 name: "set".to_string(),
10312 args: vec![],
10313 },
10314 line,
10315 });
10316 }
10317 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
10318 if progress.is_some() {
10319 return Err(self.syntax_err("`progress =>` is not supported for set", line));
10320 }
10321 Ok(Expr {
10322 kind: ExprKind::FuncCall {
10323 name: "set".to_string(),
10324 args: vec![list],
10325 },
10326 line,
10327 })
10328 }
10329 "size" => {
10333 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10334 return Ok(e);
10335 }
10336 if self.pipe_supplies_slurped_list_operand() {
10337 return Ok(Expr {
10338 kind: ExprKind::FuncCall {
10339 name: "size".to_string(),
10340 args: vec![],
10341 },
10342 line,
10343 });
10344 }
10345 let a = self.parse_one_arg_or_default()?;
10346 Ok(Expr {
10347 kind: ExprKind::FuncCall {
10348 name: "size".to_string(),
10349 args: vec![a],
10350 },
10351 line,
10352 })
10353 }
10354 "list_count" | "list_size" | "count" | "len" | "cnt" => {
10355 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10356 return Ok(e);
10357 }
10358 if self.pipe_supplies_slurped_list_operand() {
10359 return Ok(Expr {
10360 kind: ExprKind::FuncCall {
10361 name: name.clone(),
10362 args: vec![],
10363 },
10364 line,
10365 });
10366 }
10367 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
10368 if progress.is_some() {
10369 return Err(self.syntax_err(
10370 "`progress =>` is not supported for list_count / list_size / count / cnt",
10371 line,
10372 ));
10373 }
10374 Ok(Expr {
10375 kind: ExprKind::FuncCall {
10376 name: name.clone(),
10377 args: vec![list],
10378 },
10379 line,
10380 })
10381 }
10382 "shuffle" | "shuffled" => {
10383 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10384 return Ok(e);
10385 }
10386 if self.pipe_supplies_slurped_list_operand() {
10387 return Ok(Expr {
10388 kind: ExprKind::FuncCall {
10389 name: "shuffle".to_string(),
10390 args: vec![],
10391 },
10392 line,
10393 });
10394 }
10395 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
10396 if progress.is_some() {
10397 return Err(self.syntax_err("`progress =>` is not supported for shuffle", line));
10398 }
10399 Ok(Expr {
10400 kind: ExprKind::FuncCall {
10401 name: "shuffle".to_string(),
10402 args: vec![list],
10403 },
10404 line,
10405 })
10406 }
10407 "chunked" => {
10408 let mut parts = Vec::new();
10409 if self.eat(&Token::LParen) {
10410 if !matches!(self.peek(), Token::RParen) {
10411 parts.push(self.parse_assign_expr()?);
10412 while self.eat(&Token::Comma) {
10413 if matches!(self.peek(), Token::RParen) {
10414 break;
10415 }
10416 parts.push(self.parse_assign_expr()?);
10417 }
10418 }
10419 self.expect(&Token::RParen)?;
10420 } else {
10421 parts.push(self.parse_assign_expr_stop_at_pipe()?);
10425 loop {
10426 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
10427 break;
10428 }
10429 if matches!(
10430 self.peek(),
10431 Token::Semicolon
10432 | Token::RBrace
10433 | Token::RParen
10434 | Token::Eof
10435 | Token::PipeForward
10436 ) {
10437 break;
10438 }
10439 if self.peek_is_postfix_stmt_modifier_keyword() {
10440 break;
10441 }
10442 parts.push(self.parse_assign_expr_stop_at_pipe()?);
10443 }
10444 }
10445 if parts.len() == 1 {
10446 let n = parts.pop().unwrap();
10447 return Ok(Expr {
10448 kind: ExprKind::FuncCall {
10449 name: "chunked".to_string(),
10450 args: vec![n],
10451 },
10452 line,
10453 });
10454 }
10455 if parts.is_empty() {
10456 return Ok(Expr {
10457 kind: ExprKind::FuncCall {
10458 name: "chunked".to_string(),
10459 args: parts,
10460 },
10461 line,
10462 });
10463 }
10464 if parts.len() == 2 {
10465 let n = parts.pop().unwrap();
10466 let list = parts.pop().unwrap();
10467 return Ok(Expr {
10468 kind: ExprKind::FuncCall {
10469 name: "chunked".to_string(),
10470 args: vec![list, n],
10471 },
10472 line,
10473 });
10474 }
10475 Err(self.syntax_err(
10476 "chunked: use LIST |> chunked(N) or chunked((1,2,3), 2)",
10477 line,
10478 ))
10479 }
10480 "windowed" => {
10481 let mut parts = Vec::new();
10482 if self.eat(&Token::LParen) {
10483 if !matches!(self.peek(), Token::RParen) {
10484 parts.push(self.parse_assign_expr()?);
10485 while self.eat(&Token::Comma) {
10486 if matches!(self.peek(), Token::RParen) {
10487 break;
10488 }
10489 parts.push(self.parse_assign_expr()?);
10490 }
10491 }
10492 self.expect(&Token::RParen)?;
10493 } else {
10494 parts.push(self.parse_assign_expr_stop_at_pipe()?);
10497 loop {
10498 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
10499 break;
10500 }
10501 if matches!(
10502 self.peek(),
10503 Token::Semicolon
10504 | Token::RBrace
10505 | Token::RParen
10506 | Token::Eof
10507 | Token::PipeForward
10508 ) {
10509 break;
10510 }
10511 if self.peek_is_postfix_stmt_modifier_keyword() {
10512 break;
10513 }
10514 parts.push(self.parse_assign_expr_stop_at_pipe()?);
10515 }
10516 }
10517 if parts.len() == 1 {
10518 let n = parts.pop().unwrap();
10519 return Ok(Expr {
10520 kind: ExprKind::FuncCall {
10521 name: "windowed".to_string(),
10522 args: vec![n],
10523 },
10524 line,
10525 });
10526 }
10527 if parts.is_empty() {
10528 return Ok(Expr {
10529 kind: ExprKind::FuncCall {
10530 name: "windowed".to_string(),
10531 args: parts,
10532 },
10533 line,
10534 });
10535 }
10536 if parts.len() == 2 {
10537 let n = parts.pop().unwrap();
10538 let list = parts.pop().unwrap();
10539 return Ok(Expr {
10540 kind: ExprKind::FuncCall {
10541 name: "windowed".to_string(),
10542 args: vec![list, n],
10543 },
10544 line,
10545 });
10546 }
10547 Err(self.syntax_err(
10548 "windowed: use LIST |> windowed(N) or windowed((1,2,3), 2)",
10549 line,
10550 ))
10551 }
10552 "any" | "all" | "none" => {
10553 if matches!(self.peek(), Token::LParen) {
10555 self.advance();
10556 let args = self.parse_arg_list()?;
10557 self.expect(&Token::RParen)?;
10558 return Ok(Expr {
10559 kind: ExprKind::FuncCall {
10560 name: name.clone(),
10561 args,
10562 },
10563 line,
10564 });
10565 }
10566 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
10568 if progress.is_some() {
10569 return Err(self.syntax_err(
10570 "`progress =>` is not supported for any/all/none (use pany for parallel + progress)",
10571 line,
10572 ));
10573 }
10574 let cr = Expr {
10575 kind: ExprKind::CodeRef {
10576 params: vec![],
10577 body: block,
10578 },
10579 line,
10580 };
10581 Ok(Expr {
10582 kind: ExprKind::FuncCall {
10583 name: name.clone(),
10584 args: vec![cr, list],
10585 },
10586 line,
10587 })
10588 }
10589 "first" | "detect" | "find" => {
10591 if matches!(self.peek(), Token::LParen) {
10593 self.advance();
10594 let args = self.parse_arg_list()?;
10595 self.expect(&Token::RParen)?;
10596 return Ok(Expr {
10597 kind: ExprKind::FuncCall {
10598 name: "first".to_string(),
10599 args,
10600 },
10601 line,
10602 });
10603 }
10604 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
10606 if progress.is_some() {
10607 return Err(self.syntax_err(
10608 "`progress =>` is not supported for first/detect/find (use pfirst for parallel + progress)",
10609 line,
10610 ));
10611 }
10612 let cr = Expr {
10613 kind: ExprKind::CodeRef {
10614 params: vec![],
10615 body: block,
10616 },
10617 line,
10618 };
10619 Ok(Expr {
10620 kind: ExprKind::FuncCall {
10621 name: "first".to_string(),
10622 args: vec![cr, list],
10623 },
10624 line,
10625 })
10626 }
10627 "take_while" | "drop_while" | "skip_while" | "reject" | "tap" | "peek"
10628 | "partition" | "min_by" | "max_by" | "zip_with" | "count_by" => {
10629 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
10630 if progress.is_some() {
10631 return Err(
10632 self.syntax_err(format!("`progress =>` is not supported for {name}"), line)
10633 );
10634 }
10635 let cr = Expr {
10636 kind: ExprKind::CodeRef {
10637 params: vec![],
10638 body: block,
10639 },
10640 line,
10641 };
10642 Ok(Expr {
10643 kind: ExprKind::FuncCall {
10644 name: name.to_string(),
10645 args: vec![cr, list],
10646 },
10647 line,
10648 })
10649 }
10650 "group_by" | "chunk_by" => {
10651 if matches!(self.peek(), Token::LBrace) {
10652 let (block, list) = self.parse_block_list()?;
10653 let cr = Expr {
10654 kind: ExprKind::CodeRef {
10655 params: vec![],
10656 body: block,
10657 },
10658 line,
10659 };
10660 Ok(Expr {
10661 kind: ExprKind::FuncCall {
10662 name: name.to_string(),
10663 args: vec![cr, list],
10664 },
10665 line,
10666 })
10667 } else {
10668 let key_expr = self.parse_assign_expr()?;
10669 self.expect(&Token::Comma)?;
10670 let list_parts = self.parse_list_until_terminator()?;
10671 let list_expr = if list_parts.len() == 1 {
10672 list_parts.into_iter().next().unwrap()
10673 } else {
10674 Expr {
10675 kind: ExprKind::List(list_parts),
10676 line,
10677 }
10678 };
10679 Ok(Expr {
10680 kind: ExprKind::FuncCall {
10681 name: name.to_string(),
10682 args: vec![key_expr, list_expr],
10683 },
10684 line,
10685 })
10686 }
10687 }
10688 "with_index" => {
10689 if self.pipe_supplies_slurped_list_operand() {
10690 return Ok(Expr {
10691 kind: ExprKind::FuncCall {
10692 name: "with_index".to_string(),
10693 args: vec![],
10694 },
10695 line,
10696 });
10697 }
10698 let (list, progress) = self.parse_assign_expr_list_optional_progress()?;
10699 if progress.is_some() {
10700 return Err(
10701 self.syntax_err("`progress =>` is not supported for with_index", line)
10702 );
10703 }
10704 Ok(Expr {
10705 kind: ExprKind::FuncCall {
10706 name: "with_index".to_string(),
10707 args: vec![list],
10708 },
10709 line,
10710 })
10711 }
10712 "pcache" => {
10713 let (block, list, progress) = self.parse_block_then_list_optional_progress()?;
10714 Ok(Expr {
10715 kind: ExprKind::PcacheExpr {
10716 block,
10717 list: Box::new(list),
10718 progress: progress.map(Box::new),
10719 },
10720 line,
10721 })
10722 }
10723 "pselect" => {
10724 let paren = self.eat(&Token::LParen);
10725 let (receivers, timeout) = self.parse_comma_expr_list_with_timeout_tail(paren)?;
10726 if paren {
10727 self.expect(&Token::RParen)?;
10728 }
10729 if receivers.is_empty() {
10730 return Err(self.syntax_err("pselect needs at least one receiver", line));
10731 }
10732 Ok(Expr {
10733 kind: ExprKind::PselectExpr {
10734 receivers,
10735 timeout: timeout.map(Box::new),
10736 },
10737 line,
10738 })
10739 }
10740 "open" => {
10741 let paren = matches!(self.peek(), Token::LParen);
10742 if paren {
10743 self.advance();
10744 }
10745 if matches!(self.peek(), Token::Ident(ref s) if s == "my") {
10746 self.advance();
10747 let name = self.parse_scalar_var_name()?;
10748 self.expect(&Token::Comma)?;
10749 let mode = self.parse_assign_expr()?;
10750 let file = if self.eat(&Token::Comma) {
10751 Some(self.parse_assign_expr()?)
10752 } else {
10753 None
10754 };
10755 if paren {
10756 self.expect(&Token::RParen)?;
10757 }
10758 Ok(Expr {
10759 kind: ExprKind::Open {
10760 handle: Box::new(Expr {
10761 kind: ExprKind::OpenMyHandle { name },
10762 line,
10763 }),
10764 mode: Box::new(mode),
10765 file: file.map(Box::new),
10766 },
10767 line,
10768 })
10769 } else {
10770 let args = if paren {
10771 self.parse_arg_list()?
10772 } else {
10773 self.parse_list_until_terminator()?
10774 };
10775 if paren {
10776 self.expect(&Token::RParen)?;
10777 }
10778 if args.len() < 2 {
10779 return Err(self.syntax_err("open requires at least 2 arguments", line));
10780 }
10781 Ok(Expr {
10782 kind: ExprKind::Open {
10783 handle: Box::new(args[0].clone()),
10784 mode: Box::new(args[1].clone()),
10785 file: args.get(2).cloned().map(Box::new),
10786 },
10787 line,
10788 })
10789 }
10790 }
10791 "close" => {
10792 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10793 return Ok(e);
10794 }
10795 let a = self.parse_one_arg_or_default()?;
10796 Ok(Expr {
10797 kind: ExprKind::Close(Box::new(a)),
10798 line,
10799 })
10800 }
10801 "opendir" => {
10802 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10803 return Ok(e);
10804 }
10805 let args = self.parse_builtin_args()?;
10806 if args.len() != 2 {
10807 return Err(self.syntax_err("opendir requires two arguments", line));
10808 }
10809 Ok(Expr {
10810 kind: ExprKind::Opendir {
10811 handle: Box::new(args[0].clone()),
10812 path: Box::new(args[1].clone()),
10813 },
10814 line,
10815 })
10816 }
10817 "readdir" => {
10818 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10819 return Ok(e);
10820 }
10821 let a = self.parse_one_arg()?;
10822 Ok(Expr {
10823 kind: ExprKind::Readdir(Box::new(a)),
10824 line,
10825 })
10826 }
10827 "closedir" => {
10828 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10829 return Ok(e);
10830 }
10831 let a = self.parse_one_arg()?;
10832 Ok(Expr {
10833 kind: ExprKind::Closedir(Box::new(a)),
10834 line,
10835 })
10836 }
10837 "rewinddir" => {
10838 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10839 return Ok(e);
10840 }
10841 let a = self.parse_one_arg()?;
10842 Ok(Expr {
10843 kind: ExprKind::Rewinddir(Box::new(a)),
10844 line,
10845 })
10846 }
10847 "telldir" => {
10848 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10849 return Ok(e);
10850 }
10851 let a = self.parse_one_arg()?;
10852 Ok(Expr {
10853 kind: ExprKind::Telldir(Box::new(a)),
10854 line,
10855 })
10856 }
10857 "seekdir" => {
10858 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10859 return Ok(e);
10860 }
10861 let args = self.parse_builtin_args()?;
10862 if args.len() != 2 {
10863 return Err(self.syntax_err("seekdir requires two arguments", line));
10864 }
10865 Ok(Expr {
10866 kind: ExprKind::Seekdir {
10867 handle: Box::new(args[0].clone()),
10868 position: Box::new(args[1].clone()),
10869 },
10870 line,
10871 })
10872 }
10873 "eof" => {
10874 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10875 return Ok(e);
10876 }
10877 if matches!(self.peek(), Token::LParen) {
10878 self.advance();
10879 if matches!(self.peek(), Token::RParen) {
10880 self.advance();
10881 Ok(Expr {
10882 kind: ExprKind::Eof(None),
10883 line,
10884 })
10885 } else {
10886 let a = self.parse_expression()?;
10887 self.expect(&Token::RParen)?;
10888 Ok(Expr {
10889 kind: ExprKind::Eof(Some(Box::new(a))),
10890 line,
10891 })
10892 }
10893 } else {
10894 Ok(Expr {
10895 kind: ExprKind::Eof(None),
10896 line,
10897 })
10898 }
10899 }
10900 "system" => {
10901 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10902 return Ok(e);
10903 }
10904 let args = self.parse_builtin_args()?;
10905 Ok(Expr {
10906 kind: ExprKind::System(args),
10907 line,
10908 })
10909 }
10910 "exec" => {
10911 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10912 return Ok(e);
10913 }
10914 let args = self.parse_builtin_args()?;
10915 Ok(Expr {
10916 kind: ExprKind::Exec(args),
10917 line,
10918 })
10919 }
10920 "eval" => {
10921 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10922 return Ok(e);
10923 }
10924 let a = if matches!(self.peek(), Token::LBrace) {
10925 let block = self.parse_block()?;
10926 Expr {
10927 kind: ExprKind::CodeRef {
10928 params: vec![],
10929 body: block,
10930 },
10931 line,
10932 }
10933 } else {
10934 self.parse_one_arg_or_default()?
10935 };
10936 Ok(Expr {
10937 kind: ExprKind::Eval(Box::new(a)),
10938 line,
10939 })
10940 }
10941 "do" => {
10942 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10943 return Ok(e);
10944 }
10945 let a = self.parse_one_arg()?;
10946 Ok(Expr {
10947 kind: ExprKind::Do(Box::new(a)),
10948 line,
10949 })
10950 }
10951 "require" => {
10952 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10953 return Ok(e);
10954 }
10955 let a = self.parse_one_arg()?;
10956 Ok(Expr {
10957 kind: ExprKind::Require(Box::new(a)),
10958 line,
10959 })
10960 }
10961 "exit" => {
10962 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10963 return Ok(e);
10964 }
10965 if matches!(
10966 self.peek(),
10967 Token::Semicolon | Token::RBrace | Token::Eof | Token::PipeForward
10968 ) {
10969 Ok(Expr {
10970 kind: ExprKind::Exit(None),
10971 line,
10972 })
10973 } else {
10974 let a = self.parse_one_arg()?;
10975 Ok(Expr {
10976 kind: ExprKind::Exit(Some(Box::new(a))),
10977 line,
10978 })
10979 }
10980 }
10981 "chdir" => {
10982 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10983 return Ok(e);
10984 }
10985 let a = self.parse_one_arg_or_default()?;
10986 Ok(Expr {
10987 kind: ExprKind::Chdir(Box::new(a)),
10988 line,
10989 })
10990 }
10991 "mkdir" => {
10992 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
10993 return Ok(e);
10994 }
10995 let args = self.parse_builtin_args()?;
10996 Ok(Expr {
10997 kind: ExprKind::Mkdir {
10998 path: Box::new(args[0].clone()),
10999 mode: args.get(1).cloned().map(Box::new),
11000 },
11001 line,
11002 })
11003 }
11004 "unlink" | "rm" => {
11005 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11006 return Ok(e);
11007 }
11008 let args = self.parse_builtin_args()?;
11009 Ok(Expr {
11010 kind: ExprKind::Unlink(args),
11011 line,
11012 })
11013 }
11014 "rename" => {
11015 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11016 return Ok(e);
11017 }
11018 let args = self.parse_builtin_args()?;
11019 if args.len() != 2 {
11020 return Err(self.syntax_err("rename requires two arguments", line));
11021 }
11022 Ok(Expr {
11023 kind: ExprKind::Rename {
11024 old: Box::new(args[0].clone()),
11025 new: Box::new(args[1].clone()),
11026 },
11027 line,
11028 })
11029 }
11030 "chmod" => {
11031 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11032 return Ok(e);
11033 }
11034 let args = self.parse_builtin_args()?;
11035 if args.len() < 2 {
11036 return Err(self.syntax_err("chmod requires mode and at least one file", line));
11037 }
11038 Ok(Expr {
11039 kind: ExprKind::Chmod(args),
11040 line,
11041 })
11042 }
11043 "chown" => {
11044 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11045 return Ok(e);
11046 }
11047 let args = self.parse_builtin_args()?;
11048 if args.len() < 3 {
11049 return Err(
11050 self.syntax_err("chown requires uid, gid, and at least one file", line)
11051 );
11052 }
11053 Ok(Expr {
11054 kind: ExprKind::Chown(args),
11055 line,
11056 })
11057 }
11058 "stat" => {
11059 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11060 return Ok(e);
11061 }
11062 let args = self.parse_builtin_args()?;
11063 let arg = if args.len() == 1 {
11064 args[0].clone()
11065 } else if args.is_empty() {
11066 Expr {
11067 kind: ExprKind::ScalarVar("_".into()),
11068 line,
11069 }
11070 } else {
11071 return Err(self.syntax_err("stat requires zero or one argument", line));
11072 };
11073 Ok(Expr {
11074 kind: ExprKind::Stat(Box::new(arg)),
11075 line,
11076 })
11077 }
11078 "lstat" => {
11079 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11080 return Ok(e);
11081 }
11082 let args = self.parse_builtin_args()?;
11083 let arg = if args.len() == 1 {
11084 args[0].clone()
11085 } else if args.is_empty() {
11086 Expr {
11087 kind: ExprKind::ScalarVar("_".into()),
11088 line,
11089 }
11090 } else {
11091 return Err(self.syntax_err("lstat requires zero or one argument", line));
11092 };
11093 Ok(Expr {
11094 kind: ExprKind::Lstat(Box::new(arg)),
11095 line,
11096 })
11097 }
11098 "link" => {
11099 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11100 return Ok(e);
11101 }
11102 let args = self.parse_builtin_args()?;
11103 if args.len() != 2 {
11104 return Err(self.syntax_err("link requires two arguments", line));
11105 }
11106 Ok(Expr {
11107 kind: ExprKind::Link {
11108 old: Box::new(args[0].clone()),
11109 new: Box::new(args[1].clone()),
11110 },
11111 line,
11112 })
11113 }
11114 "symlink" => {
11115 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11116 return Ok(e);
11117 }
11118 let args = self.parse_builtin_args()?;
11119 if args.len() != 2 {
11120 return Err(self.syntax_err("symlink requires two arguments", line));
11121 }
11122 Ok(Expr {
11123 kind: ExprKind::Symlink {
11124 old: Box::new(args[0].clone()),
11125 new: Box::new(args[1].clone()),
11126 },
11127 line,
11128 })
11129 }
11130 "readlink" => {
11131 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11132 return Ok(e);
11133 }
11134 let args = self.parse_builtin_args()?;
11135 let arg = if args.len() == 1 {
11136 args[0].clone()
11137 } else if args.is_empty() {
11138 Expr {
11139 kind: ExprKind::ScalarVar("_".into()),
11140 line,
11141 }
11142 } else {
11143 return Err(self.syntax_err("readlink requires zero or one argument", line));
11144 };
11145 Ok(Expr {
11146 kind: ExprKind::Readlink(Box::new(arg)),
11147 line,
11148 })
11149 }
11150 "files" => {
11151 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11152 return Ok(e);
11153 }
11154 let args = self.parse_builtin_args()?;
11155 Ok(Expr {
11156 kind: ExprKind::Files(args),
11157 line,
11158 })
11159 }
11160 "filesf" | "f" => {
11161 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11162 return Ok(e);
11163 }
11164 let args = self.parse_builtin_args()?;
11165 Ok(Expr {
11166 kind: ExprKind::Filesf(args),
11167 line,
11168 })
11169 }
11170 "fr" => {
11171 let args = self.parse_builtin_args()?;
11172 Ok(Expr {
11173 kind: ExprKind::FilesfRecursive(args),
11174 line,
11175 })
11176 }
11177 "dirs" | "d" => {
11178 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11179 return Ok(e);
11180 }
11181 let args = self.parse_builtin_args()?;
11182 Ok(Expr {
11183 kind: ExprKind::Dirs(args),
11184 line,
11185 })
11186 }
11187 "dr" => {
11188 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11189 return Ok(e);
11190 }
11191 let args = self.parse_builtin_args()?;
11192 Ok(Expr {
11193 kind: ExprKind::DirsRecursive(args),
11194 line,
11195 })
11196 }
11197 "sym_links" => {
11198 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11199 return Ok(e);
11200 }
11201 let args = self.parse_builtin_args()?;
11202 Ok(Expr {
11203 kind: ExprKind::SymLinks(args),
11204 line,
11205 })
11206 }
11207 "sockets" => {
11208 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11209 return Ok(e);
11210 }
11211 let args = self.parse_builtin_args()?;
11212 Ok(Expr {
11213 kind: ExprKind::Sockets(args),
11214 line,
11215 })
11216 }
11217 "pipes" => {
11218 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11219 return Ok(e);
11220 }
11221 let args = self.parse_builtin_args()?;
11222 Ok(Expr {
11223 kind: ExprKind::Pipes(args),
11224 line,
11225 })
11226 }
11227 "block_devices" => {
11228 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11229 return Ok(e);
11230 }
11231 let args = self.parse_builtin_args()?;
11232 Ok(Expr {
11233 kind: ExprKind::BlockDevices(args),
11234 line,
11235 })
11236 }
11237 "char_devices" => {
11238 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11239 return Ok(e);
11240 }
11241 let args = self.parse_builtin_args()?;
11242 Ok(Expr {
11243 kind: ExprKind::CharDevices(args),
11244 line,
11245 })
11246 }
11247 "exe" | "executables" => {
11248 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11249 return Ok(e);
11250 }
11251 let args = self.parse_builtin_args()?;
11252 Ok(Expr {
11253 kind: ExprKind::Executables(args),
11254 line,
11255 })
11256 }
11257 "glob" => {
11258 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11259 return Ok(e);
11260 }
11261 let args = self.parse_builtin_args()?;
11262 Ok(Expr {
11263 kind: ExprKind::Glob(args),
11264 line,
11265 })
11266 }
11267 "glob_par" => {
11268 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11269 return Ok(e);
11270 }
11271 let (args, progress) = self.parse_glob_par_or_par_sed_args()?;
11272 Ok(Expr {
11273 kind: ExprKind::GlobPar { args, progress },
11274 line,
11275 })
11276 }
11277 "par_sed" => {
11278 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11279 return Ok(e);
11280 }
11281 let (args, progress) = self.parse_glob_par_or_par_sed_args()?;
11282 Ok(Expr {
11283 kind: ExprKind::ParSed { args, progress },
11284 line,
11285 })
11286 }
11287 "bless" => {
11288 if let Some(e) = self.fat_arrow_autoquote(&name, line) {
11289 return Ok(e);
11290 }
11291 let args = self.parse_builtin_args()?;
11292 Ok(Expr {
11293 kind: ExprKind::Bless {
11294 ref_expr: Box::new(args[0].clone()),
11295 class: args.get(1).cloned().map(Box::new),
11296 },
11297 line,
11298 })
11299 }
11300 "caller" => {
11301 if matches!(self.peek(), Token::LParen) {
11302 self.advance();
11303 if matches!(self.peek(), Token::RParen) {
11304 self.advance();
11305 Ok(Expr {
11306 kind: ExprKind::Caller(None),
11307 line,
11308 })
11309 } else {
11310 let a = self.parse_expression()?;
11311 self.expect(&Token::RParen)?;
11312 Ok(Expr {
11313 kind: ExprKind::Caller(Some(Box::new(a))),
11314 line,
11315 })
11316 }
11317 } else {
11318 Ok(Expr {
11319 kind: ExprKind::Caller(None),
11320 line,
11321 })
11322 }
11323 }
11324 "wantarray" => {
11325 if matches!(self.peek(), Token::LParen) {
11326 self.advance();
11327 self.expect(&Token::RParen)?;
11328 }
11329 Ok(Expr {
11330 kind: ExprKind::Wantarray,
11331 line,
11332 })
11333 }
11334 "sub" => {
11335 if crate::no_interop_mode() {
11337 return Err(self.syntax_err(
11338 "stryke uses `fn {}` instead of `sub {}` (--no-interop)",
11339 line,
11340 ));
11341 }
11342 let (params, _prototype) = self.parse_sub_sig_or_prototype_opt()?;
11344 let body = self.parse_block()?;
11345 Ok(Expr {
11346 kind: ExprKind::CodeRef { params, body },
11347 line,
11348 })
11349 }
11350 "fn" => {
11351 let (params, _prototype) = self.parse_sub_sig_or_prototype_opt()?;
11353 let body = self.parse_block()?;
11354 Ok(Expr {
11355 kind: ExprKind::CodeRef { params, body },
11356 line,
11357 })
11358 }
11359 _ => {
11360 if matches!(self.peek(), Token::FatArrow) {
11363 return Ok(Expr {
11364 kind: ExprKind::String(name),
11365 line,
11366 });
11367 }
11368 if name == "_" {
11371 return Ok(Expr {
11372 kind: ExprKind::ScalarVar("_".to_string()),
11373 line,
11374 });
11375 }
11376 if matches!(self.peek(), Token::LParen) {
11378 self.advance();
11379 let args = self.parse_arg_list()?;
11380 self.expect(&Token::RParen)?;
11381 Ok(Expr {
11382 kind: ExprKind::FuncCall { name, args },
11383 line,
11384 })
11385 } else if self.peek().is_term_start()
11386 && !(matches!(self.peek(), Token::Ident(ref kw) if kw == "sub")
11387 && matches!(self.peek_at(1), Token::Ident(_)))
11388 && !(self.suppress_parenless_call > 0 && matches!(self.peek(), Token::Ident(_)))
11389 && !(matches!(self.peek(), Token::LBrace)
11390 && self.peek_line() > self.prev_line())
11391 {
11392 let args = self.parse_list_until_terminator()?;
11402 Ok(Expr {
11403 kind: ExprKind::FuncCall { name, args },
11404 line,
11405 })
11406 } else {
11407 Ok(Expr {
11413 kind: ExprKind::Bareword(name),
11414 line,
11415 })
11416 }
11417 }
11418 }
11419 }
11420
11421 fn parse_print_like(
11422 &mut self,
11423 make: impl FnOnce(Option<String>, Vec<Expr>) -> ExprKind,
11424 ) -> PerlResult<Expr> {
11425 let line = self.peek_line();
11426 let handle = if let Token::Ident(ref h) = self.peek().clone() {
11428 if h.chars().all(|c| c.is_uppercase() || c == '_')
11429 && !matches!(self.peek(), Token::LParen)
11430 {
11431 let h = h.clone();
11432 let saved = self.pos;
11433 self.advance();
11434 if self.peek().is_term_start()
11436 || matches!(
11437 self.peek(),
11438 Token::DoubleString(_) | Token::BacktickString(_) | Token::SingleString(_)
11439 )
11440 {
11441 Some(h)
11442 } else {
11443 self.pos = saved;
11444 None
11445 }
11446 } else {
11447 None
11448 }
11449 } else if let Token::ScalarVar(ref v) = self.peek().clone() {
11450 let v = v.clone();
11460 if v == "_" {
11461 None
11462 } else {
11463 let saved = self.pos;
11464 self.advance();
11465 let next = self.peek().clone();
11466 let is_stmt_modifier = matches!(&next, Token::Ident(kw)
11467 if matches!(kw.as_str(), "if" | "unless" | "while" | "until" | "for" | "foreach"));
11468 if !is_stmt_modifier
11469 && !matches!(next, Token::LBracket | Token::LBrace)
11470 && (next.is_term_start()
11471 || matches!(
11472 next,
11473 Token::DoubleString(_)
11474 | Token::BacktickString(_)
11475 | Token::SingleString(_)
11476 ))
11477 {
11478 Some(format!("${v}"))
11480 } else {
11481 self.pos = saved;
11482 None
11483 }
11484 }
11485 } else {
11486 None
11487 };
11488 let args =
11493 if matches!(self.peek(), Token::LParen) && matches!(self.peek_at(1), Token::RParen) {
11494 let line_topic = self.peek_line();
11495 self.advance(); self.advance(); vec![Expr {
11498 kind: ExprKind::ScalarVar("_".into()),
11499 line: line_topic,
11500 }]
11501 } else {
11502 self.parse_list_until_terminator()?
11503 };
11504 Ok(Expr {
11505 kind: make(handle, args),
11506 line,
11507 })
11508 }
11509
11510 fn parse_block_list(&mut self) -> PerlResult<(Block, Expr)> {
11511 let block = self.parse_block()?;
11512 let block_end_line = self.prev_line();
11513 self.eat(&Token::Comma);
11514 if self.in_pipe_rhs()
11518 && (matches!(
11519 self.peek(),
11520 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof | Token::PipeForward
11521 ) || self.peek_line() > block_end_line)
11522 {
11523 let line = self.peek_line();
11524 return Ok((block, self.pipe_placeholder_list(line)));
11525 }
11526 let list = self.parse_expression()?;
11527 Ok((block, list))
11528 }
11529
11530 fn parse_comma_expr_list_with_timeout_tail(
11533 &mut self,
11534 paren: bool,
11535 ) -> PerlResult<(Vec<Expr>, Option<Expr>)> {
11536 let mut parts = vec![self.parse_assign_expr()?];
11537 loop {
11538 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
11539 break;
11540 }
11541 if paren && matches!(self.peek(), Token::RParen) {
11542 break;
11543 }
11544 if matches!(
11545 self.peek(),
11546 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof
11547 ) {
11548 break;
11549 }
11550 if self.peek_is_postfix_stmt_modifier_keyword() {
11551 break;
11552 }
11553 if let Token::Ident(ref kw) = self.peek().clone() {
11554 if kw == "timeout" && matches!(self.peek_at(1), Token::FatArrow) {
11555 self.advance();
11556 self.expect(&Token::FatArrow)?;
11557 let t = self.parse_assign_expr()?;
11558 return Ok((parts, Some(t)));
11559 }
11560 }
11561 parts.push(self.parse_assign_expr()?);
11562 }
11563 Ok((parts, None))
11564 }
11565
11566 fn parse_init_block_then_list_optional_progress(
11568 &mut self,
11569 ) -> PerlResult<(Expr, Block, Expr, Option<Expr>)> {
11570 let init = self.parse_assign_expr()?;
11571 self.expect(&Token::Comma)?;
11572 let block = self.parse_block_or_bareword_block()?;
11573 self.eat(&Token::Comma);
11574 let line = self.peek_line();
11575 if let Token::Ident(ref kw) = self.peek().clone() {
11576 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
11577 self.advance();
11578 self.expect(&Token::FatArrow)?;
11579 let prog = self.parse_assign_expr()?;
11580 return Ok((
11581 init,
11582 block,
11583 Expr {
11584 kind: ExprKind::List(vec![]),
11585 line,
11586 },
11587 Some(prog),
11588 ));
11589 }
11590 }
11591 if matches!(
11592 self.peek(),
11593 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof
11594 ) {
11595 return Ok((
11596 init,
11597 block,
11598 Expr {
11599 kind: ExprKind::List(vec![]),
11600 line,
11601 },
11602 None,
11603 ));
11604 }
11605 let mut parts = vec![self.parse_assign_expr()?];
11606 loop {
11607 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
11608 break;
11609 }
11610 if matches!(
11611 self.peek(),
11612 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof
11613 ) {
11614 break;
11615 }
11616 if self.peek_is_postfix_stmt_modifier_keyword() {
11617 break;
11618 }
11619 if let Token::Ident(ref kw) = self.peek().clone() {
11620 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
11621 self.advance();
11622 self.expect(&Token::FatArrow)?;
11623 let prog = self.parse_assign_expr()?;
11624 return Ok((init, block, merge_expr_list(parts), Some(prog)));
11625 }
11626 }
11627 parts.push(self.parse_assign_expr()?);
11628 }
11629 Ok((init, block, merge_expr_list(parts), None))
11630 }
11631
11632 fn parse_cluster_block_then_list_optional_progress(
11634 &mut self,
11635 ) -> PerlResult<(Expr, Block, Expr, Option<Expr>)> {
11636 self.suppress_scalar_hash_brace = self.suppress_scalar_hash_brace.saturating_add(1);
11639 let cluster = self.parse_assign_expr();
11640 self.suppress_scalar_hash_brace = self.suppress_scalar_hash_brace.saturating_sub(1);
11641 let cluster = cluster?;
11642 self.eat(&Token::Comma);
11644 let block = self.parse_block_or_bareword_block()?;
11645 self.eat(&Token::Comma);
11646 let line = self.peek_line();
11647 if let Token::Ident(ref kw) = self.peek().clone() {
11648 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
11649 self.advance();
11650 self.expect(&Token::FatArrow)?;
11651 let prog = self.parse_assign_expr_stop_at_pipe()?;
11652 return Ok((
11653 cluster,
11654 block,
11655 Expr {
11656 kind: ExprKind::List(vec![]),
11657 line,
11658 },
11659 Some(prog),
11660 ));
11661 }
11662 }
11663 let empty_list_ok = matches!(
11664 self.peek(),
11665 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof | Token::PipeForward
11666 ) || (self.in_pipe_rhs() && matches!(self.peek(), Token::Comma));
11667 if empty_list_ok {
11668 return Ok((
11669 cluster,
11670 block,
11671 Expr {
11672 kind: ExprKind::List(vec![]),
11673 line,
11674 },
11675 None,
11676 ));
11677 }
11678 let mut parts = vec![self.parse_assign_expr_stop_at_pipe()?];
11679 loop {
11680 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
11681 break;
11682 }
11683 if matches!(
11684 self.peek(),
11685 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof | Token::PipeForward
11686 ) {
11687 break;
11688 }
11689 if self.peek_is_postfix_stmt_modifier_keyword() {
11690 break;
11691 }
11692 if let Token::Ident(ref kw) = self.peek().clone() {
11693 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
11694 self.advance();
11695 self.expect(&Token::FatArrow)?;
11696 let prog = self.parse_assign_expr_stop_at_pipe()?;
11697 return Ok((cluster, block, merge_expr_list(parts), Some(prog)));
11698 }
11699 }
11700 parts.push(self.parse_assign_expr_stop_at_pipe()?);
11701 }
11702 Ok((cluster, block, merge_expr_list(parts), None))
11703 }
11704
11705 fn parse_block_then_list_optional_progress(
11714 &mut self,
11715 ) -> PerlResult<(Block, Expr, Option<Expr>)> {
11716 let block = self.parse_block_or_bareword_block()?;
11717 self.eat(&Token::Comma);
11718 let line = self.peek_line();
11719 if let Token::Ident(ref kw) = self.peek().clone() {
11720 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
11721 self.advance();
11722 self.expect(&Token::FatArrow)?;
11723 let prog = self.parse_assign_expr_stop_at_pipe()?;
11724 return Ok((
11725 block,
11726 Expr {
11727 kind: ExprKind::List(vec![]),
11728 line,
11729 },
11730 Some(prog),
11731 ));
11732 }
11733 }
11734 let empty_list_ok = matches!(
11740 self.peek(),
11741 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof | Token::PipeForward
11742 ) || (self.in_pipe_rhs() && matches!(self.peek(), Token::Comma));
11743 if empty_list_ok {
11744 return Ok((
11745 block,
11746 Expr {
11747 kind: ExprKind::List(vec![]),
11748 line,
11749 },
11750 None,
11751 ));
11752 }
11753 let mut parts = vec![self.parse_assign_expr_stop_at_pipe()?];
11754 loop {
11755 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
11756 break;
11757 }
11758 if matches!(
11759 self.peek(),
11760 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof | Token::PipeForward
11761 ) {
11762 break;
11763 }
11764 if self.peek_is_postfix_stmt_modifier_keyword() {
11765 break;
11766 }
11767 if let Token::Ident(ref kw) = self.peek().clone() {
11768 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
11769 self.advance();
11770 self.expect(&Token::FatArrow)?;
11771 let prog = self.parse_assign_expr_stop_at_pipe()?;
11772 return Ok((block, merge_expr_list(parts), Some(prog)));
11773 }
11774 }
11775 parts.push(self.parse_assign_expr_stop_at_pipe()?);
11776 }
11777 Ok((block, merge_expr_list(parts), None))
11778 }
11779
11780 fn parse_fan_count_and_block(&mut self, line: usize) -> PerlResult<(Option<Box<Expr>>, Block)> {
11782 if matches!(self.peek(), Token::LBrace) {
11784 let block = self.parse_block()?;
11785 return Ok((None, block));
11786 }
11787 let saved = self.pos;
11788 let first = self.parse_postfix()?;
11790 if matches!(self.peek(), Token::LBrace) {
11791 let block = self.parse_block()?;
11793 Ok((Some(Box::new(first)), block))
11794 } else if matches!(self.peek(), Token::Semicolon | Token::RBrace | Token::Eof)
11795 || (matches!(self.peek(), Token::Comma)
11796 && matches!(self.peek_at(1), Token::Ident(ref kw) if kw == "progress"))
11797 {
11798 let block = self.bareword_to_no_arg_block(first);
11800 Ok((None, block))
11801 } else if matches!(first.kind, ExprKind::Integer(_)) {
11802 self.eat(&Token::Comma);
11804 let body = self.parse_fan_blockless_body(line)?;
11805 Ok((Some(Box::new(first)), body))
11806 } else {
11807 self.pos = saved;
11810 let body = self.parse_fan_blockless_body(line)?;
11811 Ok((None, body))
11812 }
11813 }
11814
11815 fn parse_fan_blockless_body(&mut self, line: usize) -> PerlResult<Block> {
11817 if matches!(self.peek(), Token::LBrace) {
11818 return self.parse_block();
11819 }
11820 if let Token::Ident(ref name) = self.peek().clone() {
11822 if matches!(
11823 self.peek_at(1),
11824 Token::Comma | Token::Semicolon | Token::RBrace | Token::Eof | Token::PipeForward
11825 ) {
11826 let name = name.clone();
11827 self.advance();
11828 let body = Expr {
11829 kind: ExprKind::FuncCall { name, args: vec![] },
11830 line,
11831 };
11832 return Ok(vec![Statement::new(StmtKind::Expression(body), line)]);
11833 }
11834 }
11835 let expr = self.parse_assign_expr_stop_at_pipe()?;
11837 Ok(vec![Statement::new(StmtKind::Expression(expr), line)])
11838 }
11839
11840 fn bareword_to_no_arg_block(&self, expr: Expr) -> Block {
11843 let line = expr.line;
11844 let body = match &expr.kind {
11845 ExprKind::Bareword(name) => Expr {
11846 kind: ExprKind::FuncCall {
11847 name: name.clone(),
11848 args: vec![],
11849 },
11850 line,
11851 },
11852 _ => expr,
11853 };
11854 vec![Statement::new(StmtKind::Expression(body), line)]
11855 }
11856
11857 fn parse_block_or_bareword_block(&mut self) -> PerlResult<Block> {
11866 if matches!(self.peek(), Token::LBrace) {
11867 return self.parse_block();
11868 }
11869 let line = self.peek_line();
11870 if let Token::Ident(ref name) = self.peek().clone() {
11873 if matches!(
11874 self.peek_at(1),
11875 Token::Comma | Token::Semicolon | Token::RBrace | Token::Eof | Token::PipeForward
11876 ) {
11877 let name = name.clone();
11878 self.advance();
11879 let body = Expr {
11880 kind: ExprKind::FuncCall {
11881 name,
11882 args: vec![Expr {
11883 kind: ExprKind::ScalarVar("_".to_string()),
11884 line,
11885 }],
11886 },
11887 line,
11888 };
11889 return Ok(vec![Statement::new(StmtKind::Expression(body), line)]);
11890 }
11891 }
11892 let expr = self.parse_assign_expr_stop_at_pipe()?;
11894 Ok(vec![Statement::new(StmtKind::Expression(expr), line)])
11895 }
11896
11897 fn parse_block_or_bareword_block_no_args(&mut self) -> PerlResult<Block> {
11902 if matches!(self.peek(), Token::LBrace) {
11903 return self.parse_block();
11904 }
11905 let line = self.peek_line();
11906 if let Token::Ident(ref name) = self.peek().clone() {
11907 if matches!(
11908 self.peek_at(1),
11909 Token::Comma
11910 | Token::Semicolon
11911 | Token::RBrace
11912 | Token::Eof
11913 | Token::PipeForward
11914 | Token::Integer(_)
11915 ) {
11916 let name = name.clone();
11917 self.advance();
11918 let body = Expr {
11919 kind: ExprKind::FuncCall { name, args: vec![] },
11920 line,
11921 };
11922 return Ok(vec![Statement::new(StmtKind::Expression(body), line)]);
11923 }
11924 }
11925 let expr = self.parse_postfix()?;
11926 Ok(vec![Statement::new(StmtKind::Expression(expr), line)])
11927 }
11928
11929 fn is_known_bareword(name: &str) -> bool {
11937 Self::is_perl5_core(name) || Self::stryke_extension_name(name).is_some()
11938 }
11939
11940 fn is_try_builtin_name(name: &str) -> bool {
11946 crate::builtins::BUILTIN_ARMS
11947 .iter()
11948 .any(|arm| arm.contains(&name))
11949 }
11950
11951 fn is_perl5_core(name: &str) -> bool {
11956 matches!(
11957 name,
11958 "map" | "grep" | "sort" | "reverse" | "join" | "split"
11960 | "push" | "pop" | "shift" | "unshift" | "splice"
11961 | "pack" | "unpack"
11962 | "keys" | "values" | "each"
11964 | "chomp" | "chop" | "chr" | "ord" | "hex" | "oct"
11966 | "lc" | "uc" | "lcfirst" | "ucfirst"
11967 | "length" | "substr" | "index" | "rindex"
11968 | "sprintf" | "printf" | "print" | "say"
11969 | "pos" | "quotemeta" | "study"
11970 | "abs" | "int" | "sqrt" | "sin" | "cos" | "atan2"
11972 | "exp" | "log" | "rand" | "srand"
11973 | "time" | "localtime" | "gmtime"
11975 | "defined" | "undef" | "ref" | "scalar" | "wantarray"
11977 | "caller" | "delete" | "exists" | "bless" | "prototype"
11978 | "tie" | "untie" | "tied"
11979 | "open" | "close" | "read" | "readline" | "write" | "seek" | "tell"
11981 | "eof" | "binmode" | "getc" | "fileno" | "truncate"
11982 | "format" | "formline" | "select" | "vec"
11983 | "sysopen" | "sysread" | "sysseek" | "syswrite"
11984 | "stat" | "lstat" | "rename" | "unlink" | "utime"
11986 | "mkdir" | "rmdir" | "chdir" | "chmod" | "chown"
11987 | "glob" | "opendir" | "readdir" | "closedir"
11988 | "link" | "readlink" | "symlink"
11989 | "fcntl" | "flock" | "ioctl" | "pipe" | "dbmopen" | "dbmclose"
11991 | "msgctl" | "msgget" | "msgrcv" | "msgsnd"
11993 | "semctl" | "semget" | "semop"
11994 | "shmctl" | "shmget" | "shmread" | "shmwrite"
11995 | "system" | "exec" | "exit" | "die" | "warn" | "dump"
11997 | "fork" | "wait" | "waitpid" | "kill" | "alarm" | "sleep"
11998 | "chroot" | "times" | "umask" | "reset"
11999 | "getpgrp" | "setpgrp" | "getppid"
12000 | "getpriority" | "setpriority"
12001 | "socket" | "socketpair" | "connect" | "listen" | "accept" | "shutdown"
12003 | "send" | "recv" | "bind" | "setsockopt" | "getsockopt"
12004 | "getpeername" | "getsockname"
12005 | "getpwnam" | "getpwuid" | "getpwent" | "setpwent"
12007 | "getgrnam" | "getgrgid" | "getgrent" | "setgrent"
12008 | "getlogin"
12009 | "gethostbyname" | "gethostbyaddr" | "gethostent"
12010 | "getnetbyname" | "getnetent"
12011 | "getprotobyname" | "getprotoent"
12012 | "getservbyname" | "getservent"
12013 | "sethostent" | "setnetent" | "setprotoent" | "setservent"
12014 | "endpwent" | "endgrent"
12015 | "endhostent" | "endnetent" | "endprotoent" | "endservent"
12016 | "return" | "do" | "eval" | "require"
12018 | "my" | "our" | "local" | "use" | "no"
12019 | "sub" | "if" | "unless" | "while" | "until"
12020 | "for" | "foreach" | "last" | "next" | "redo" | "goto"
12021 | "not" | "and" | "or"
12022 | "qw" | "qq" | "q"
12024 | "BEGIN" | "END"
12026 )
12027 }
12028
12029 fn stryke_extension_name(name: &str) -> Option<&str> {
12032 match name {
12033 | "pmap" | "pmap_on" | "pflat_map" | "pflat_map_on" | "pmap_chunked"
12035 | "pgrep" | "pfor" | "psort" | "preduce" | "preduce_init" | "pmap_reduce"
12036 | "pcache" | "pchannel" | "pselect" | "puniq" | "pfirst" | "pany"
12037 | "fan" | "fan_cap" | "par_lines" | "par_walk" | "par_sed"
12038 | "par_find_files" | "par_line_count" | "pwatch" | "par_pipeline_stream"
12039 | "glob_par" | "ppool" | "barrier" | "pipeline" | "cluster"
12040 | "pmaps" | "pflat_maps" | "pgreps"
12041 | "fore" | "e" | "ep" | "flat_map" | "flat_maps" | "maps" | "filter" | "fi" | "find_all" | "reduce" | "fold"
12043 | "inject" | "collect" | "uniq" | "distinct" | "any" | "all" | "none"
12044 | "first" | "detect" | "find" | "compact" | "concat" | "chain" | "reject" | "flatten" | "set"
12045 | "min_by" | "max_by" | "sort_by" | "tally" | "find_index"
12046 | "each_with_index" | "count" | "cnt" |"len" | "group_by" | "chunk_by"
12047 | "zip" | "chunk" | "chunked" | "sliding_window" | "windowed"
12048 | "enumerate" | "with_index" | "shuffle" | "shuffled"| "heap"
12049 | "take_while" | "drop_while" | "skip_while" | "tap" | "peek" | "partition"
12050 | "zip_with" | "count_by" | "skip" | "first_or"
12051 | "input" | "lines" | "words" | "chars" | "digits" | "letters" | "letters_uc" | "letters_lc"
12053 | "punctuation" | "punct"
12054 | "sentences" | "sents"
12055 | "paragraphs" | "paras" | "sections" | "sects"
12056 | "numbers" | "nums" | "graphemes" | "grs" | "columns" | "cols"
12057 | "trim" | "avg" | "stddev"
12058 | "squared" | "sq" | "square" | "cubed" | "cb" | "cube" | "expt" | "pow" | "pw"
12059 | "normalize" | "snake_case" | "camel_case" | "kebab_case"
12060 | "frequencies" | "freq" | "interleave" | "ddump" | "stringify" | "str" | "top"
12061 | "to_json" | "to_csv" | "to_toml" | "to_yaml" | "to_xml"
12062 | "to_html" | "to_markdown" | "to_table" | "xopen"
12063 | "from_json" | "from_csv" | "from_toml" | "from_yaml" | "from_xml"
12064 | "clip" | "clipboard" | "paste" | "pbcopy" | "pbpaste" | "preview"
12065 | "sparkline" | "spark" | "bar_chart" | "bars" | "flame" | "flamechart"
12066 | "histo" | "gauge" | "spinner" | "spinner_start" | "spinner_stop"
12067 | "to_hash" | "to_set"
12068 | "to_file" | "read_lines" | "append_file" | "write_json" | "read_json"
12069 | "tempfile" | "tempdir" | "list_count" | "list_size" | "size"
12070 | "clamp" | "grep_v" | "select_keys" | "pluck" | "glob_match" | "which_all"
12071 | "dedup" | "nth" | "tail" | "take" | "drop" | "tee" | "range"
12072 | "inc" | "dec" | "elapsed"
12073 | "files" | "filesf" | "f" | "fr" | "dirs" | "d" | "dr" | "sym_links"
12075 | "sockets" | "pipes" | "block_devices" | "char_devices" | "exe" | "executables"
12076 | "basename" | "dirname" | "fileparse" | "realpath" | "canonpath"
12077 | "copy" | "move" | "spurt" | "read_bytes" | "which"
12078 | "getcwd" | "touch" | "gethostname" | "uname"
12079 | "csv_read" | "csv_write" | "dataframe" | "sqlite"
12081 | "fetch" | "fetch_json" | "fetch_async" | "fetch_async_json"
12082 | "par_fetch" | "par_csv_read" | "par_pipeline"
12083 | "json_encode" | "json_decode" | "json_jq"
12084 | "http_request" | "serve" | "ssh"
12085 | "html_parse" | "css_select" | "xml_parse" | "xpath"
12086 | "smtp_send"
12087 | "net_interfaces" | "net_ipv4" | "net_ipv6" | "net_mac"
12088 | "net_public_ip" | "net_dns" | "net_reverse_dns"
12089 | "net_ping" | "net_port_open" | "net_ports_scan"
12090 | "net_latency" | "net_download" | "net_headers"
12091 | "net_dns_servers" | "net_gateway" | "net_whois" | "net_hostname"
12092 | "git_log" | "git_status" | "git_diff" | "git_branches"
12094 | "git_tags" | "git_blame" | "git_authors" | "git_files"
12095 | "git_show" | "git_root"
12096 | "audio_convert" | "audio_info" | "id3_read" | "id3_write"
12098 | "to_pdf" | "pdf_text" | "pdf_pages"
12100 | "toml_encode" | "toml_decode"
12102 | "yaml_encode" | "yaml_decode"
12103 | "xml_encode" | "xml_decode"
12104 | "md5" | "sha1" | "sha224" | "sha256" | "sha384" | "sha512"
12106 | "sha3_256" | "s3_256" | "sha3_512" | "s3_512"
12107 | "shake128" | "shake256"
12108 | "hmac_sha256" | "hmac_sha1" | "hmac_sha384" | "hmac_sha512" | "hmac_md5"
12109 | "uuid" | "crc32"
12110 | "blake2b" | "b2b" | "blake2s" | "b2s" | "blake3" | "b3"
12111 | "ripemd160" | "rmd160" | "md4"
12112 | "xxh32" | "xxhash32" | "xxh64" | "xxhash64" | "xxh3" | "xxhash3" | "xxh3_128" | "xxhash3_128"
12113 | "murmur3" | "murmur3_32" | "murmur3_128"
12114 | "siphash" | "siphash_keyed"
12115 | "hkdf_sha256" | "hkdf" | "hkdf_sha512"
12116 | "poly1305" | "poly1305_mac"
12117 | "base32_encode" | "b32e" | "base32_decode" | "b32d"
12118 | "base58_encode" | "b58e" | "base58_decode" | "b58d"
12119 | "totp" | "totp_generate" | "totp_verify" | "hotp" | "hotp_generate"
12120 | "aes_cbc_encrypt" | "aes_cbc_enc" | "aes_cbc_decrypt" | "aes_cbc_dec"
12121 | "blowfish_encrypt" | "bf_enc" | "blowfish_decrypt" | "bf_dec"
12122 | "des3_encrypt" | "3des_enc" | "tdes_enc" | "des3_decrypt" | "3des_dec" | "tdes_dec"
12123 | "twofish_encrypt" | "tf_enc" | "twofish_decrypt" | "tf_dec"
12124 | "camellia_encrypt" | "cam_enc" | "camellia_decrypt" | "cam_dec"
12125 | "cast5_encrypt" | "cast5_enc" | "cast5_decrypt" | "cast5_dec"
12126 | "salsa20" | "salsa20_encrypt" | "salsa20_decrypt"
12127 | "xsalsa20" | "xsalsa20_encrypt" | "xsalsa20_decrypt"
12128 | "secretbox" | "secretbox_seal" | "secretbox_open"
12129 | "nacl_box_keygen" | "box_keygen" | "nacl_box" | "nacl_box_seal" | "box_seal"
12130 | "nacl_box_open" | "box_open"
12131 | "qr_ascii" | "qr" | "qr_png" | "qr_svg"
12132 | "barcode_code128" | "code128" | "barcode_code39" | "code39"
12133 | "barcode_ean13" | "ean13" | "barcode_svg"
12134 | "argon2_hash" | "argon2" | "argon2_verify"
12135 | "bcrypt_hash" | "bcrypt" | "bcrypt_verify"
12136 | "scrypt_hash" | "scrypt" | "scrypt_verify"
12137 | "pbkdf2" | "pbkdf2_derive"
12138 | "random_bytes" | "randbytes" | "random_bytes_hex" | "randhex"
12139 | "aes_encrypt" | "aes_enc" | "aes_decrypt" | "aes_dec"
12140 | "chacha_encrypt" | "chacha_enc" | "chacha_decrypt" | "chacha_dec"
12141 | "rsa_keygen" | "rsa_encrypt" | "rsa_enc" | "rsa_decrypt" | "rsa_dec"
12142 | "rsa_encrypt_pkcs1" | "rsa_decrypt_pkcs1" | "rsa_sign" | "rsa_verify"
12143 | "ecdsa_p256_keygen" | "p256_keygen" | "ecdsa_p256_sign" | "p256_sign"
12144 | "ecdsa_p256_verify" | "p256_verify"
12145 | "ecdsa_p384_keygen" | "p384_keygen" | "ecdsa_p384_sign" | "p384_sign"
12146 | "ecdsa_p384_verify" | "p384_verify"
12147 | "ecdsa_secp256k1_keygen" | "secp256k1_keygen"
12148 | "ecdsa_secp256k1_sign" | "secp256k1_sign"
12149 | "ecdsa_secp256k1_verify" | "secp256k1_verify"
12150 | "ecdh_p256" | "p256_dh" | "ecdh_p384" | "p384_dh"
12151 | "ed25519_keygen" | "ed_keygen" | "ed25519_sign" | "ed_sign"
12152 | "ed25519_verify" | "ed_verify"
12153 | "x25519_keygen" | "x_keygen" | "x25519_dh" | "x_dh"
12154 | "base64_encode" | "base64_decode"
12155 | "hex_encode" | "hex_decode"
12156 | "url_encode" | "url_decode"
12157 | "gzip" | "gunzip" | "gz" | "ugz" | "zstd" | "zstd_decode" | "zst" | "uzst"
12158 | "brotli" | "br" | "brotli_decode" | "ubr"
12159 | "xz" | "lzma" | "xz_decode" | "unxz" | "unlzma"
12160 | "bzip2" | "bz2" | "bzip2_decode" | "bunzip2" | "ubz2"
12161 | "lz4" | "lz4_decode" | "unlz4"
12162 | "snappy" | "snp" | "snappy_decode" | "unsnappy"
12163 | "lzw" | "lzw_decode" | "unlzw"
12164 | "tar_create" | "tar" | "tar_extract" | "untar" | "tar_list"
12165 | "tar_gz_create" | "tgz" | "tar_gz_extract" | "untgz"
12166 | "zip_create" | "zip_archive" | "zip_extract" | "unzip_archive" | "zip_list"
12167 | "erf" | "erfc" | "gamma" | "tgamma" | "lgamma" | "ln_gamma"
12169 | "digamma" | "psi" | "beta_fn" | "lbeta" | "ln_beta"
12170 | "betainc" | "beta_reg" | "gammainc" | "gamma_li"
12171 | "gammaincc" | "gamma_ui" | "gammainc_reg" | "gamma_lr"
12172 | "gammaincc_reg" | "gamma_ur"
12173 | "datetime_utc" | "datetime_now_tz"
12175 | "datetime_format_tz" | "datetime_add_seconds"
12176 | "datetime_from_epoch"
12177 | "datetime_parse_rfc3339" | "datetime_parse_local"
12178 | "datetime_strftime"
12179 | "dateseq" | "dategrep" | "dateround" | "datesort"
12180 | "jwt_encode" | "jwt_decode" | "jwt_decode_unsafe"
12182 | "log_info" | "log_warn" | "log_error"
12184 | "log_debug" | "log_trace" | "log_json" | "log_level"
12185 | "async" | "spawn" | "trace" | "timer" | "bench"
12187 | "eval_timeout" | "retry" | "rate_limit" | "every"
12188 | "gen" | "watch"
12189 | "cache_clear" | "cache_exists" | "cache_stats" | "cacheview"
12191 | "assert_eq" | "assert_ne" | "assert_ok" | "assert_err"
12193 | "assert_true" | "assert_false"
12194 | "assert_gt" | "assert_lt" | "assert_ge" | "assert_le"
12195 | "assert_match" | "assert_contains" | "assert_near" | "assert_dies"
12196 | "test_run"
12197 | "mounts" | "du" | "du_tree" | "process_list"
12199 | "thread_count" | "pool_info" | "par_bench"
12200 | "stress_cpu" | "scpu" | "stress_mem" | "smem"
12202 | "stress_io" | "sio" | "stress_test" | "st"
12203 | "heat" | "fire" | "fire_and_forget" | "pin"
12204 | "slurp" | "cat" | "c" | "capture" | "pager" | "pg" | "less"
12206 | "stdin"
12207 | "__stryke_rust_compile"
12209 | "p" | "rev"
12211 | "even" | "odd" | "zero" | "nonzero"
12213 | "positive" | "pos_n" | "negative" | "neg_n"
12214 | "sign" | "negate" | "double" | "triple" | "half"
12215 | "identity" | "id"
12216 | "round" | "floor" | "ceil" | "ceiling" | "trunc" | "truncn"
12217 | "gcd" | "lcm" | "min2" | "max2"
12218 | "log2" | "log10" | "hypot"
12219 | "rad_to_deg" | "r2d" | "deg_to_rad" | "d2r"
12220 | "pow2" | "abs_diff"
12221 | "factorial" | "fact" | "fibonacci" | "fib"
12222 | "is_prime" | "is_square" | "is_power_of_two" | "is_pow2"
12223 | "cbrt" | "exp2" | "percent" | "pct" | "inverse"
12224 | "median" | "mode_val" | "variance"
12225 | "is_empty" | "is_blank" | "is_numeric"
12227 | "is_upper" | "is_lower" | "is_alpha" | "is_digit" | "is_alnum"
12228 | "is_space" | "is_whitespace"
12229 | "starts_with" | "sw" | "ends_with" | "ew" | "contains"
12230 | "capitalize" | "cap" | "swap_case" | "repeat"
12231 | "title_case" | "title" | "squish"
12232 | "pad_left" | "lpad" | "pad_right" | "rpad" | "center"
12233 | "truncate_at" | "shorten" | "reverse_str" | "rev_str"
12234 | "char_count" | "word_count" | "wc" | "line_count" | "lc_lines"
12235 | "is_array" | "is_arrayref" | "is_hash" | "is_hashref"
12237 | "is_code" | "is_coderef" | "is_ref"
12238 | "is_undef" | "is_defined" | "is_def"
12239 | "is_string" | "is_str" | "is_int" | "is_integer" | "is_float"
12240 | "invert" | "merge_hash"
12242 | "has_key" | "hk" | "has_any_key" | "has_all_keys"
12243 | "both" | "either" | "neither" | "xor_bool" | "bool_to_int" | "b2i"
12245 | "riffle" | "intersperse" | "every_nth"
12247 | "drop_n" | "take_n" | "rotate" | "swap_pairs"
12248 | "to_bin" | "bin_of" | "to_hex" | "hex_of" | "to_oct" | "oct_of"
12250 | "from_bin" | "from_hex" | "from_oct" | "to_base" | "from_base"
12251 | "bits_count" | "popcount" | "leading_zeros" | "lz"
12252 | "trailing_zeros" | "tz" | "bit_length" | "bitlen"
12253 | "bit_and" | "bit_or" | "bit_xor" | "bit_not"
12255 | "shift_left" | "shl" | "shift_right" | "shr"
12256 | "bit_set" | "bit_clear" | "bit_toggle" | "bit_test"
12257 | "c_to_f" | "f_to_c" | "c_to_k" | "k_to_c" | "f_to_k" | "k_to_f"
12259 | "miles_to_km" | "km_to_miles" | "miles_to_m" | "m_to_miles"
12261 | "feet_to_m" | "m_to_feet" | "inches_to_cm" | "cm_to_inches"
12262 | "yards_to_m" | "m_to_yards"
12263 | "kg_to_lbs" | "lbs_to_kg" | "g_to_oz" | "oz_to_g"
12265 | "stone_to_kg" | "kg_to_stone"
12266 | "bytes_to_kb" | "b_to_kb" | "kb_to_bytes" | "kb_to_b"
12268 | "bytes_to_mb" | "mb_to_bytes" | "bytes_to_gb" | "gb_to_bytes"
12269 | "kb_to_mb" | "mb_to_gb"
12270 | "bits_to_bytes" | "bytes_to_bits"
12271 | "seconds_to_minutes" | "s_to_m" | "minutes_to_seconds" | "m_to_s"
12273 | "seconds_to_hours" | "hours_to_seconds"
12274 | "seconds_to_days" | "days_to_seconds"
12275 | "minutes_to_hours" | "hours_to_minutes"
12276 | "hours_to_days" | "days_to_hours"
12277 | "is_leap_year" | "is_leap" | "days_in_month"
12279 | "month_name" | "month_short"
12280 | "weekday_name" | "weekday_short" | "quarter_of"
12281 | "now_ms" | "now_us" | "now_ns"
12283 | "unix_epoch" | "epoch" | "unix_epoch_ms" | "epoch_ms"
12284 | "rgb_to_hex" | "hex_to_rgb"
12286 | "ansi_red" | "ansi_green" | "ansi_yellow" | "ansi_blue"
12287 | "ansi_magenta" | "ansi_cyan" | "ansi_white" | "ansi_black"
12288 | "ansi_bold" | "ansi_dim" | "ansi_underline" | "ansi_reverse"
12289 | "strip_ansi"
12290 | "red" | "green" | "yellow" | "blue" | "magenta" | "purple" | "cyan"
12291 | "white" | "black" | "bold" | "dim" | "italic" | "underline"
12292 | "strikethrough" | "ansi_off" | "off" | "gray" | "grey"
12293 | "bright_red" | "bright_green" | "bright_yellow" | "bright_blue"
12294 | "bright_magenta" | "bright_cyan" | "bright_white"
12295 | "bg_red" | "bg_green" | "bg_yellow" | "bg_blue"
12296 | "bg_magenta" | "bg_cyan" | "bg_white" | "bg_black"
12297 | "red_bold" | "bold_red" | "green_bold" | "bold_green"
12298 | "yellow_bold" | "bold_yellow" | "blue_bold" | "bold_blue"
12299 | "magenta_bold" | "bold_magenta" | "cyan_bold" | "bold_cyan"
12300 | "white_bold" | "bold_white"
12301 | "blink" | "rapid_blink" | "hidden" | "overline"
12302 | "bg_bright_red" | "bg_bright_green" | "bg_bright_yellow" | "bg_bright_blue"
12303 | "bg_bright_magenta" | "bg_bright_cyan" | "bg_bright_white"
12304 | "rgb" | "bg_rgb" | "color256" | "c256" | "bg_color256" | "bg_c256"
12305 | "ipv4_to_int" | "int_to_ipv4"
12307 | "is_valid_ipv4" | "is_valid_ipv6" | "is_valid_email" | "is_valid_url"
12308 | "path_ext" | "path_stem" | "path_parent" | "path_join" | "path_split"
12310 | "strip_prefix" | "strip_suffix" | "ensure_prefix" | "ensure_suffix"
12311 | "const_fn" | "always_true" | "always_false"
12313 | "flip_args" | "first_arg" | "second_arg" | "last_arg"
12314 | "count_eq" | "count_ne" | "all_eq"
12316 | "all_distinct" | "all_unique" | "has_duplicates"
12317 | "sum_of" | "product_of" | "max_of" | "min_of" | "range_of"
12318 | "quote" | "single_quote" | "unquote"
12320 | "extract_between" | "ellipsis"
12321 | "coin_flip" | "dice_roll"
12323 | "random_int" | "random_float" | "random_bool"
12324 | "random_choice" | "random_between"
12325 | "random_string" | "random_alpha" | "random_digit"
12326 | "os_name" | "os_arch" | "num_cpus"
12328 | "pid" | "ppid" | "uid" | "gid"
12329 | "username" | "home_dir" | "temp_dir"
12330 | "mem_total" | "mem_free" | "mem_used"
12331 | "swap_total" | "swap_free" | "swap_used"
12332 | "disk_total" | "disk_free" | "disk_avail" | "disk_used"
12333 | "load_avg" | "sys_uptime" | "page_size"
12334 | "os_version" | "os_family" | "endianness" | "pointer_width"
12335 | "proc_mem" | "rss"
12336 | "transpose" | "unzip"
12338 | "run_length_encode" | "rle" | "run_length_decode" | "rld"
12339 | "sliding_pairs" | "consecutive_eq" | "flatten_deep"
12340 | "tan" | "asin" | "acos" | "atan"
12342 | "sinh" | "cosh" | "tanh" | "asinh" | "acosh" | "atanh"
12343 | "sqr" | "cube_fn"
12344 | "mod_op" | "ceil_div" | "floor_div"
12345 | "is_finite" | "is_infinite" | "is_inf" | "is_nan"
12346 | "degrees" | "radians"
12347 | "min_abs" | "max_abs"
12348 | "saturate" | "sat01" | "wrap_around"
12349 | "rot13" | "rot47" | "caesar_shift" | "reverse_words"
12351 | "count_vowels" | "count_consonants" | "is_vowel" | "is_consonant"
12352 | "first_word" | "last_word"
12353 | "left_str" | "head_str" | "right_str" | "tail_str" | "mid_str"
12354 | "lowercase" | "uppercase"
12355 | "pascal_case" | "pc_case"
12356 | "constant_case" | "upper_snake" | "dot_case" | "path_case"
12357 | "is_palindrome" | "hamming_distance"
12358 | "longest_common_prefix" | "lcp"
12359 | "ascii_ord" | "ascii_chr" | "count_char" | "indexes_of"
12360 | "replace_first" | "replace_all_str"
12361 | "contains_any" | "contains_all"
12362 | "starts_with_any" | "ends_with_any"
12363 | "is_pair" | "is_triple"
12365 | "is_sorted" | "is_asc" | "is_sorted_desc" | "is_desc"
12366 | "is_empty_arr" | "is_empty_hash"
12367 | "is_subset" | "is_superset" | "is_permutation"
12368 | "first_eq" | "last_eq"
12370 | "index_of" | "last_index_of" | "positions_of"
12371 | "batch" | "binary_search" | "bsearch" | "linear_search" | "lsearch"
12372 | "distinct_count" | "longest" | "shortest"
12373 | "array_union" | "list_union"
12374 | "array_intersection" | "list_intersection"
12375 | "array_difference" | "list_difference"
12376 | "symmetric_diff" | "group_of_n" | "chunk_n"
12377 | "repeat_list" | "cycle_n" | "random_sample" | "sample_n"
12378 | "pick_keys" | "pick" | "omit_keys" | "omit"
12380 | "map_keys_fn" | "map_values_fn"
12381 | "hash_size" | "hash_from_pairs" | "pairs_from_hash"
12382 | "hash_eq" | "keys_sorted" | "values_sorted" | "remove_keys"
12383 | "today" | "yesterday" | "tomorrow" | "is_weekend" | "is_weekday"
12385 | "json_pretty" | "json_minify" | "escape_json" | "json_escape"
12387 | "cmd_exists" | "env_get" | "env_has" | "env_keys"
12389 | "argc" | "script_name"
12390 | "has_stdin_tty" | "has_stdout_tty" | "has_stderr_tty"
12391 | "uuid_v4" | "nanoid" | "short_id" | "is_uuid" | "token"
12393 | "email_domain" | "email_local"
12395 | "url_host" | "url_path" | "url_query" | "url_scheme"
12396 | "file_size" | "fsize" | "file_mtime" | "mtime"
12398 | "file_atime" | "atime" | "file_ctime" | "ctime"
12399 | "is_symlink" | "is_readable" | "is_writable" | "is_executable"
12400 | "path_is_abs" | "path_is_rel"
12401 | "min_max" | "percentile" | "harmonic_mean" | "geometric_mean" | "zscore"
12403 | "sorted" | "sorted_desc" | "sorted_nums" | "sorted_by_length"
12404 | "reverse_list" | "list_reverse"
12405 | "without" | "without_nth" | "take_last" | "drop_last"
12406 | "pairwise" | "zipmap"
12407 | "format_bytes" | "human_bytes"
12408 | "format_duration" | "human_duration"
12409 | "format_number" | "group_number"
12410 | "format_percent" | "pad_number"
12411 | "spaceship" | "cmp_num" | "cmp_str"
12412 | "compare_versions" | "version_cmp"
12413 | "hash_insert" | "hash_update" | "hash_delete"
12414 | "matches_regex" | "re_match"
12415 | "count_regex_matches" | "regex_extract"
12416 | "regex_split_str" | "regex_replace_str"
12417 | "shuffle_chars" | "random_char" | "nth_word"
12418 | "head_lines" | "tail_lines" | "count_substring"
12419 | "is_valid_hex" | "hex_upper" | "hex_lower"
12420 | "ms_to_s" | "s_to_ms" | "ms_to_ns" | "ns_to_ms"
12421 | "us_to_ns" | "ns_to_us"
12422 | "liters_to_gallons" | "gallons_to_liters"
12423 | "liters_to_ml" | "ml_to_liters"
12424 | "cups_to_ml" | "ml_to_cups"
12425 | "newtons_to_lbf" | "lbf_to_newtons"
12426 | "joules_to_cal" | "cal_to_joules"
12427 | "watts_to_hp" | "hp_to_watts"
12428 | "pascals_to_psi" | "psi_to_pascals"
12429 | "bar_to_pascals" | "pascals_to_bar"
12430 | "match"
12432 | "fst" | "rest" | "rst" | "second" | "snd"
12434 | "last_clj" | "lastc" | "butlast" | "bl"
12435 | "ffirst" | "ffs" | "fnext" | "fne" | "nfirst" | "nfs" | "nnext" | "nne"
12436 | "cons" | "conj"
12437 | "peek_clj" | "pkc" | "pop_clj" | "popc"
12438 | "some" | "not_any" | "not_every"
12439 | "comp" | "compose" | "partial" | "constantly" | "complement" | "compl"
12440 | "fnil" | "juxt"
12441 | "memoize" | "memo" | "curry" | "once"
12442 | "deep_clone" | "dclone" | "deep_merge" | "dmerge" | "deep_equal" | "deq"
12443 | "iterate" | "iter" | "repeatedly" | "rptd" | "cycle" | "cyc"
12444 | "mapcat" | "mcat" | "keep" | "kp" | "remove_clj" | "remc"
12445 | "reductions" | "rdcs"
12446 | "partition_by" | "pby" | "partition_all" | "pall"
12447 | "split_at" | "spat" | "split_with" | "spw"
12448 | "assoc" | "dissoc" | "get_in" | "gin" | "assoc_in" | "ain" | "update_in" | "uin"
12449 | "into" | "empty_clj" | "empc" | "seq" | "vec_clj" | "vecc"
12450 | "apply" | "appl"
12451 | "divmod" | "dm" | "accumulate" | "accum" | "starmap" | "smap"
12453 | "zip_longest" | "zipl" | "zip_fill" | "zipf" | "combinations" | "comb" | "permutations" | "perm"
12454 | "cartesian_product" | "cprod" | "compress" | "cmpr" | "filterfalse" | "falf"
12455 | "islice" | "isl" | "chain_from" | "chfr" | "pairwise_iter" | "pwi"
12456 | "tee_iter" | "teei" | "groupby_iter" | "gbi"
12457 | "each_slice" | "eslice" | "each_cons" | "econs"
12458 | "one" | "none_match" | "nonem"
12459 | "find_index_fn" | "fidx" | "rindex_fn" | "ridx"
12460 | "minmax" | "mmx" | "minmax_by" | "mmxb"
12461 | "dig" | "values_at" | "vat" | "fetch_val" | "fv" | "slice_arr" | "sla"
12462 | "transform_keys" | "tkeys" | "transform_values" | "tvals"
12463 | "sum_by" | "sumb" | "uniq_by" | "uqb"
12464 | "flat_map_fn" | "fmf" | "then_fn" | "thfn" | "times_fn" | "timf"
12465 | "step" | "upto" | "downto"
12466 | "find_last" | "fndl" | "find_last_index" | "fndli"
12468 | "at_index" | "ati" | "replace_at" | "repa"
12469 | "to_sorted" | "tsrt" | "to_reversed" | "trev" | "to_spliced" | "tspl"
12470 | "flat_depth" | "fltd" | "fill_arr" | "filla" | "includes_val" | "incv"
12471 | "object_keys" | "okeys" | "object_values" | "ovals"
12472 | "object_entries" | "oents" | "object_from_entries" | "ofents"
12473 | "span_fn" | "spanf" | "break_fn" | "brkf" | "group_runs" | "gruns"
12475 | "nub" | "sort_on" | "srton"
12476 | "intersperse_val" | "isp" | "intercalate" | "ical"
12477 | "replicate_val" | "repv" | "elem_of" | "elof" | "not_elem" | "ntelm"
12478 | "lookup_assoc" | "lkpa" | "scanl" | "scanr" | "unfoldr" | "unfr"
12479 | "find_map" | "fndm" | "filter_map" | "fltm" | "fold_right" | "fldr"
12481 | "partition_either" | "peith" | "try_fold" | "tfld"
12482 | "map_while" | "mapw" | "inspect" | "insp"
12483 | "tally_by" | "talb" | "sole" | "chunk_while" | "chkw" | "count_while" | "cntw"
12485 | "insert_at" | "insa" | "delete_at" | "dela" | "update_at" | "upda"
12487 | "split_on" | "spon" | "words_from" | "wfrm" | "unwords" | "unwds"
12488 | "lines_from" | "lfrm" | "unlines" | "unlns"
12489 | "window_n" | "winn" | "adjacent_pairs" | "adjp"
12490 | "zip_all" | "zall" | "unzip_pairs" | "uzp"
12491 | "interpose" | "ipos" | "partition_n" | "partn"
12492 | "map_indexed" | "mapi" | "reduce_indexed" | "redi" | "filter_indexed" | "flti"
12493 | "group_by_fn" | "gbf" | "index_by" | "idxb" | "associate" | "assoc_fn"
12494 | "combinations_rep" | "combrep" | "inits" | "tails" | "subsequences" | "subseqs"
12496 | "nub_by" | "nubb" | "slice_when" | "slcw" | "slice_before" | "slcb" | "slice_after" | "slca"
12497 | "each_with_object" | "ewo" | "reduce_right" | "redr"
12498 | "is_sorted_by" | "issrtb" | "intersperse_with" | "ispw"
12499 | "running_reduce" | "runred" | "windowed_circular" | "wincirc"
12500 | "distinct_by" | "distb" | "average" | "mean" | "copy_within" | "cpyw"
12501 | "and_list" | "andl" | "or_list" | "orl" | "concat_map" | "cmap"
12502 | "elem_index" | "elidx" | "elem_indices" | "elidxs" | "find_indices" | "fndidxs"
12503 | "delete_first" | "delfst" | "delete_by" | "delby" | "insert_sorted" | "inssrt"
12504 | "union_list" | "unionl" | "intersect_list" | "intl"
12505 | "maximum_by" | "maxby" | "minimum_by" | "minby" | "batched" | "btch"
12506 | "match_all" | "mall" | "capture_groups" | "capg" | "is_match" | "ism"
12508 | "split_regex" | "splre" | "replace_regex" | "replre"
12509 | "is_ascii" | "isasc" | "to_ascii" | "toasc"
12510 | "char_at" | "chat" | "code_point_at" | "cpat" | "from_code_point" | "fcp"
12511 | "normalize_spaces" | "nrmsp" | "remove_whitespace" | "rmws"
12512 | "pluralize" | "plur" | "ordinalize" | "ordn"
12513 | "parse_int" | "pint" | "parse_float" | "pflt" | "parse_bool" | "pbool"
12514 | "levenshtein" | "lev" | "soundex" | "sdx" | "similarity" | "sim"
12515 | "common_prefix" | "cpfx" | "common_suffix" | "csfx"
12516 | "wrap_text" | "wrpt" | "dedent" | "ddt" | "indent" | "idt"
12517 | "lerp" | "inv_lerp" | "ilerp" | "smoothstep" | "smst" | "remap"
12519 | "dot_product" | "dotp" | "cross_product" | "crossp"
12520 | "matrix_mul" | "matmul" | "mm"
12521 | "magnitude" | "mag" | "normalize_vec" | "nrmv"
12522 | "distance" | "dist" | "manhattan_distance" | "mdist"
12523 | "covariance" | "cov" | "correlation" | "corr"
12524 | "iqr" | "quantile" | "qntl" | "clamp_int" | "clpi"
12525 | "in_range" | "inrng" | "wrap_range" | "wrprng"
12526 | "sum_squares" | "sumsq" | "rms" | "cumsum" | "csum" | "cumprod" | "cprod_acc" | "diff"
12527 | "add_days" | "addd" | "add_hours" | "addh" | "add_minutes" | "addm"
12529 | "diff_days" | "diffd" | "diff_hours" | "diffh"
12530 | "start_of_day" | "sod" | "end_of_day" | "eod"
12531 | "start_of_hour" | "soh" | "start_of_minute" | "som"
12532 | "urle" | "urld"
12534 | "html_encode" | "htmle" | "html_decode" | "htmld"
12535 | "adler32" | "adl32" | "fnv1a" | "djb2"
12536 | "is_credit_card" | "iscc" | "is_isbn10" | "isbn10" | "is_isbn13" | "isbn13"
12538 | "is_iban" | "isiban" | "is_hex_str" | "ishex" | "is_binary_str" | "isbin"
12539 | "is_octal_str" | "isoct" | "is_json" | "isjson" | "is_base64" | "isb64"
12540 | "is_semver" | "issv" | "is_slug" | "isslug" | "slugify" | "slug"
12541 | "mode_stat" | "mstat" | "sampn" | "weighted_sample" | "wsamp"
12543 | "shuffle_arr" | "shuf" | "argmax" | "amax" | "argmin" | "amin"
12544 | "argsort" | "asrt" | "rank" | "rnk" | "dense_rank" | "drnk"
12545 | "partition_point" | "ppt" | "lower_bound" | "lbound"
12546 | "upper_bound" | "ubound" | "equal_range" | "eqrng"
12547 | "matrix_add" | "madd" | "matrix_sub" | "msub" | "matrix_mult" | "mmult"
12549 | "matrix_scalar" | "mscal" | "matrix_identity" | "mident"
12550 | "matrix_zeros" | "mzeros" | "matrix_ones" | "mones"
12551 | "matrix_diag" | "mdiag" | "matrix_trace" | "mtrace"
12552 | "matrix_row" | "mrow" | "matrix_col" | "mcol"
12553 | "matrix_shape" | "mshape" | "matrix_det" | "mdet"
12554 | "matrix_scale" | "mat_scale" | "diagonal" | "diag"
12555 | "topological_sort" | "toposort" | "bfs_traverse" | "bfs"
12557 | "dfs_traverse" | "dfs" | "shortest_path_bfs" | "spbfs"
12558 | "connected_components_graph" | "ccgraph"
12559 | "has_cycle_graph" | "hascyc" | "is_bipartite_graph" | "isbip"
12560 | "is_ipv4_addr" | "isip4" | "is_ipv6_addr" | "isip6"
12562 | "is_mac_addr" | "ismac" | "is_port_num" | "isport"
12563 | "is_hostname_valid" | "ishost"
12564 | "is_iso_date" | "isisodt" | "is_iso_time" | "isisotm"
12565 | "is_iso_datetime" | "isisodtm"
12566 | "is_phone_num" | "isphone" | "is_us_zip" | "iszip"
12567 | "word_wrap_text" | "wwrap" | "center_text" | "ctxt"
12569 | "ljust_text" | "ljt" | "rjust_text" | "rjt" | "zfill_num" | "zfill"
12570 | "remove_all_str" | "rmall" | "replace_n_times" | "repln"
12571 | "find_all_indices" | "fndalli"
12572 | "text_between" | "txbtwn" | "text_before" | "txbef" | "text_after" | "txaft"
12573 | "text_before_last" | "txbefl" | "text_after_last" | "txaftl"
12574 | "is_even_num" | "iseven" | "is_odd_num" | "isodd"
12576 | "is_positive_num" | "ispos" | "is_negative_num" | "isneg"
12577 | "is_zero_num" | "iszero" | "is_whole_num" | "iswhole"
12578 | "log_with_base" | "logb" | "nth_root_of" | "nroot"
12579 | "frac_part" | "fracp" | "reciprocal_of" | "recip"
12580 | "copy_sign" | "cpsgn" | "fused_mul_add" | "fmadd"
12581 | "floor_mod" | "fmod" | "floor_div_op" | "fdivop"
12582 | "signum_of" | "sgnum" | "midpoint_of" | "midpt"
12583 | "longest_run" | "lrun" | "longest_increasing" | "linc"
12585 | "longest_decreasing" | "ldec" | "max_sum_subarray" | "maxsub"
12586 | "majority_element" | "majority" | "kth_largest" | "kthl"
12587 | "kth_smallest" | "kths" | "count_inversions" | "cinv"
12588 | "is_monotonic" | "ismono" | "equilibrium_index" | "eqidx"
12589 | "jaccard_index" | "jaccard" | "dice_coefficient" | "dicecoef"
12591 | "overlap_coefficient" | "overlapcoef"
12592 | "power_set" | "powerset" | "cartesian_power" | "cartpow"
12593 | "is_isogram" | "isiso" | "is_heterogram" | "ishet"
12595 | "hamdist" | "jaro_similarity" | "jarosim"
12596 | "longest_common_substring" | "lcsub"
12597 | "longest_common_subsequence" | "lcseq"
12598 | "count_words" | "wcount" | "count_lines" | "lcount"
12599 | "count_chars" | "ccount" | "count_bytes" | "bcount"
12600 | "binomial" | "binom" | "catalan" | "catn" | "pascal_row" | "pascrow"
12602 | "is_coprime" | "iscopr" | "euler_totient" | "etot"
12603 | "mobius" | "mob" | "is_squarefree" | "issqfr"
12604 | "digital_root" | "digroot" | "is_narcissistic" | "isnarc"
12605 | "is_harshad" | "isharsh" | "is_kaprekar" | "iskap"
12606 | "day_of_year" | "doy" | "week_of_year" | "woy"
12608 | "days_in_month_fn" | "daysinmo" | "is_valid_date" | "isvdate"
12609 | "age_in_years" | "ageyrs"
12610 | "when_true" | "when_false" | "if_else" | "clamp_fn"
12613 | "attempt" | "try_fn" | "safe_div" | "safe_mod" | "safe_sqrt" | "safe_log"
12614 | "juxt2" | "juxt3" | "tap_val" | "debug_val" | "converge"
12615 | "iterate_n" | "unfold" | "arity_of" | "is_callable"
12616 | "coalesce" | "default_to" | "fallback"
12617 | "apply_list" | "zip_apply" | "scan"
12618 | "keep_if" | "reject_if" | "group_consecutive"
12619 | "after_n" | "before_n" | "clamp_list" | "normalize_list" | "softmax"
12620
12621 | "matrix_multiply" | "mat_mul"
12625 | "identity_matrix" | "eye" | "zeros_matrix" | "zeros" | "ones_matrix" | "ones"
12626
12627
12628
12629 | "vec_normalize" | "unit_vec" | "vec_add" | "vec_sub" | "vec_scale"
12630 | "linspace" | "arange"
12631 | "re_test" | "re_find_all" | "re_groups" | "re_escape"
12633 | "re_split_limit" | "glob_to_regex" | "is_regex_valid"
12634 | "cwd" | "pwd_str" | "cpu_count" | "is_root" | "uptime_secs"
12636 | "env_pairs" | "env_set" | "env_remove" | "hostname_str" | "is_tty" | "signal_name"
12637 | "stack_new" | "queue_new" | "lru_new"
12639 | "counter" | "counter_most_common" | "defaultdict" | "ordered_set"
12640 | "bitset_new" | "bitset_set" | "bitset_test" | "bitset_clear"
12641 | "abs_ceil" | "abs_each" | "abs_floor" | "ceil_each" | "dec_each"
12643 | "double_each" | "floor_each" | "half_each" | "inc_each" | "length_each"
12644 | "negate_each" | "not_each" | "offset_each" | "reverse_each" | "round_each"
12645 | "scale_each" | "sqrt_each" | "square_each" | "to_float_each" | "to_int_each"
12646 | "trim_each" | "type_each" | "upcase_each" | "downcase_each" | "bool_each"
12647 | "avogadro" | "boltzmann" | "golden_ratio" | "gravity" | "ln10" | "ln2"
12649 | "planck" | "speed_of_light" | "sqrt2"
12650 | "bmi_calc" | "compound_interest" | "dew_point" | "discount_amount"
12652 | "force_mass_acc" | "freq_wavelength" | "future_value" | "haversine"
12653 | "heat_index" | "kinetic_energy" | "margin_price" | "markup_price"
12654 | "mortgage_payment" | "ohms_law_i" | "ohms_law_r" | "ohms_law_v"
12655 | "potential_energy" | "present_value" | "simple_interest" | "speed_distance_time"
12656 | "tax_amount" | "tip_amount" | "wavelength_freq" | "wind_chill"
12657 | "angle_between_deg" | "approx_eq" | "chebyshev_distance" | "copysign"
12659 | "cosine_similarity" | "cube_root" | "entropy" | "float_bits" | "fma"
12660 | "int_bits" | "jaccard_similarity" | "log_base" | "mae" | "mse" | "nth_root"
12661 | "r_squared" | "reciprocal" | "relu" | "rmse" | "rotate_point" | "round_to"
12662 | "sigmoid" | "signum" | "square_root"
12663 | "cubes_seq" | "fibonacci_seq" | "powers_of_seq" | "primes_seq"
12665 | "squares_seq" | "triangular_seq"
12666 | "alternate_case" | "angle_bracket" | "bracket" | "byte_length"
12668 | "bytes_to_hex_str" | "camel_words" | "char_length" | "chars_to_string"
12669 | "chomp_str" | "chop_str" | "filter_chars" | "from_csv_line" | "hex_to_bytes"
12670 | "insert_str" | "intersperse_char" | "ljust" | "map_chars" | "mirror_string"
12671 | "normalize_whitespace" | "only_alnum" | "only_alpha" | "only_ascii"
12672 | "only_digits" | "parenthesize" | "remove_str" | "repeat_string" | "rjust"
12673 | "sentence_case" | "string_count" | "string_sort" | "string_to_chars"
12674 | "string_unique_chars" | "substring" | "to_csv_line" | "trim_left" | "trim_right"
12675 | "xor_strings"
12676 | "adjacent_difference" | "append_elem" | "consecutive_pairs" | "contains_elem"
12678 | "count_elem" | "drop_every" | "duplicate_count" | "elem_at" | "find_first"
12679 | "first_elem" | "flatten_once" | "fold_left" | "from_digits" | "from_pairs"
12680 | "group_by_size" | "hash_filter_keys" | "hash_from_list" | "hash_map_values"
12681 | "hash_merge_deep" | "hash_to_list" | "hash_zip" | "head_n" | "histogram_bins"
12682 | "index_of_elem" | "init_list" | "interleave_lists" | "last_elem" | "least_common"
12683 | "list_compact" | "list_eq" | "list_flatten_deep" | "max_list" | "mean_list"
12684 | "min_list" | "mode_list" | "most_common" | "partition_two" | "prefix_sums"
12685 | "prepend" | "product_list" | "remove_at" | "remove_elem" | "remove_first_elem"
12686 | "repeat_elem" | "running_max" | "running_min" | "sample_one" | "scan_left"
12687 | "second_elem" | "span" | "suffix_sums" | "sum_list" | "tail_n" | "take_every"
12688 | "third_elem" | "to_array" | "to_pairs" | "trimmed_mean" | "unique_count_of"
12689 | "wrap_index" | "digits_of"
12690 | "all_match" | "any_match" | "is_between" | "is_blank_or_nil" | "is_divisible_by"
12692 | "is_email" | "is_even" | "is_falsy" | "is_fibonacci" | "is_hex_color"
12693 | "is_in_range" | "is_ipv4" | "is_multiple_of" | "is_negative" | "is_nil"
12694 | "is_nonzero" | "is_odd" | "is_perfect_square" | "is_positive" | "is_power_of"
12695 | "is_prefix" | "is_present" | "is_strictly_decreasing" | "is_strictly_increasing"
12696 | "is_suffix" | "is_triangular" | "is_truthy" | "is_url" | "is_whole" | "is_zero"
12697 | "count_digits" | "count_letters" | "count_lower" | "count_match"
12699 | "count_punctuation" | "count_spaces" | "count_upper" | "defined_count"
12700 | "empty_count" | "falsy_count" | "nonempty_count" | "numeric_count"
12701 | "truthy_count" | "undef_count"
12702 | "assert_type" | "between" | "clamp_each" | "die_if" | "die_unless"
12704 | "join_colons" | "join_commas" | "join_dashes" | "join_dots" | "join_lines"
12705 | "join_pipes" | "join_slashes" | "join_spaces" | "join_tabs" | "measure"
12706 | "max_float" | "min_float" | "noop_val" | "nop" | "pass" | "pred" | "succ"
12707 | "tap_debug" | "to_bool" | "to_float" | "to_int" | "to_string" | "void"
12708 | "range_exclusive" | "range_inclusive"
12709 | "aliquot_sum" | "autocorrelation" | "bell_number" | "cagr" | "coeff_of_variation"
12711 | "collatz_length" | "collatz_sequence" | "convolution" | "cross_entropy"
12712 | "depreciation_double" | "depreciation_linear" | "discount" | "divisors"
12713 | "epsilon" | "euclidean_distance" | "euler_number" | "exponential_moving_average"
12714 | "f64_max" | "f64_min" | "fft_magnitude" | "goldbach" | "i64_max" | "i64_min"
12715 | "kurtosis" | "linear_regression" | "look_and_say" | "lucas" | "luhn_check"
12716 | "mean_absolute_error" | "mean_squared_error" | "median_absolute_deviation"
12717 | "minkowski_distance" | "moving_average" | "multinomial" | "neg_inf" | "npv"
12718 | "num_divisors" | "partition_number" | "pascals_triangle" | "skewness"
12719 | "standard_error" | "subfactorial" | "sum_divisors" | "totient_sum"
12720 | "tribonacci" | "weighted_mean" | "winsorize"
12721 | "chi_square_stat" | "describe" | "five_number_summary"
12723 | "gini" | "gini_coefficient" | "lorenz_curve" | "outliers_iqr"
12724 | "percentile_rank" | "quartiles" | "sample_stddev" | "sample_variance"
12725 | "spearman_correlation" | "t_test_one_sample" | "t_test_two_sample"
12726 | "z_score" | "z_scores"
12727 | "abundant_numbers" | "deficient_numbers" | "is_abundant" | "is_deficient"
12729 | "is_pentagonal" | "is_perfect" | "is_smith" | "next_prime" | "nth_prime"
12730 | "pentagonal_number" | "perfect_numbers" | "prev_prime" | "prime_factors"
12731 | "prime_pi" | "primes_up_to" | "triangular_number" | "twin_primes"
12732 | "area_circle" | "area_ellipse" | "area_rectangle" | "area_trapezoid" | "area_triangle"
12734 | "bearing" | "circumference" | "cone_volume" | "cylinder_volume" | "heron_area"
12735 | "midpoint" | "perimeter_rectangle" | "perimeter_triangle" | "point_distance"
12736 | "polygon_area" | "slope" | "sphere_surface" | "sphere_volume" | "triangle_hypotenuse"
12737 | "angle_between" | "arc_length" | "bounding_box" | "centroid"
12739 | "circle_from_three_points" | "convex_hull" | "ellipse_perimeter"
12740 | "frustum_volume" | "haversine_distance" | "line_intersection"
12741 | "point_in_polygon" | "polygon_perimeter" | "pyramid_volume"
12742 | "reflect_point" | "scale_point" | "sector_area"
12743 | "torus_surface" | "torus_volume" | "translate_point"
12744 | "vector_angle" | "vector_cross" | "vector_dot" | "vector_magnitude" | "vector_normalize"
12745 | "avogadro_number" | "boltzmann_constant" | "electron_mass" | "elementary_charge"
12747 | "gravitational_constant" | "phi" | "pi" | "planck_constant" | "proton_mass"
12748 | "sol" | "tau"
12749 | "bac_estimate" | "bmi" | "break_even" | "margin" | "markup" | "roi" | "tax" | "tip"
12751 | "amortization_schedule" | "black_scholes_call" | "black_scholes_put"
12753 | "bond_price" | "bond_yield" | "capm" | "continuous_compound"
12754 | "discounted_payback" | "duration" | "irr"
12755 | "max_drawdown" | "modified_duration" | "nper" | "num_periods" | "payback_period"
12756 | "pmt" | "pv" | "rule_of_72" | "sharpe_ratio" | "sortino_ratio"
12757 | "wacc" | "xirr"
12758 | "acronym" | "atbash" | "bigrams" | "camel_to_snake" | "char_frequencies"
12760 | "chunk_string" | "collapse_whitespace" | "dedent_text" | "indent_text"
12761 | "initials" | "leetspeak" | "mask_string" | "ngrams" | "pig_latin"
12762 | "remove_consonants" | "remove_vowels" | "reverse_each_word" | "snake_to_camel"
12763 | "sort_words" | "string_distance" | "string_multiply" | "strip_html"
12764 | "trigrams" | "unique_words" | "word_frequencies" | "zalgo"
12765 | "braille_encode" | "double_metaphone" | "metaphone" | "morse_decode"
12767 | "morse_encode" | "nato_phonetic" | "phonetic_digit" | "subscript" | "superscript"
12768 | "to_emoji_num"
12769 | "int_to_roman" | "roman_add" | "roman_numeral_list" | "roman_to_int"
12771 | "base_convert" | "binary_to_gray" | "gray_code_sequence" | "gray_to_binary"
12773 | "ansi_256" | "ansi_truecolor" | "color_blend" | "color_complement"
12775 | "color_darken" | "color_distance" | "color_grayscale" | "color_invert"
12776 | "color_lighten" | "hsl_to_rgb" | "hsv_to_rgb" | "random_color"
12777 | "rgb_to_hsl" | "rgb_to_hsv"
12778 | "matrix_flatten" | "matrix_from_rows" | "matrix_hadamard" | "matrix_inverse"
12780 | "matrix_map" | "matrix_max" | "matrix_min" | "matrix_power" | "matrix_sum"
12781 | "matrix_transpose"
12782 | "binary_insert" | "bucket" | "clamp_array" | "group_consecutive_by"
12784 | "histogram" | "merge_sorted" | "next_permutation" | "normalize_array"
12785 | "normalize_range" | "peak_detect" | "range_compress" | "range_expand"
12786 | "reservoir_sample" | "run_length_decode_str" | "run_length_encode_str"
12787 | "zero_crossings"
12788 | "apply_window" | "bandpass_filter" | "cross_correlation" | "dft"
12790 | "downsample" | "energy" | "envelope" | "highpass_filter" | "idft"
12791 | "lowpass_filter" | "median_filter" | "normalize_signal" | "phase_spectrum"
12792 | "power_spectrum" | "resample" | "spectral_centroid" | "spectrogram" | "upsample"
12793 | "window_blackman" | "window_hamming" | "window_hann" | "window_kaiser"
12794 | "is_anagram" | "is_balanced_parens" | "is_control" | "is_numeric_string"
12796 | "is_pangram" | "is_printable" | "is_valid_cidr" | "is_valid_cron"
12797 | "is_valid_hex_color" | "is_valid_latitude" | "is_valid_longitude" | "is_valid_mime"
12798 | "eval_rpn" | "fizzbuzz" | "game_of_life_step" | "mandelbrot_char"
12800 | "sierpinski" | "tower_of_hanoi" | "truth_table"
12801 | "byte_size" | "degrees_to_compass" | "to_string_val" | "type_of"
12803 | "quadratic_roots" | "quadratic_discriminant" | "arithmetic_series"
12805 | "geometric_series" | "stirling_approx"
12806 | "double_factorial" | "rising_factorial" | "falling_factorial"
12807 | "gamma_approx" | "erf_approx" | "normal_pdf" | "normal_cdf"
12808 | "poisson_pmf" | "exponential_pdf" | "inverse_lerp"
12809 | "map_range"
12810 | "momentum" | "impulse" | "work" | "power_phys" | "torque" | "angular_velocity"
12812 | "centripetal_force" | "escape_velocity" | "orbital_velocity" | "orbital_period"
12813 | "gravitational_force" | "coulomb_force" | "electric_field" | "capacitance"
12814 | "capacitor_energy" | "inductor_energy" | "resonant_frequency"
12815 | "rc_time_constant" | "rl_time_constant" | "impedance_rlc"
12816 | "relativistic_mass" | "lorentz_factor" | "time_dilation" | "length_contraction"
12817 | "relativistic_energy" | "rest_energy" | "de_broglie_wavelength"
12818 | "photon_energy" | "photon_energy_wavelength" | "schwarzschild_radius"
12819 | "stefan_boltzmann" | "wien_displacement" | "ideal_gas_pressure" | "ideal_gas_volume"
12820 | "projectile_range" | "projectile_max_height" | "projectile_time"
12821 | "spring_force" | "spring_energy" | "pendulum_period" | "doppler_frequency"
12822 | "decibel_ratio" | "snells_law" | "brewster_angle" | "critical_angle"
12823 | "lens_power" | "thin_lens" | "magnification_lens"
12824 | "euler_mascheroni" | "apery_constant" | "feigenbaum_delta" | "feigenbaum_alpha"
12826 | "catalan_constant" | "khinchin_constant" | "glaisher_constant"
12827 | "plastic_number" | "silver_ratio" | "supergolden_ratio"
12828 | "vacuum_permittivity" | "vacuum_permeability" | "coulomb_constant"
12830 | "fine_structure_constant" | "rydberg_constant" | "bohr_radius"
12831 | "bohr_magneton" | "nuclear_magneton" | "stefan_boltzmann_constant"
12832 | "wien_constant" | "gas_constant" | "faraday_constant" | "neutron_mass"
12833 | "atomic_mass_unit" | "earth_mass" | "earth_radius" | "sun_mass" | "sun_radius"
12834 | "astronomical_unit" | "light_year" | "parsec" | "hubble_constant"
12835 | "planck_length" | "planck_time" | "planck_mass" | "planck_temperature"
12836 | "matrix_solve" | "msolve" | "solve"
12838 | "matrix_lu" | "mlu" | "matrix_qr" | "mqr"
12839 | "matrix_eigenvalues" | "meig" | "eigenvalues" | "eig"
12840 | "matrix_norm" | "mnorm" | "matrix_cond" | "mcond" | "cond"
12841 | "matrix_pinv" | "mpinv" | "pinv"
12842 | "matrix_cholesky" | "mchol" | "cholesky"
12843 | "matrix_det_general" | "mdetg" | "det"
12844 | "welch_ttest" | "welcht" | "paired_ttest" | "pairedt"
12846 | "cohen_d" | "cohend" | "anova_oneway" | "anova" | "anova1"
12847 | "spearman_corr" | "rho" | "kendall_tau" | "kendall" | "ktau"
12848 | "confidence_interval" | "ci"
12849 | "beta_pdf" | "betapdf" | "gamma_pdf" | "gammapdf"
12851 | "chi2_pdf" | "chi2pdf" | "chi_squared_pdf"
12852 | "t_pdf" | "tpdf" | "student_pdf"
12853 | "f_pdf" | "fpdf" | "fisher_pdf"
12854 | "lognormal_pdf" | "lnormpdf" | "weibull_pdf" | "weibpdf"
12855 | "cauchy_pdf" | "cauchypdf" | "laplace_pdf" | "laplacepdf"
12856 | "pareto_pdf" | "paretopdf"
12857 | "lagrange_interp" | "lagrange" | "linterp"
12859 | "cubic_spline" | "cspline" | "spline"
12860 | "poly_eval" | "polyval" | "polynomial_fit" | "polyfit"
12861 | "trapz" | "trapezoid" | "simpson" | "simps"
12863 | "numerical_diff" | "numdiff" | "diff_array"
12864 | "cumtrapz" | "cumulative_trapz"
12865 | "bisection" | "bisect" | "newton_method" | "newton" | "newton_raphson"
12867 | "golden_section" | "golden" | "gss"
12868 | "rk4" | "runge_kutta" | "rk4_ode" | "euler_ode" | "euler_method"
12870 | "dijkstra" | "shortest_path" | "bellman_ford" | "bellmanford"
12872 | "floyd_warshall" | "floydwarshall" | "apsp"
12873 | "prim_mst" | "mst" | "prim"
12874 | "cot" | "sec" | "csc" | "acot" | "asec" | "acsc" | "sinc" | "versin" | "versine"
12876 | "leaky_relu" | "lrelu" | "elu" | "selu" | "gelu"
12878 | "silu" | "swish" | "mish" | "softplus"
12879 | "hard_sigmoid" | "hardsigmoid" | "hard_swish" | "hardswish"
12880 | "bessel_j0" | "j0" | "bessel_j1" | "j1"
12882 | "lambert_w" | "lambertw" | "productlog"
12883 | "mod_exp" | "modexp" | "powmod"
12885 | "mod_inv" | "modinv" | "chinese_remainder" | "crt"
12886 | "miller_rabin" | "millerrabin" | "is_probable_prime"
12887 | "derangements" | "stirling2" | "stirling_second"
12889 | "bernoulli_number" | "bernoulli" | "harmonic_number" | "harmonic"
12890 | "drag_force" | "fdrag" | "ideal_gas" | "pv_nrt"
12892 | "bs_delta" | "bsdelta" | "option_delta"
12894 | "bs_gamma" | "bsgamma" | "option_gamma"
12895 | "bs_vega" | "bsvega" | "option_vega"
12896 | "bs_theta" | "bstheta" | "option_theta"
12897 | "bs_rho" | "bsrho" | "option_rho"
12898 | "bond_duration" | "mac_duration"
12899 | "dct" | "idct" | "goertzel" | "chirp" | "chirp_signal"
12901 | "base85_encode" | "b85e" | "ascii85_encode" | "a85e"
12903 | "base85_decode" | "b85d" | "ascii85_decode" | "a85d"
12904 | "pnorm" | "qnorm" | "pbinom" | "dbinom" | "ppois"
12906 | "punif" | "pexp" | "pweibull" | "plnorm" | "pcauchy"
12907 | "rbind" | "cbind"
12909 | "row_sums" | "rowSums" | "col_sums" | "colSums"
12910 | "row_means" | "rowMeans" | "col_means" | "colMeans"
12911 | "outer_product" | "outer" | "crossprod" | "tcrossprod"
12912 | "nrow" | "ncol" | "prop_table" | "proptable"
12913 | "cummax" | "cummin" | "scale_vec" | "scale"
12915 | "which_fn" | "tabulate"
12916 | "duplicated" | "duped" | "rev_vec"
12917 | "seq_fn" | "rep_fn" | "rep"
12918 | "cut_bins" | "cut" | "find_interval" | "findInterval"
12919 | "ecdf_fn" | "ecdf" | "density_est" | "density"
12920 | "embed_ts" | "embed"
12921 | "shapiro_test" | "shapiro" | "ks_test" | "ks"
12923 | "wilcox_test" | "wilcox" | "mann_whitney"
12924 | "prop_test" | "proptest" | "binom_test" | "binomtest"
12925 | "sapply" | "tapply" | "do_call" | "docall"
12927 | "kmeans" | "prcomp" | "pca"
12929 | "rnorm" | "runif" | "rexp" | "rbinom" | "rpois" | "rgeom"
12931 | "rgamma" | "rbeta" | "rchisq" | "rt" | "rf"
12932 | "rweibull" | "rlnorm" | "rcauchy"
12933 | "qunif" | "qexp" | "qweibull" | "qlnorm" | "qcauchy"
12935 | "pgamma" | "pbeta" | "pchisq" | "pt_cdf" | "pt" | "pf_cdf" | "pf"
12937 | "dgeom" | "dunif" | "dnbinom" | "dhyper"
12939 | "lowess" | "loess" | "approx_fn" | "approx"
12941 | "lm_fit" | "lm"
12943 | "qgamma" | "qbeta" | "qchisq" | "qt_fn" | "qt" | "qf_fn" | "qf"
12945 | "qbinom" | "qpois"
12946 | "acf_fn" | "acf" | "pacf_fn" | "pacf"
12948 | "diff_lag" | "diff_ts" | "ts_filter" | "filter_ts"
12949 | "predict_lm" | "predict" | "confint_lm" | "confint"
12951 | "cor_matrix" | "cor_mat" | "cov_matrix" | "cov_mat"
12953 | "mahalanobis" | "mahal" | "dist_matrix" | "dist_mat"
12954 | "hclust" | "cutree" | "weighted_var" | "wvar" | "cov2cor"
12955 | "scatter_svg" | "scatter_plot" | "line_svg" | "line_plot"
12957 | "plot_svg" | "hist_svg" | "histogram_svg"
12958 | "boxplot_svg" | "box_plot" | "bar_svg" | "barchart_svg"
12959 | "pie_svg" | "pie_chart" | "heatmap_svg" | "heatmap"
12960 | "donut_svg" | "donut" | "area_svg" | "area_chart"
12961 | "hbar_svg" | "hbar" | "radar_svg" | "radar" | "spider"
12962 | "candlestick_svg" | "candlestick" | "ohlc"
12963 | "violin_svg" | "violin" | "cor_heatmap" | "cor_matrix_svg"
12964 | "stacked_bar_svg" | "stacked_bar"
12965 | "wordcloud_svg" | "wordcloud" | "wcloud"
12966 | "treemap_svg" | "treemap"
12967 | "pvw"
12968 | "cyber_city" | "cyber_grid" | "cyber_rain" | "matrix_rain"
12970 | "cyber_glitch" | "glitch_text" | "cyber_banner" | "neon_banner"
12971 | "cyber_circuit" | "cyber_skull" | "cyber_eye"
12972 => Some(name),
12973 _ => None,
12974 }
12975 }
12976
12977 fn is_reserved_hash_name(name: &str) -> bool {
12980 matches!(
12981 name,
12982 "b" | "pc"
12983 | "e"
12984 | "a"
12985 | "d"
12986 | "c"
12987 | "p"
12988 | "all"
12989 | "stryke::builtins"
12990 | "stryke::perl_compats"
12991 | "stryke::extensions"
12992 | "stryke::aliases"
12993 | "stryke::descriptions"
12994 | "stryke::categories"
12995 | "stryke::primaries"
12996 | "stryke::all"
12997 )
12998 }
12999
13000 const RESERVED_FUNCTION_NAMES: &'static [&'static str] = &[
13005 "y",
13006 "tr",
13007 "s",
13008 "m",
13009 "q",
13010 "qq",
13011 "qw",
13012 "qx",
13013 "qr",
13014 "if",
13015 "unless",
13016 "while",
13017 "until",
13018 "for",
13019 "foreach",
13020 "given",
13021 "when",
13022 "else",
13023 "elsif",
13024 "do",
13025 "eval",
13026 "return",
13027 "last",
13028 "next",
13029 "redo",
13030 "goto",
13031 "my",
13032 "our",
13033 "local",
13034 "state",
13035 "sub",
13036 "fn",
13037 "class",
13038 "struct",
13039 "enum",
13040 "trait",
13041 "use",
13042 "no",
13043 "require",
13044 "package",
13045 "BEGIN",
13046 "END",
13047 "CHECK",
13048 "INIT",
13049 "UNITCHECK",
13050 "and",
13051 "or",
13052 "not",
13053 "x",
13054 "eq",
13055 "ne",
13056 "lt",
13057 "gt",
13058 "le",
13059 "ge",
13060 "cmp",
13061 ];
13062
13063 fn check_udf_shadows_builtin(&self, name: &str, line: usize) -> PerlResult<()> {
13064 if !name.contains("::") {
13066 if Self::RESERVED_FUNCTION_NAMES.contains(&name) {
13067 return Err(self.syntax_err(
13068 format!("`{name}` is a reserved word and cannot be used as a function name"),
13069 line,
13070 ));
13071 }
13072 if Self::is_known_bareword(name)
13073 || Self::is_try_builtin_name(name)
13074 || crate::list_builtins::is_list_builtin_name(name)
13075 {
13076 return Err(self.syntax_err(
13077 format!(
13078"`{name}` is a stryke builtin and cannot be redefined (this is not Perl 5; use `fn` not `sub`, or pass --compat)"
13079 ),
13080 line,
13081 ));
13082 }
13083 }
13084 Ok(())
13085 }
13086
13087 fn check_hash_shadows_reserved(&self, name: &str, line: usize) -> PerlResult<()> {
13090 if Self::is_reserved_hash_name(name) {
13091 return Err(self.syntax_err(
13092 format!(
13093"`%{name}` is a stryke reserved hash and cannot be redefined (this is not Perl 5; pass --compat for Perl 5 mode)"
13094 ),
13095 line,
13096 ));
13097 }
13098 Ok(())
13099 }
13100
13101 fn validate_hash_assignment(&self, value: &Expr, line: usize) -> PerlResult<()> {
13104 match &value.kind {
13105 ExprKind::Integer(_) | ExprKind::Float(_) => {
13106 return Err(self.syntax_err(
13107 "cannot assign scalar to hash — use %h = (key => value) or %h = %{$hashref}",
13108 line,
13109 ));
13110 }
13111 ExprKind::String(_) | ExprKind::InterpolatedString(_) | ExprKind::Bareword(_) => {
13112 return Err(self.syntax_err(
13113 "cannot assign string to hash — use %h = (key => value) or %h = %{$hashref}",
13114 line,
13115 ));
13116 }
13117 ExprKind::ArrayRef(_) => {
13118 return Err(self.syntax_err(
13119 "cannot assign arrayref to hash — use %h = @{$arrayref} for even-length list",
13120 line,
13121 ));
13122 }
13123 ExprKind::ScalarRef(inner) => {
13124 if matches!(inner.kind, ExprKind::ArrayVar(_)) {
13125 return Err(self.syntax_err(
13126 "cannot assign \\@array to hash — use %h = @array for even-length list",
13127 line,
13128 ));
13129 }
13130 if matches!(inner.kind, ExprKind::HashVar(_)) {
13131 return Err(self.syntax_err(
13132 "cannot assign \\%hash to hash — use %h = %other directly",
13133 line,
13134 ));
13135 }
13136 }
13137 ExprKind::HashRef(_) => {
13138 return Err(self.syntax_err(
13139 "cannot assign hashref to hash — use %h = %{$hashref} to dereference",
13140 line,
13141 ));
13142 }
13143 ExprKind::CodeRef { .. } => {
13144 return Err(self.syntax_err("cannot assign coderef to hash", line));
13145 }
13146 ExprKind::Undef => {
13147 return Err(
13148 self.syntax_err("cannot assign undef to hash — use %h = () to empty", line)
13149 );
13150 }
13151 ExprKind::List(items)
13152 if items.len() % 2 != 0
13153 && !items.iter().any(|e| {
13154 matches!(
13155 e.kind,
13156 ExprKind::ArrayVar(_)
13157 | ExprKind::HashVar(_)
13158 | ExprKind::FuncCall { .. }
13159 | ExprKind::Deref { .. }
13160 | ExprKind::ScalarVar(_)
13161 )
13162 }) =>
13163 {
13164 return Err(self.syntax_err(
13165 format!(
13166 "odd-length list ({} elements) in hash assignment — missing value for last key",
13167 items.len()
13168 ),
13169 line,
13170 ));
13171 }
13172 _ => {}
13173 }
13174 Ok(())
13175 }
13176
13177 fn validate_array_assignment(&self, value: &Expr, line: usize) -> PerlResult<()> {
13182 if let ExprKind::Undef = &value.kind {
13183 return Err(
13184 self.syntax_err("cannot assign undef to array — use @a = () to empty", line)
13185 );
13186 }
13187 Ok(())
13188 }
13189
13190 fn validate_scalar_assignment(&self, value: &Expr, line: usize) -> PerlResult<()> {
13193 if let ExprKind::List(items) = &value.kind {
13194 if items.len() > 1 {
13195 return Err(self.syntax_err(
13196 format!(
13197 "cannot assign {}-element list to scalar — Perl 5 silently takes last element; use ($x) = (list) or $x = $list[-1]",
13198 items.len()
13199 ),
13200 line,
13201 ));
13202 }
13203 }
13204 Ok(())
13205 }
13206
13207 fn validate_assignment(&self, target: &Expr, value: &Expr, line: usize) -> PerlResult<()> {
13209 if crate::compat_mode() {
13210 return Ok(());
13211 }
13212 match &target.kind {
13213 ExprKind::HashVar(_) => self.validate_hash_assignment(value, line),
13214 ExprKind::ArrayVar(_) => self.validate_array_assignment(value, line),
13215 ExprKind::ScalarVar(_) => self.validate_scalar_assignment(value, line),
13216 _ => Ok(()),
13217 }
13218 }
13219
13220 fn parse_block_or_bareword_cmp_block(&mut self) -> PerlResult<Block> {
13224 if matches!(self.peek(), Token::LBrace) {
13225 return self.parse_block();
13226 }
13227 let line = self.peek_line();
13228 if let Token::Ident(ref name) = self.peek().clone() {
13230 if matches!(
13231 self.peek_at(1),
13232 Token::Comma | Token::Semicolon | Token::RBrace | Token::Eof | Token::PipeForward
13233 ) {
13234 let name = name.clone();
13235 self.advance();
13236 let body = Expr {
13237 kind: ExprKind::FuncCall {
13238 name,
13239 args: vec![
13240 Expr {
13241 kind: ExprKind::ScalarVar("a".to_string()),
13242 line,
13243 },
13244 Expr {
13245 kind: ExprKind::ScalarVar("b".to_string()),
13246 line,
13247 },
13248 ],
13249 },
13250 line,
13251 };
13252 return Ok(vec![Statement::new(StmtKind::Expression(body), line)]);
13253 }
13254 }
13255 let expr = self.parse_assign_expr_stop_at_pipe()?;
13257 Ok(vec![Statement::new(StmtKind::Expression(expr), line)])
13258 }
13259
13260 fn parse_fan_optional_progress(
13262 &mut self,
13263 which: &'static str,
13264 ) -> PerlResult<Option<Box<Expr>>> {
13265 let line = self.peek_line();
13266 if self.eat(&Token::Comma) {
13267 match self.peek() {
13268 Token::Ident(ref kw)
13269 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) =>
13270 {
13271 self.advance();
13272 self.expect(&Token::FatArrow)?;
13273 return Ok(Some(Box::new(self.parse_assign_expr()?)));
13274 }
13275 _ => {
13276 return Err(self.syntax_err(
13277 format!("{which}: expected `progress => EXPR` after comma"),
13278 line,
13279 ));
13280 }
13281 }
13282 }
13283 if let Token::Ident(ref kw) = self.peek().clone() {
13284 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
13285 self.advance();
13286 self.expect(&Token::FatArrow)?;
13287 return Ok(Some(Box::new(self.parse_assign_expr()?)));
13288 }
13289 }
13290 Ok(None)
13291 }
13292
13293 fn parse_assign_expr_list_optional_progress(&mut self) -> PerlResult<(Expr, Option<Expr>)> {
13300 if self.in_pipe_rhs()
13306 && matches!(
13307 self.peek(),
13308 Token::Semicolon
13309 | Token::RBrace
13310 | Token::RParen
13311 | Token::Eof
13312 | Token::PipeForward
13313 | Token::Comma
13314 )
13315 {
13316 return Ok((self.pipe_placeholder_list(self.peek_line()), None));
13317 }
13318 let mut parts = vec![self.parse_assign_expr_stop_at_pipe()?];
13319 loop {
13320 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
13321 break;
13322 }
13323 if matches!(
13324 self.peek(),
13325 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof | Token::PipeForward
13326 ) {
13327 break;
13328 }
13329 if self.peek_is_postfix_stmt_modifier_keyword() {
13330 break;
13331 }
13332 if let Token::Ident(ref kw) = self.peek().clone() {
13333 if kw == "progress" && matches!(self.peek_at(1), Token::FatArrow) {
13334 self.advance();
13335 self.expect(&Token::FatArrow)?;
13336 let prog = self.parse_assign_expr_stop_at_pipe()?;
13337 return Ok((merge_expr_list(parts), Some(prog)));
13338 }
13339 }
13340 parts.push(self.parse_assign_expr_stop_at_pipe()?);
13341 }
13342 Ok((merge_expr_list(parts), None))
13343 }
13344
13345 fn parse_one_arg(&mut self) -> PerlResult<Expr> {
13346 if matches!(self.peek(), Token::LParen) {
13347 self.advance();
13348 let expr = self.parse_expression()?;
13349 self.expect(&Token::RParen)?;
13350 Ok(expr)
13351 } else {
13352 self.parse_assign_expr_stop_at_pipe()
13353 }
13354 }
13355
13356 fn parse_one_arg_or_default(&mut self) -> PerlResult<Expr> {
13357 if matches!(
13364 self.peek(),
13365 Token::Semicolon
13367 | Token::RBrace
13368 | Token::RParen
13369 | Token::RBracket
13370 | Token::Eof
13371 | Token::Comma
13372 | Token::FatArrow
13373 | Token::PipeForward
13374 | Token::Question
13376 | Token::Colon
13377 | Token::NumEq | Token::NumNe | Token::NumLt | Token::NumGt
13379 | Token::NumLe | Token::NumGe | Token::Spaceship
13380 | Token::StrEq | Token::StrNe | Token::StrLt | Token::StrGt
13381 | Token::StrLe | Token::StrGe | Token::StrCmp
13382 | Token::LogAnd | Token::LogOr | Token::LogNot
13384 | Token::LogAndWord | Token::LogOrWord | Token::LogNotWord
13385 | Token::DefinedOr
13386 | Token::Range | Token::RangeExclusive
13388 | Token::Assign | Token::PlusAssign | Token::MinusAssign
13390 | Token::MulAssign | Token::DivAssign | Token::ModAssign
13391 | Token::PowAssign | Token::DotAssign | Token::AndAssign
13392 | Token::OrAssign | Token::XorAssign | Token::DefinedOrAssign
13393 | Token::ShiftLeftAssign | Token::ShiftRightAssign
13394 | Token::BitAndAssign | Token::BitOrAssign
13395 ) {
13396 return Ok(Expr {
13397 kind: ExprKind::ScalarVar("_".into()),
13398 line: self.peek_line(),
13399 });
13400 }
13401 if matches!(self.peek(), Token::LParen) && matches!(self.peek_at(1), Token::RParen) {
13405 let line = self.peek_line();
13406 self.advance(); self.advance(); return Ok(Expr {
13409 kind: ExprKind::ScalarVar("_".into()),
13410 line,
13411 });
13412 }
13413 self.parse_one_arg()
13414 }
13415
13416 fn parse_one_arg_or_argv(&mut self) -> PerlResult<Expr> {
13418 let line = self.prev_line(); if matches!(self.peek(), Token::LParen) {
13420 self.advance();
13421 if matches!(self.peek(), Token::RParen) {
13422 self.advance();
13423 return Ok(Expr {
13424 kind: ExprKind::ArrayVar("_".into()),
13425 line: self.peek_line(),
13426 });
13427 }
13428 let expr = self.parse_expression()?;
13429 self.expect(&Token::RParen)?;
13430 return Ok(expr);
13431 }
13432 if matches!(
13434 self.peek(),
13435 Token::Semicolon
13436 | Token::RBrace
13437 | Token::RParen
13438 | Token::Eof
13439 | Token::Comma
13440 | Token::PipeForward
13441 ) || self.peek_line() > line
13442 {
13443 Ok(Expr {
13444 kind: ExprKind::ArrayVar("_".into()),
13445 line,
13446 })
13447 } else {
13448 self.parse_assign_expr()
13449 }
13450 }
13451
13452 fn parse_builtin_args(&mut self) -> PerlResult<Vec<Expr>> {
13453 if matches!(self.peek(), Token::LParen) {
13454 self.advance();
13455 let args = self.parse_arg_list()?;
13456 self.expect(&Token::RParen)?;
13457 Ok(args)
13458 } else if self.suppress_parenless_call > 0 && matches!(self.peek(), Token::Ident(_)) {
13459 Ok(vec![])
13462 } else {
13463 self.parse_list_until_terminator()
13464 }
13465 }
13466
13467 #[inline]
13471 fn fat_arrow_autoquote(&self, name: &str, line: usize) -> Option<Expr> {
13472 if matches!(self.peek(), Token::FatArrow) {
13473 Some(Expr {
13474 kind: ExprKind::String(name.to_string()),
13475 line,
13476 })
13477 } else {
13478 None
13479 }
13480 }
13481
13482 fn parse_hash_subscript_key(&mut self) -> PerlResult<Expr> {
13487 let line = self.peek_line();
13488 if let Token::Ident(ref k) = self.peek().clone() {
13489 if matches!(self.peek_at(1), Token::RBrace) {
13490 let s = k.clone();
13491 self.advance();
13492 return Ok(Expr {
13493 kind: ExprKind::String(s),
13494 line,
13495 });
13496 }
13497 }
13498 self.parse_expression()
13499 }
13500
13501 #[inline]
13503 fn peek_is_glob_par_progress_kw(&self) -> bool {
13504 matches!(self.peek(), Token::Ident(ref kw) if kw == "progress")
13505 && matches!(self.peek_at(1), Token::FatArrow)
13506 }
13507
13508 fn parse_pattern_list_until_rparen_or_progress(&mut self) -> PerlResult<Vec<Expr>> {
13510 let mut args = Vec::new();
13511 loop {
13512 if matches!(self.peek(), Token::RParen | Token::Eof) {
13513 break;
13514 }
13515 if self.peek_is_glob_par_progress_kw() {
13516 break;
13517 }
13518 args.push(self.parse_assign_expr()?);
13519 match self.peek() {
13520 Token::RParen => break,
13521 Token::Comma => {
13522 self.advance();
13523 if matches!(self.peek(), Token::RParen) {
13524 break;
13525 }
13526 if self.peek_is_glob_par_progress_kw() {
13527 break;
13528 }
13529 }
13530 _ => {
13531 return Err(self.syntax_err(
13532 "expected `,`, `)`, or `progress =>` after argument in `glob_par` / `par_sed`",
13533 self.peek_line(),
13534 ));
13535 }
13536 }
13537 }
13538 Ok(args)
13539 }
13540
13541 fn parse_pattern_list_glob_par_bare(&mut self) -> PerlResult<Vec<Expr>> {
13543 let mut args = Vec::new();
13544 loop {
13545 if matches!(
13546 self.peek(),
13547 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof
13548 ) {
13549 break;
13550 }
13551 if self.peek_is_postfix_stmt_modifier_keyword() {
13552 break;
13553 }
13554 if self.peek_is_glob_par_progress_kw() {
13555 break;
13556 }
13557 args.push(self.parse_assign_expr()?);
13558 if !self.eat(&Token::Comma) {
13559 break;
13560 }
13561 if self.peek_is_glob_par_progress_kw() {
13562 break;
13563 }
13564 }
13565 Ok(args)
13566 }
13567
13568 fn parse_glob_par_or_par_sed_args(&mut self) -> PerlResult<(Vec<Expr>, Option<Box<Expr>>)> {
13570 if matches!(self.peek(), Token::LParen) {
13571 self.advance();
13572 let args = self.parse_pattern_list_until_rparen_or_progress()?;
13573 let progress = if self.peek_is_glob_par_progress_kw() {
13574 self.advance();
13575 self.expect(&Token::FatArrow)?;
13576 Some(Box::new(self.parse_assign_expr()?))
13577 } else {
13578 None
13579 };
13580 self.expect(&Token::RParen)?;
13581 Ok((args, progress))
13582 } else {
13583 let args = self.parse_pattern_list_glob_par_bare()?;
13584 let progress = if self.peek_is_glob_par_progress_kw() {
13586 self.advance();
13587 self.expect(&Token::FatArrow)?;
13588 Some(Box::new(self.parse_assign_expr()?))
13589 } else {
13590 None
13591 };
13592 Ok((args, progress))
13593 }
13594 }
13595
13596 pub(crate) fn parse_arg_list(&mut self) -> PerlResult<Vec<Expr>> {
13597 let mut args = Vec::new();
13598 let saved_no_pf = self.no_pipe_forward_depth;
13602 self.no_pipe_forward_depth = 0;
13603 while !matches!(
13604 self.peek(),
13605 Token::RParen | Token::RBracket | Token::RBrace | Token::Eof
13606 ) {
13607 let arg = match self.parse_assign_expr() {
13608 Ok(e) => e,
13609 Err(err) => {
13610 self.no_pipe_forward_depth = saved_no_pf;
13611 return Err(err);
13612 }
13613 };
13614 args.push(arg);
13615 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
13616 break;
13617 }
13618 }
13619 self.no_pipe_forward_depth = saved_no_pf;
13620 Ok(args)
13621 }
13622
13623 pub(crate) fn parse_slice_arg_list(&mut self, is_hash: bool) -> PerlResult<Vec<Expr>> {
13632 let mut args = Vec::new();
13633 let saved_no_pf = self.no_pipe_forward_depth;
13634 self.no_pipe_forward_depth = 0;
13635 while !matches!(
13636 self.peek(),
13637 Token::RParen | Token::RBracket | Token::RBrace | Token::Eof
13638 ) {
13639 let arg = match self.parse_slice_arg(is_hash) {
13640 Ok(e) => e,
13641 Err(err) => {
13642 self.no_pipe_forward_depth = saved_no_pf;
13643 return Err(err);
13644 }
13645 };
13646 args.push(arg);
13647 if !self.eat(&Token::Comma) && !self.eat(&Token::FatArrow) {
13648 break;
13649 }
13650 }
13651 self.no_pipe_forward_depth = saved_no_pf;
13652 Ok(args)
13653 }
13654
13655 fn parse_slice_arg(&mut self, is_hash: bool) -> PerlResult<Expr> {
13657 let line = self.peek_line();
13658
13659 if matches!(self.peek(), Token::Colon) {
13661 self.advance();
13662 return self.finish_slice_range(None, false, is_hash, line);
13663 }
13664 if matches!(self.peek(), Token::PackageSep) {
13665 self.advance();
13666 return self.finish_slice_range(None, true, is_hash, line);
13667 }
13668
13669 self.suppress_colon_range = self.suppress_colon_range.saturating_add(1);
13672 let result = self.parse_slice_endpoint(is_hash);
13673 self.suppress_colon_range = self.suppress_colon_range.saturating_sub(1);
13674 let from_expr = result?;
13675
13676 if matches!(self.peek(), Token::Colon) {
13678 self.advance();
13679 return self.finish_slice_range(Some(Box::new(from_expr)), false, is_hash, line);
13680 }
13681 if matches!(self.peek(), Token::PackageSep) {
13682 self.advance();
13683 return self.finish_slice_range(Some(Box::new(from_expr)), true, is_hash, line);
13684 }
13685
13686 Ok(from_expr)
13687 }
13688
13689 fn finish_slice_range(
13696 &mut self,
13697 from: Option<Box<Expr>>,
13698 double: bool,
13699 is_hash: bool,
13700 line: usize,
13701 ) -> PerlResult<Expr> {
13702 let (to, step) = if double {
13703 let step_v = self.parse_slice_optional_endpoint(is_hash)?;
13705 (None, step_v)
13706 } else {
13707 let to_v = self.parse_slice_optional_endpoint(is_hash)?;
13709 let step_v = if matches!(self.peek(), Token::Colon) {
13710 self.advance();
13711 self.parse_slice_optional_endpoint(is_hash)?
13712 } else if matches!(self.peek(), Token::PackageSep) {
13713 return Err(
13714 self.syntax_err("Unexpected `::` after slice TO endpoint".to_string(), line)
13715 );
13716 } else {
13717 None
13718 };
13719 (to_v, step_v)
13720 };
13721
13722 if let (Some(f), Some(t)) = (from.as_ref(), to.as_ref()) {
13725 return Ok(Expr {
13726 kind: ExprKind::Range {
13727 from: f.clone(),
13728 to: t.clone(),
13729 exclusive: false,
13730 step,
13731 },
13732 line,
13733 });
13734 }
13735
13736 Ok(Expr {
13737 kind: ExprKind::SliceRange { from, to, step },
13738 line,
13739 })
13740 }
13741
13742 fn parse_slice_optional_endpoint(&mut self, is_hash: bool) -> PerlResult<Option<Box<Expr>>> {
13745 if matches!(
13746 self.peek(),
13747 Token::Colon
13748 | Token::PackageSep
13749 | Token::Comma
13750 | Token::RBracket
13751 | Token::RBrace
13752 | Token::Eof
13753 ) {
13754 return Ok(None);
13755 }
13756 self.suppress_colon_range = self.suppress_colon_range.saturating_add(1);
13757 let r = self.parse_slice_endpoint(is_hash);
13758 self.suppress_colon_range = self.suppress_colon_range.saturating_sub(1);
13759 Ok(Some(Box::new(r?)))
13760 }
13761
13762 fn parse_slice_endpoint(&mut self, is_hash: bool) -> PerlResult<Expr> {
13766 if is_hash {
13767 if let Token::Ident(name) = self.peek().clone() {
13768 if matches!(
13769 self.peek_at(1),
13770 Token::Colon
13771 | Token::PackageSep
13772 | Token::Comma
13773 | Token::RBracket
13774 | Token::RBrace
13775 ) {
13776 let line = self.peek_line();
13777 self.advance();
13778 return Ok(Expr {
13779 kind: ExprKind::String(name),
13780 line,
13781 });
13782 }
13783 }
13784 }
13785 self.parse_assign_expr()
13786 }
13787
13788 fn parse_method_arg_list_no_paren(&mut self) -> PerlResult<Vec<Expr>> {
13792 let mut args = Vec::new();
13793 let call_line = self.prev_line();
13794 loop {
13795 if args.is_empty() && matches!(self.peek(), Token::LBrace) {
13798 break;
13799 }
13800 if matches!(
13801 self.peek(),
13802 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof | Token::PipeForward
13803 ) {
13804 break;
13805 }
13806 if let Token::Ident(ref kw) = self.peek().clone() {
13807 if matches!(
13808 kw.as_str(),
13809 "if" | "unless" | "while" | "until" | "for" | "foreach"
13810 ) {
13811 break;
13812 }
13813 }
13814 if args.is_empty()
13817 && (self.peek_method_arg_infix_terminator() || matches!(self.peek(), Token::Comma))
13818 {
13819 break;
13820 }
13821 if args.is_empty() && self.peek_line() > call_line {
13824 break;
13825 }
13826 args.push(self.parse_assign_expr()?);
13827 if !self.eat(&Token::Comma) {
13828 break;
13829 }
13830 }
13831 Ok(args)
13832 }
13833
13834 fn peek_method_arg_infix_terminator(&self) -> bool {
13837 matches!(
13838 self.peek(),
13839 Token::Plus
13840 | Token::Minus
13841 | Token::Star
13842 | Token::Slash
13843 | Token::Percent
13844 | Token::Power
13845 | Token::Dot
13846 | Token::X
13847 | Token::NumEq
13848 | Token::NumNe
13849 | Token::NumLt
13850 | Token::NumGt
13851 | Token::NumLe
13852 | Token::NumGe
13853 | Token::Spaceship
13854 | Token::StrEq
13855 | Token::StrNe
13856 | Token::StrLt
13857 | Token::StrGt
13858 | Token::StrLe
13859 | Token::StrGe
13860 | Token::StrCmp
13861 | Token::LogAnd
13862 | Token::LogOr
13863 | Token::LogAndWord
13864 | Token::LogOrWord
13865 | Token::DefinedOr
13866 | Token::BitAnd
13867 | Token::BitOr
13868 | Token::BitXor
13869 | Token::ShiftLeft
13870 | Token::ShiftRight
13871 | Token::Range
13872 | Token::RangeExclusive
13873 | Token::BindMatch
13874 | Token::BindNotMatch
13875 | Token::Arrow
13876 | Token::Question
13878 | Token::Colon
13879 | Token::Assign
13881 | Token::PlusAssign
13882 | Token::MinusAssign
13883 | Token::MulAssign
13884 | Token::DivAssign
13885 | Token::ModAssign
13886 | Token::PowAssign
13887 | Token::DotAssign
13888 | Token::AndAssign
13889 | Token::OrAssign
13890 | Token::XorAssign
13891 | Token::DefinedOrAssign
13892 | Token::ShiftLeftAssign
13893 | Token::ShiftRightAssign
13894 | Token::BitAndAssign
13895 | Token::BitOrAssign
13896 )
13897 }
13898
13899 fn parse_list_until_terminator(&mut self) -> PerlResult<Vec<Expr>> {
13900 let mut args = Vec::new();
13901 let call_line = self.prev_line();
13906 loop {
13907 if matches!(
13908 self.peek(),
13909 Token::Semicolon | Token::RBrace | Token::RParen | Token::Eof | Token::PipeForward
13910 ) {
13911 break;
13912 }
13913 if let Token::Ident(ref kw) = self.peek().clone() {
13915 if matches!(
13916 kw.as_str(),
13917 "if" | "unless" | "while" | "until" | "for" | "foreach"
13918 ) {
13919 break;
13920 }
13921 }
13922 if args.is_empty() && self.peek_line() > call_line {
13929 break;
13930 }
13931 args.push(self.parse_assign_expr_stop_at_pipe()?);
13934 if !self.eat(&Token::Comma) {
13935 break;
13936 }
13937 }
13938 Ok(args)
13939 }
13940
13941 fn try_parse_hash_ref(&mut self) -> PerlResult<Vec<(Expr, Expr)>> {
13942 let mut pairs = Vec::new();
13943 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
13944 let line = self.peek_line();
13947 let key = if let Token::Ident(ref name) = self.peek().clone() {
13948 if matches!(self.peek_at(1), Token::FatArrow) {
13949 self.advance();
13950 Expr {
13951 kind: ExprKind::String(name.clone()),
13952 line,
13953 }
13954 } else {
13955 self.parse_assign_expr()?
13956 }
13957 } else {
13958 self.parse_assign_expr()?
13959 };
13960 if matches!(self.peek(), Token::RBrace | Token::Comma)
13964 && matches!(
13965 key.kind,
13966 ExprKind::HashVar(_)
13967 | ExprKind::Deref {
13968 kind: Sigil::Hash,
13969 ..
13970 }
13971 )
13972 {
13973 let sentinel_key = Expr {
13977 kind: ExprKind::String("__HASH_SPREAD__".into()),
13978 line,
13979 };
13980 pairs.push((sentinel_key, key));
13981 self.eat(&Token::Comma);
13982 continue;
13983 }
13984 if self.eat(&Token::FatArrow) || self.eat(&Token::Comma) {
13986 let val = self.parse_assign_expr()?;
13987 pairs.push((key, val));
13988 self.eat(&Token::Comma);
13989 } else {
13990 return Err(self.syntax_err("Expected => or , in hash ref", key.line));
13991 }
13992 }
13993 self.expect(&Token::RBrace)?;
13994 Ok(pairs)
13995 }
13996
13997 fn parse_hashref_pairs_until(&mut self, term: &Token) -> PerlResult<Vec<(Expr, Expr)>> {
14002 let mut pairs = Vec::new();
14003 while !matches!(&self.peek(), t if std::mem::discriminant(*t) == std::mem::discriminant(term))
14004 && !matches!(self.peek(), Token::Eof)
14005 {
14006 let line = self.peek_line();
14007 let key = if let Token::Ident(ref name) = self.peek().clone() {
14008 if matches!(self.peek_at(1), Token::FatArrow) {
14009 self.advance();
14010 Expr {
14011 kind: ExprKind::String(name.clone()),
14012 line,
14013 }
14014 } else {
14015 self.parse_assign_expr()?
14016 }
14017 } else {
14018 self.parse_assign_expr()?
14019 };
14020 if self.eat(&Token::FatArrow) || self.eat(&Token::Comma) {
14021 let val = self.parse_assign_expr()?;
14022 pairs.push((key, val));
14023 self.eat(&Token::Comma);
14024 } else {
14025 return Err(self.syntax_err("Expected => or , in hash ref", key.line));
14026 }
14027 }
14028 Ok(pairs)
14029 }
14030
14031 fn interp_chain_subscripts(
14037 &self,
14038 chars: &[char],
14039 i: &mut usize,
14040 mut base: Expr,
14041 line: usize,
14042 ) -> Expr {
14043 loop {
14044 let (after, requires_subscript) =
14046 if *i + 1 < chars.len() && chars[*i] == '-' && chars[*i + 1] == '>' {
14047 (*i + 2, true)
14048 } else {
14049 (*i, false)
14050 };
14051 if after >= chars.len() {
14052 break;
14053 }
14054 match chars[after] {
14055 '[' => {
14056 *i = after + 1;
14057 let mut idx_str = String::new();
14058 while *i < chars.len() && chars[*i] != ']' {
14059 idx_str.push(chars[*i]);
14060 *i += 1;
14061 }
14062 if *i < chars.len() {
14063 *i += 1;
14064 }
14065 let idx_expr = if let Some(rest) = idx_str.strip_prefix('$') {
14066 Expr {
14067 kind: ExprKind::ScalarVar(rest.to_string()),
14068 line,
14069 }
14070 } else if let Ok(n) = idx_str.parse::<i64>() {
14071 Expr {
14072 kind: ExprKind::Integer(n),
14073 line,
14074 }
14075 } else {
14076 Expr {
14077 kind: ExprKind::String(idx_str),
14078 line,
14079 }
14080 };
14081 base = Expr {
14082 kind: ExprKind::ArrowDeref {
14083 expr: Box::new(base),
14084 index: Box::new(idx_expr),
14085 kind: DerefKind::Array,
14086 },
14087 line,
14088 };
14089 }
14090 '{' => {
14091 *i = after + 1;
14092 let mut key = String::new();
14093 let mut depth = 1usize;
14094 while *i < chars.len() && depth > 0 {
14095 if chars[*i] == '{' {
14096 depth += 1;
14097 } else if chars[*i] == '}' {
14098 depth -= 1;
14099 if depth == 0 {
14100 break;
14101 }
14102 }
14103 key.push(chars[*i]);
14104 *i += 1;
14105 }
14106 if *i < chars.len() {
14107 *i += 1;
14108 }
14109 let key_expr = if let Some(rest) = key.strip_prefix('$') {
14110 Expr {
14111 kind: ExprKind::ScalarVar(rest.to_string()),
14112 line,
14113 }
14114 } else {
14115 Expr {
14116 kind: ExprKind::String(key),
14117 line,
14118 }
14119 };
14120 base = Expr {
14121 kind: ExprKind::ArrowDeref {
14122 expr: Box::new(base),
14123 index: Box::new(key_expr),
14124 kind: DerefKind::Hash,
14125 },
14126 line,
14127 };
14128 }
14129 _ => {
14130 if requires_subscript {
14131 }
14133 break;
14134 }
14135 }
14136 }
14137 base
14138 }
14139
14140 fn no_interop_check_scalar_var_name(&self, name: &str, line: usize) -> PerlResult<()> {
14144 if crate::no_interop_mode() && (name == "a" || name == "b") {
14145 return Err(self.syntax_err(
14146 format!(
14147 "stryke uses `$_0` / `$_1` instead of `${}` (--no-interop is active)",
14148 name
14149 ),
14150 line,
14151 ));
14152 }
14153 Ok(())
14154 }
14155
14156 fn parse_interpolated_string(&self, s: &str, line: usize) -> PerlResult<Expr> {
14157 let mut parts = Vec::new();
14159 let mut literal = String::new();
14160 let chars: Vec<char> = s.chars().collect();
14161 let mut i = 0;
14162
14163 'istr: while i < chars.len() {
14164 if chars[i] == LITERAL_DOLLAR_IN_DQUOTE {
14165 literal.push('$');
14166 i += 1;
14167 continue;
14168 }
14169 if chars[i] == '\\' && i + 1 < chars.len() && chars[i + 1] == '$' {
14171 literal.push('\\');
14172 i += 1;
14173 }
14175 if chars[i] == '$' && i + 1 < chars.len() {
14176 if !literal.is_empty() {
14177 parts.push(StringPart::Literal(std::mem::take(&mut literal)));
14178 }
14179 i += 1; while i < chars.len() && chars[i].is_whitespace() {
14182 i += 1;
14183 }
14184 if i >= chars.len() {
14185 return Err(self.syntax_err("Final $ should be \\$ or $name", line));
14186 }
14187 if chars[i] == '#' {
14189 i += 1;
14190 let mut sname = String::from("#");
14191 while i < chars.len()
14192 && (chars[i].is_alphanumeric() || chars[i] == '_' || chars[i] == ':')
14193 {
14194 sname.push(chars[i]);
14195 i += 1;
14196 }
14197 while i + 1 < chars.len() && chars[i] == ':' && chars[i + 1] == ':' {
14198 sname.push_str("::");
14199 i += 2;
14200 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
14201 sname.push(chars[i]);
14202 i += 1;
14203 }
14204 }
14205 self.no_interop_check_scalar_var_name(&sname, line)?;
14206 parts.push(StringPart::ScalarVar(sname));
14207 continue;
14208 }
14209 if chars[i] == '$' {
14213 let next_c = chars.get(i + 1).copied();
14214 let is_pid = match next_c {
14215 None => true,
14216 Some(c)
14217 if !c.is_ascii_digit() && !matches!(c, 'A'..='Z' | 'a'..='z' | '_') =>
14218 {
14219 true
14220 }
14221 _ => false,
14222 };
14223 if is_pid {
14224 parts.push(StringPart::ScalarVar("$$".to_string()));
14225 i += 1; continue;
14227 }
14228 i += 1; }
14230 if chars[i] == '{' {
14231 i += 1;
14237 let mut inner = String::new();
14238 let mut depth = 1usize;
14239 while i < chars.len() && depth > 0 {
14240 match chars[i] {
14241 '{' => depth += 1,
14242 '}' => {
14243 depth -= 1;
14244 if depth == 0 {
14245 break;
14246 }
14247 }
14248 _ => {}
14249 }
14250 inner.push(chars[i]);
14251 i += 1;
14252 }
14253 if i < chars.len() {
14254 i += 1; }
14256
14257 let trimmed = inner.trim();
14261 let is_expr = trimmed.starts_with('$')
14262 || trimmed.starts_with('\\')
14263 || trimmed.starts_with('@') || trimmed.starts_with('%') || trimmed.contains(['(', '+', '-', '*', '/', '.', '?', '&', '|']);
14266 let mut base: Expr = if is_expr {
14267 match parse_expression_from_str(trimmed, "<interp>") {
14271 Ok(e) => Expr {
14272 kind: ExprKind::Deref {
14273 expr: Box::new(e),
14274 kind: Sigil::Scalar,
14275 },
14276 line,
14277 },
14278 Err(_) => Expr {
14279 kind: ExprKind::ScalarVar(inner.clone()),
14280 line,
14281 },
14282 }
14283 } else {
14284 self.no_interop_check_scalar_var_name(&inner, line)?;
14286 Expr {
14287 kind: ExprKind::ScalarVar(inner),
14288 line,
14289 }
14290 };
14291
14292 base = self.interp_chain_subscripts(&chars, &mut i, base, line);
14296 parts.push(StringPart::Expr(base));
14297 } else if chars[i] == '^' {
14298 let mut name = String::from("^");
14300 i += 1;
14301 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
14302 name.push(chars[i]);
14303 i += 1;
14304 }
14305 if i < chars.len() && chars[i] == '{' {
14306 i += 1; let mut key = String::new();
14308 let mut depth = 1;
14309 while i < chars.len() && depth > 0 {
14310 if chars[i] == '{' {
14311 depth += 1;
14312 } else if chars[i] == '}' {
14313 depth -= 1;
14314 if depth == 0 {
14315 break;
14316 }
14317 }
14318 key.push(chars[i]);
14319 i += 1;
14320 }
14321 if i < chars.len() {
14322 i += 1;
14323 }
14324 let key_expr = if let Some(rest) = key.strip_prefix('$') {
14325 Expr {
14326 kind: ExprKind::ScalarVar(rest.to_string()),
14327 line,
14328 }
14329 } else {
14330 Expr {
14331 kind: ExprKind::String(key),
14332 line,
14333 }
14334 };
14335 parts.push(StringPart::Expr(Expr {
14336 kind: ExprKind::HashElement {
14337 hash: name,
14338 key: Box::new(key_expr),
14339 },
14340 line,
14341 }));
14342 } else if i < chars.len() && chars[i] == '[' {
14343 i += 1;
14344 let mut idx_str = String::new();
14345 while i < chars.len() && chars[i] != ']' {
14346 idx_str.push(chars[i]);
14347 i += 1;
14348 }
14349 if i < chars.len() {
14350 i += 1;
14351 }
14352 let idx_expr = if let Some(rest) = idx_str.strip_prefix('$') {
14353 Expr {
14354 kind: ExprKind::ScalarVar(rest.to_string()),
14355 line,
14356 }
14357 } else if let Ok(n) = idx_str.parse::<i64>() {
14358 Expr {
14359 kind: ExprKind::Integer(n),
14360 line,
14361 }
14362 } else {
14363 Expr {
14364 kind: ExprKind::String(idx_str),
14365 line,
14366 }
14367 };
14368 parts.push(StringPart::Expr(Expr {
14369 kind: ExprKind::ArrayElement {
14370 array: name,
14371 index: Box::new(idx_expr),
14372 },
14373 line,
14374 }));
14375 } else {
14376 self.no_interop_check_scalar_var_name(&name, line)?;
14377 parts.push(StringPart::ScalarVar(name));
14378 }
14379 } else if chars[i].is_alphabetic() || chars[i] == '_' {
14380 let mut name = String::new();
14381 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
14382 name.push(chars[i]);
14383 i += 1;
14384 }
14385 if name == "_" {
14387 while i < chars.len() && chars[i] == '<' {
14388 name.push('<');
14389 i += 1;
14390 }
14391 }
14392 self.no_interop_check_scalar_var_name(&name, line)?;
14397 let mut base = if i < chars.len() && chars[i] == '{' {
14402 i += 1; let mut key = String::new();
14405 let mut depth = 1;
14406 while i < chars.len() && depth > 0 {
14407 if chars[i] == '{' {
14408 depth += 1;
14409 } else if chars[i] == '}' {
14410 depth -= 1;
14411 if depth == 0 {
14412 break;
14413 }
14414 }
14415 key.push(chars[i]);
14416 i += 1;
14417 }
14418 if i < chars.len() {
14419 i += 1;
14420 } let key_expr = if let Some(rest) = key.strip_prefix('$') {
14422 Expr {
14423 kind: ExprKind::ScalarVar(rest.to_string()),
14424 line,
14425 }
14426 } else {
14427 Expr {
14428 kind: ExprKind::String(key),
14429 line,
14430 }
14431 };
14432 Expr {
14433 kind: ExprKind::HashElement {
14434 hash: name,
14435 key: Box::new(key_expr),
14436 },
14437 line,
14438 }
14439 } else if i < chars.len() && chars[i] == '[' {
14440 i += 1;
14442 let mut idx_str = String::new();
14443 while i < chars.len() && chars[i] != ']' {
14444 idx_str.push(chars[i]);
14445 i += 1;
14446 }
14447 if i < chars.len() {
14448 i += 1;
14449 }
14450 let idx_expr = if let Some(rest) = idx_str.strip_prefix('$') {
14451 Expr {
14452 kind: ExprKind::ScalarVar(rest.to_string()),
14453 line,
14454 }
14455 } else if let Ok(n) = idx_str.parse::<i64>() {
14456 Expr {
14457 kind: ExprKind::Integer(n),
14458 line,
14459 }
14460 } else {
14461 Expr {
14462 kind: ExprKind::String(idx_str),
14463 line,
14464 }
14465 };
14466 Expr {
14467 kind: ExprKind::ArrayElement {
14468 array: name,
14469 index: Box::new(idx_expr),
14470 },
14471 line,
14472 }
14473 } else {
14474 Expr {
14476 kind: ExprKind::ScalarVar(name),
14477 line,
14478 }
14479 };
14480
14481 base = self.interp_chain_subscripts(&chars, &mut i, base, line);
14485 parts.push(StringPart::Expr(base));
14486 } else if chars[i].is_ascii_digit() {
14487 if chars[i] == '0' {
14489 i += 1;
14490 if i < chars.len() && chars[i].is_ascii_digit() {
14491 return Err(self.syntax_err(
14492 "Numeric variables with more than one digit may not start with '0'",
14493 line,
14494 ));
14495 }
14496 parts.push(StringPart::ScalarVar("0".into()));
14497 } else {
14498 let start = i;
14499 while i < chars.len() && chars[i].is_ascii_digit() {
14500 i += 1;
14501 }
14502 parts.push(StringPart::ScalarVar(chars[start..i].iter().collect()));
14503 }
14504 } else {
14505 let c = chars[i];
14506 let probe = c.to_string();
14507 if Interpreter::is_special_scalar_name_for_get(&probe)
14508 || matches!(c, '\'' | '`')
14509 {
14510 i += 1;
14511 if i < chars.len() && chars[i] == '{' {
14513 i += 1; let mut key = String::new();
14515 let mut depth = 1;
14516 while i < chars.len() && depth > 0 {
14517 if chars[i] == '{' {
14518 depth += 1;
14519 } else if chars[i] == '}' {
14520 depth -= 1;
14521 if depth == 0 {
14522 break;
14523 }
14524 }
14525 key.push(chars[i]);
14526 i += 1;
14527 }
14528 if i < chars.len() {
14529 i += 1;
14530 } let key_expr = if let Some(rest) = key.strip_prefix('$') {
14532 Expr {
14533 kind: ExprKind::ScalarVar(rest.to_string()),
14534 line,
14535 }
14536 } else {
14537 Expr {
14538 kind: ExprKind::String(key),
14539 line,
14540 }
14541 };
14542 let mut base = Expr {
14543 kind: ExprKind::HashElement {
14544 hash: probe,
14545 key: Box::new(key_expr),
14546 },
14547 line,
14548 };
14549 base = self.interp_chain_subscripts(&chars, &mut i, base, line);
14550 parts.push(StringPart::Expr(base));
14551 } else {
14552 let mut base = Expr {
14554 kind: ExprKind::ScalarVar(probe),
14555 line,
14556 };
14557 base = self.interp_chain_subscripts(&chars, &mut i, base, line);
14558 if matches!(base.kind, ExprKind::ScalarVar(_)) {
14559 if let ExprKind::ScalarVar(name) = base.kind {
14561 self.no_interop_check_scalar_var_name(&name, line)?;
14562 parts.push(StringPart::ScalarVar(name));
14563 }
14564 } else {
14565 parts.push(StringPart::Expr(base));
14566 }
14567 }
14568 } else {
14569 literal.push('$');
14570 literal.push(c);
14571 i += 1;
14572 }
14573 }
14574 } else if chars[i] == '@' && i + 1 < chars.len() {
14575 let next = chars[i + 1];
14576 if next == '$' {
14578 if !literal.is_empty() {
14579 parts.push(StringPart::Literal(std::mem::take(&mut literal)));
14580 }
14581 i += 1; debug_assert_eq!(chars[i], '$');
14583 i += 1; while i < chars.len() && chars[i].is_whitespace() {
14585 i += 1;
14586 }
14587 if i >= chars.len() {
14588 return Err(self.syntax_err(
14589 "Expected variable or block after `@$` in double-quoted string",
14590 line,
14591 ));
14592 }
14593 let inner_expr = if chars[i] == '{' {
14594 i += 1;
14595 let start = i;
14596 let mut depth = 1usize;
14597 while i < chars.len() && depth > 0 {
14598 match chars[i] {
14599 '{' => depth += 1,
14600 '}' => {
14601 depth -= 1;
14602 if depth == 0 {
14603 break;
14604 }
14605 }
14606 _ => {}
14607 }
14608 i += 1;
14609 }
14610 if depth != 0 {
14611 return Err(self.syntax_err(
14612 "Unterminated `${ ... }` after `@` in double-quoted string",
14613 line,
14614 ));
14615 }
14616 let inner: String = chars[start..i].iter().collect();
14617 i += 1; parse_expression_from_str(inner.trim(), "-e")?
14619 } else {
14620 let mut name = String::new();
14621 if chars[i] == '^' {
14622 name.push('^');
14623 i += 1;
14624 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_')
14625 {
14626 name.push(chars[i]);
14627 i += 1;
14628 }
14629 } else {
14630 while i < chars.len()
14631 && (chars[i].is_alphanumeric()
14632 || chars[i] == '_'
14633 || chars[i] == ':')
14634 {
14635 name.push(chars[i]);
14636 i += 1;
14637 }
14638 while i + 1 < chars.len() && chars[i] == ':' && chars[i + 1] == ':' {
14639 name.push_str("::");
14640 i += 2;
14641 while i < chars.len()
14642 && (chars[i].is_alphanumeric() || chars[i] == '_')
14643 {
14644 name.push(chars[i]);
14645 i += 1;
14646 }
14647 }
14648 }
14649 if name.is_empty() {
14650 return Err(self.syntax_err(
14651 "Expected identifier after `@$` in double-quoted string",
14652 line,
14653 ));
14654 }
14655 Expr {
14656 kind: ExprKind::ScalarVar(name),
14657 line,
14658 }
14659 };
14660 parts.push(StringPart::Expr(Expr {
14661 kind: ExprKind::Deref {
14662 expr: Box::new(inner_expr),
14663 kind: Sigil::Array,
14664 },
14665 line,
14666 }));
14667 continue 'istr;
14668 }
14669 if next == '{' {
14670 if !literal.is_empty() {
14671 parts.push(StringPart::Literal(std::mem::take(&mut literal)));
14672 }
14673 i += 2; let start = i;
14675 let mut depth = 1usize;
14676 while i < chars.len() && depth > 0 {
14677 match chars[i] {
14678 '{' => depth += 1,
14679 '}' => {
14680 depth -= 1;
14681 if depth == 0 {
14682 break;
14683 }
14684 }
14685 _ => {}
14686 }
14687 i += 1;
14688 }
14689 if depth != 0 {
14690 return Err(
14691 self.syntax_err("Unterminated @{ ... } in double-quoted string", line)
14692 );
14693 }
14694 let inner: String = chars[start..i].iter().collect();
14695 i += 1; let inner_expr = parse_expression_from_str(inner.trim(), "-e")?;
14697 parts.push(StringPart::Expr(Expr {
14698 kind: ExprKind::Deref {
14699 expr: Box::new(inner_expr),
14700 kind: Sigil::Array,
14701 },
14702 line,
14703 }));
14704 continue 'istr;
14705 }
14706 if !(next.is_alphabetic() || next == '_' || next == '+' || next == '-') {
14707 literal.push(chars[i]);
14708 i += 1;
14709 } else {
14710 if !literal.is_empty() {
14711 parts.push(StringPart::Literal(std::mem::take(&mut literal)));
14712 }
14713 i += 1;
14714 let mut name = String::new();
14715 if i < chars.len() && (chars[i] == '+' || chars[i] == '-') {
14716 name.push(chars[i]);
14717 i += 1;
14718 } else {
14719 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
14720 name.push(chars[i]);
14721 i += 1;
14722 }
14723 while i + 1 < chars.len() && chars[i] == ':' && chars[i + 1] == ':' {
14724 name.push_str("::");
14725 i += 2;
14726 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_')
14727 {
14728 name.push(chars[i]);
14729 i += 1;
14730 }
14731 }
14732 }
14733 if i < chars.len() && chars[i] == '[' {
14734 i += 1;
14735 let start_inner = i;
14736 let mut depth = 1usize;
14737 while i < chars.len() && depth > 0 {
14738 match chars[i] {
14739 '[' => depth += 1,
14740 ']' => depth -= 1,
14741 _ => {}
14742 }
14743 if depth == 0 {
14744 let inner: String = chars[start_inner..i].iter().collect();
14745 i += 1; let indices = parse_slice_indices_from_str(inner.trim(), "-e")?;
14747 parts.push(StringPart::Expr(Expr {
14748 kind: ExprKind::ArraySlice {
14749 array: name.clone(),
14750 indices,
14751 },
14752 line,
14753 }));
14754 continue 'istr;
14755 }
14756 i += 1;
14757 }
14758 return Err(self.syntax_err(
14759 "Unterminated [ in array slice inside quoted string",
14760 line,
14761 ));
14762 }
14763 parts.push(StringPart::ArrayVar(name));
14764 }
14765 } else if chars[i] == '#'
14766 && i + 1 < chars.len()
14767 && chars[i + 1] == '{'
14768 && !crate::compat_mode()
14769 {
14770 if !literal.is_empty() {
14772 parts.push(StringPart::Literal(std::mem::take(&mut literal)));
14773 }
14774 i += 2; let mut inner = String::new();
14776 let mut depth = 1usize;
14777 while i < chars.len() && depth > 0 {
14778 match chars[i] {
14779 '{' => depth += 1,
14780 '}' => {
14781 depth -= 1;
14782 if depth == 0 {
14783 break;
14784 }
14785 }
14786 _ => {}
14787 }
14788 inner.push(chars[i]);
14789 i += 1;
14790 }
14791 if i < chars.len() {
14792 i += 1; }
14794 let expr = parse_block_from_str(inner.trim(), "-e", line)?;
14795 parts.push(StringPart::Expr(expr));
14796 } else {
14797 literal.push(chars[i]);
14798 i += 1;
14799 }
14800 }
14801 if !literal.is_empty() {
14802 parts.push(StringPart::Literal(literal));
14803 }
14804
14805 if parts.len() == 1 {
14806 if let StringPart::Literal(s) = &parts[0] {
14807 return Ok(Expr {
14808 kind: ExprKind::String(s.clone()),
14809 line,
14810 });
14811 }
14812 }
14813 if parts.is_empty() {
14814 return Ok(Expr {
14815 kind: ExprKind::String(String::new()),
14816 line,
14817 });
14818 }
14819
14820 Ok(Expr {
14821 kind: ExprKind::InterpolatedString(parts),
14822 line,
14823 })
14824 }
14825
14826 fn expr_to_overload_key(&self, e: &Expr) -> PerlResult<String> {
14827 match &e.kind {
14828 ExprKind::String(s) => Ok(s.clone()),
14829 _ => Err(self.syntax_err(
14830 "overload key must be a string literal (e.g. '\"\"' or '+')",
14831 e.line,
14832 )),
14833 }
14834 }
14835
14836 fn expr_to_overload_sub(&self, e: &Expr) -> PerlResult<String> {
14837 match &e.kind {
14838 ExprKind::String(s) => Ok(s.clone()),
14839 ExprKind::Integer(n) => Ok(n.to_string()),
14840 ExprKind::SubroutineRef(s) | ExprKind::SubroutineCodeRef(s) => Ok(s.clone()),
14841 _ => Err(self.syntax_err(
14842 "overload handler must be a string literal, number (e.g. fallback => 1), or \\&subname (method in current package)",
14843 e.line,
14844 )),
14845 }
14846 }
14847}
14848
14849fn merge_expr_list(parts: Vec<Expr>) -> Expr {
14850 if parts.len() == 1 {
14851 parts.into_iter().next().unwrap()
14852 } else {
14853 let line = parts.first().map(|e| e.line).unwrap_or(0);
14854 Expr {
14855 kind: ExprKind::List(parts),
14856 line,
14857 }
14858 }
14859}
14860
14861pub fn parse_expression_from_str(s: &str, file: &str) -> PerlResult<Expr> {
14863 let mut lexer = Lexer::new_with_file(s, file);
14864 let tokens = lexer.tokenize()?;
14865 let mut parser = Parser::new_with_file(tokens, file);
14866 let e = parser.parse_expression()?;
14867 if !parser.at_eof() {
14868 return Err(parser.syntax_err(
14869 "Extra tokens in embedded string expression",
14870 parser.peek_line(),
14871 ));
14872 }
14873 Ok(e)
14874}
14875
14876pub fn parse_block_from_str(s: &str, file: &str, line: usize) -> PerlResult<Expr> {
14878 let mut lexer = Lexer::new_with_file(s, file);
14879 let tokens = lexer.tokenize()?;
14880 let mut parser = Parser::new_with_file(tokens, file);
14881 let stmts = parser.parse_statements()?;
14882 let inner_line = stmts.first().map(|st| st.line).unwrap_or(line);
14883 let inner = Expr {
14884 kind: ExprKind::CodeRef {
14885 params: vec![],
14886 body: stmts,
14887 },
14888 line: inner_line,
14889 };
14890 Ok(Expr {
14891 kind: ExprKind::Do(Box::new(inner)),
14892 line,
14893 })
14894}
14895
14896pub fn parse_slice_indices_from_str(s: &str, file: &str) -> PerlResult<Vec<Expr>> {
14899 let mut lexer = Lexer::new_with_file(s, file);
14900 let tokens = lexer.tokenize()?;
14901 let mut parser = Parser::new_with_file(tokens, file);
14902 parser.parse_arg_list()
14903}
14904
14905pub fn parse_format_value_line(line: &str) -> PerlResult<Vec<Expr>> {
14906 let trimmed = line.trim();
14907 if trimmed.is_empty() {
14908 return Ok(vec![]);
14909 }
14910 let mut lexer = Lexer::new(trimmed);
14911 let tokens = lexer.tokenize()?;
14912 let mut parser = Parser::new(tokens);
14913 let mut exprs = Vec::new();
14914 loop {
14915 if parser.at_eof() {
14916 break;
14917 }
14918 exprs.push(parser.parse_assign_expr()?);
14920 if parser.eat(&Token::Comma) {
14921 continue;
14922 }
14923 if !parser.at_eof() {
14924 return Err(parser.syntax_err("Extra tokens in format value line", parser.peek_line()));
14925 }
14926 break;
14927 }
14928 Ok(exprs)
14929}
14930
14931#[cfg(test)]
14932mod tests {
14933 use super::*;
14934
14935 fn parse_ok(code: &str) -> Program {
14936 let mut lexer = Lexer::new(code);
14937 let tokens = lexer.tokenize().expect("tokenize");
14938 let mut parser = Parser::new(tokens);
14939 parser.parse_program().expect("parse")
14940 }
14941
14942 fn parse_err(code: &str) -> String {
14943 let mut lexer = Lexer::new(code);
14944 let tokens = match lexer.tokenize() {
14945 Ok(t) => t,
14946 Err(e) => return e.message,
14947 };
14948 let mut parser = Parser::new(tokens);
14949 parser.parse_program().unwrap_err().message
14950 }
14951
14952 #[test]
14953 fn parse_empty_program() {
14954 let p = parse_ok("");
14955 assert!(p.statements.is_empty());
14956 }
14957
14958 #[test]
14959 fn parse_semicolons_only() {
14960 let p = parse_ok(";;");
14961 assert!(p.statements.len() <= 3);
14962 }
14963
14964 #[test]
14965 fn parse_simple_scalar_assignment() {
14966 let p = parse_ok("$x = 1");
14967 assert_eq!(p.statements.len(), 1);
14968 }
14969
14970 #[test]
14971 fn parse_simple_array_assignment() {
14972 let p = parse_ok("@arr = (1, 2, 3)");
14973 assert_eq!(p.statements.len(), 1);
14974 }
14975
14976 #[test]
14977 fn parse_simple_hash_assignment() {
14978 let p = parse_ok("%h = (a => 1, b => 2)");
14979 assert_eq!(p.statements.len(), 1);
14980 }
14981
14982 #[test]
14983 fn parse_subroutine_decl() {
14984 let p = parse_ok("fn foo { 1 }");
14985 assert_eq!(p.statements.len(), 1);
14986 match &p.statements[0].kind {
14987 StmtKind::SubDecl { name, .. } => assert_eq!(name, "foo"),
14988 _ => panic!("expected SubDecl"),
14989 }
14990 }
14991
14992 #[test]
14993 fn parse_subroutine_with_prototype() {
14994 let p = parse_ok("fn foo ($$) { 1 }");
14995 assert_eq!(p.statements.len(), 1);
14996 match &p.statements[0].kind {
14997 StmtKind::SubDecl { prototype, .. } => {
14998 assert!(prototype.is_some());
14999 }
15000 _ => panic!("expected SubDecl"),
15001 }
15002 }
15003
15004 #[test]
15005 fn parse_anonymous_fn() {
15006 let p = parse_ok("my $f = fn { 1 }");
15007 assert_eq!(p.statements.len(), 1);
15008 }
15009
15010 #[test]
15011 fn parse_if_statement() {
15012 let p = parse_ok("if (1) { 2 }");
15013 assert_eq!(p.statements.len(), 1);
15014 matches!(&p.statements[0].kind, StmtKind::If { .. });
15015 }
15016
15017 #[test]
15018 fn parse_if_elsif_else() {
15019 let p = parse_ok("if (0) { 1 } elsif (1) { 2 } else { 3 }");
15020 assert_eq!(p.statements.len(), 1);
15021 }
15022
15023 #[test]
15024 fn parse_unless_statement() {
15025 let p = parse_ok("unless (0) { 1 }");
15026 assert_eq!(p.statements.len(), 1);
15027 }
15028
15029 #[test]
15030 fn parse_while_loop() {
15031 let p = parse_ok("while ($x) { $x-- }");
15032 assert_eq!(p.statements.len(), 1);
15033 }
15034
15035 #[test]
15036 fn parse_until_loop() {
15037 let p = parse_ok("until ($x) { $x++ }");
15038 assert_eq!(p.statements.len(), 1);
15039 }
15040
15041 #[test]
15042 fn parse_for_c_style() {
15043 let p = parse_ok("for (my $i=0; $i<10; $i++) { 1 }");
15044 assert_eq!(p.statements.len(), 1);
15045 }
15046
15047 #[test]
15048 fn parse_foreach_loop() {
15049 let p = parse_ok("foreach my $x (@arr) { 1 }");
15050 assert_eq!(p.statements.len(), 1);
15051 }
15052
15053 #[test]
15054 fn parse_loop_with_label() {
15055 let p = parse_ok("OUTER: for my $i (1..10) { last OUTER }");
15056 assert_eq!(p.statements.len(), 1);
15057 assert_eq!(p.statements[0].label.as_deref(), Some("OUTER"));
15058 }
15059
15060 #[test]
15061 fn parse_begin_block() {
15062 let p = parse_ok("BEGIN { 1 }");
15063 assert_eq!(p.statements.len(), 1);
15064 matches!(&p.statements[0].kind, StmtKind::Begin(_));
15065 }
15066
15067 #[test]
15068 fn parse_end_block() {
15069 let p = parse_ok("END { 1 }");
15070 assert_eq!(p.statements.len(), 1);
15071 matches!(&p.statements[0].kind, StmtKind::End(_));
15072 }
15073
15074 #[test]
15075 fn parse_package_statement() {
15076 let p = parse_ok("package Foo::Bar");
15077 assert_eq!(p.statements.len(), 1);
15078 match &p.statements[0].kind {
15079 StmtKind::Package { name } => assert_eq!(name, "Foo::Bar"),
15080 _ => panic!("expected Package"),
15081 }
15082 }
15083
15084 #[test]
15085 fn parse_use_statement() {
15086 let p = parse_ok("use strict");
15087 assert_eq!(p.statements.len(), 1);
15088 }
15089
15090 #[test]
15091 fn parse_no_statement() {
15092 let p = parse_ok("no warnings");
15093 assert_eq!(p.statements.len(), 1);
15094 }
15095
15096 #[test]
15097 fn parse_require_bareword() {
15098 let p = parse_ok("require Foo::Bar");
15099 assert_eq!(p.statements.len(), 1);
15100 }
15101
15102 #[test]
15103 fn parse_require_string() {
15104 let p = parse_ok(r#"require "foo.pl""#);
15105 assert_eq!(p.statements.len(), 1);
15106 }
15107
15108 #[test]
15109 fn parse_eval_block() {
15110 let p = parse_ok("eval { 1 }");
15111 assert_eq!(p.statements.len(), 1);
15112 }
15113
15114 #[test]
15115 fn parse_eval_string() {
15116 let p = parse_ok(r#"eval "1 + 2""#);
15117 assert_eq!(p.statements.len(), 1);
15118 }
15119
15120 #[test]
15121 fn parse_qw_word_list() {
15122 let p = parse_ok("my @a = qw(foo bar baz)");
15123 assert_eq!(p.statements.len(), 1);
15124 }
15125
15126 #[test]
15127 fn parse_q_string() {
15128 let p = parse_ok("my $s = q{hello}");
15129 assert_eq!(p.statements.len(), 1);
15130 }
15131
15132 #[test]
15133 fn parse_qq_string() {
15134 let p = parse_ok(r#"my $s = qq(hello $x)"#);
15135 assert_eq!(p.statements.len(), 1);
15136 }
15137
15138 #[test]
15139 fn parse_regex_match() {
15140 let p = parse_ok(r#"$x =~ /foo/"#);
15141 assert_eq!(p.statements.len(), 1);
15142 }
15143
15144 #[test]
15145 fn parse_regex_substitution() {
15146 let p = parse_ok(r#"$x =~ s/foo/bar/g"#);
15147 assert_eq!(p.statements.len(), 1);
15148 }
15149
15150 #[test]
15151 fn parse_transliterate() {
15152 let p = parse_ok(r#"$x =~ tr/a-z/A-Z/"#);
15153 assert_eq!(p.statements.len(), 1);
15154 }
15155
15156 #[test]
15157 fn parse_ternary_operator() {
15158 let p = parse_ok("my $x = $a ? 1 : 2");
15159 assert_eq!(p.statements.len(), 1);
15160 }
15161
15162 #[test]
15163 fn parse_arrow_method_call() {
15164 let p = parse_ok("$obj->method()");
15165 assert_eq!(p.statements.len(), 1);
15166 }
15167
15168 #[test]
15169 fn parse_arrow_deref_hash() {
15170 let p = parse_ok("$r->{key}");
15171 assert_eq!(p.statements.len(), 1);
15172 }
15173
15174 #[test]
15175 fn parse_arrow_deref_array() {
15176 let p = parse_ok("$r->[0]");
15177 assert_eq!(p.statements.len(), 1);
15178 }
15179
15180 #[test]
15181 fn parse_chained_arrow_deref() {
15182 let p = parse_ok("$r->{a}[0]{b}");
15183 assert_eq!(p.statements.len(), 1);
15184 }
15185
15186 #[test]
15187 fn parse_my_multiple_vars() {
15188 let p = parse_ok("my ($a, $b, $c) = (1, 2, 3)");
15189 assert_eq!(p.statements.len(), 1);
15190 }
15191
15192 #[test]
15193 fn parse_our_scalar() {
15194 let p = parse_ok("our $VERSION = '1.0'");
15195 assert_eq!(p.statements.len(), 1);
15196 }
15197
15198 #[test]
15199 fn parse_local_scalar() {
15200 let p = parse_ok("local $/ = undef");
15201 assert_eq!(p.statements.len(), 1);
15202 }
15203
15204 #[test]
15205 fn parse_state_variable() {
15206 let p = parse_ok("fn Test::counter { state $n = 0; $n++ }");
15207 assert_eq!(p.statements.len(), 1);
15208 }
15209
15210 #[test]
15211 fn parse_postfix_if() {
15212 let p = parse_ok("print 1 if $x");
15213 assert_eq!(p.statements.len(), 1);
15214 }
15215
15216 #[test]
15217 fn parse_postfix_unless() {
15218 let p = parse_ok("die 'error' unless $ok");
15219 assert_eq!(p.statements.len(), 1);
15220 }
15221
15222 #[test]
15223 fn parse_postfix_while() {
15224 let p = parse_ok("$x++ while $x < 10");
15225 assert_eq!(p.statements.len(), 1);
15226 }
15227
15228 #[test]
15229 fn parse_postfix_for() {
15230 let p = parse_ok("print for @arr");
15231 assert_eq!(p.statements.len(), 1);
15232 }
15233
15234 #[test]
15235 fn parse_last_next_redo() {
15236 let p = parse_ok("for (@a) { next if $_ < 0; last if $_ > 10 }");
15237 assert_eq!(p.statements.len(), 1);
15238 }
15239
15240 #[test]
15241 fn parse_return_statement() {
15242 let p = parse_ok("fn foo { return 42 }");
15243 assert_eq!(p.statements.len(), 1);
15244 }
15245
15246 #[test]
15247 fn parse_wantarray() {
15248 let p = parse_ok("fn foo { wantarray ? @a : $a }");
15249 assert_eq!(p.statements.len(), 1);
15250 }
15251
15252 #[test]
15253 fn parse_caller_builtin() {
15254 let p = parse_ok("my @c = caller");
15255 assert_eq!(p.statements.len(), 1);
15256 }
15257
15258 #[test]
15259 fn parse_ref_to_array() {
15260 let p = parse_ok("my $r = \\@arr");
15261 assert_eq!(p.statements.len(), 1);
15262 }
15263
15264 #[test]
15265 fn parse_ref_to_hash() {
15266 let p = parse_ok("my $r = \\%hash");
15267 assert_eq!(p.statements.len(), 1);
15268 }
15269
15270 #[test]
15271 fn parse_ref_to_scalar() {
15272 let p = parse_ok("my $r = \\$x");
15273 assert_eq!(p.statements.len(), 1);
15274 }
15275
15276 #[test]
15277 fn parse_deref_scalar() {
15278 let p = parse_ok("my $v = $$r");
15279 assert_eq!(p.statements.len(), 1);
15280 }
15281
15282 #[test]
15283 fn parse_deref_array() {
15284 let p = parse_ok("my @a = @$r");
15285 assert_eq!(p.statements.len(), 1);
15286 }
15287
15288 #[test]
15289 fn parse_deref_hash() {
15290 let p = parse_ok("my %h = %$r");
15291 assert_eq!(p.statements.len(), 1);
15292 }
15293
15294 #[test]
15295 fn parse_blessed_ref() {
15296 let p = parse_ok("bless $r, 'Foo'");
15297 assert_eq!(p.statements.len(), 1);
15298 }
15299
15300 #[test]
15301 fn parse_heredoc_basic() {
15302 let p = parse_ok("my $s = <<END;\nfoo\nEND");
15303 assert_eq!(p.statements.len(), 1);
15304 }
15305
15306 #[test]
15307 fn parse_heredoc_quoted() {
15308 let p = parse_ok("my $s = <<'END';\nfoo\nEND");
15309 assert_eq!(p.statements.len(), 1);
15310 }
15311
15312 #[test]
15313 fn parse_do_block() {
15314 let p = parse_ok("my $x = do { 1 + 2 }");
15315 assert_eq!(p.statements.len(), 1);
15316 }
15317
15318 #[test]
15319 fn parse_do_file() {
15320 let p = parse_ok(r#"do "foo.pl""#);
15321 assert_eq!(p.statements.len(), 1);
15322 }
15323
15324 #[test]
15325 fn parse_map_expression() {
15326 let p = parse_ok("my @b = map { $_ * 2 } @a");
15327 assert_eq!(p.statements.len(), 1);
15328 }
15329
15330 #[test]
15331 fn parse_grep_expression() {
15332 let p = parse_ok("my @b = grep { $_ > 0 } @a");
15333 assert_eq!(p.statements.len(), 1);
15334 }
15335
15336 #[test]
15337 fn parse_sort_expression() {
15338 let p = parse_ok("my @b = sort { $a <=> $b } @a");
15339 assert_eq!(p.statements.len(), 1);
15340 }
15341
15342 #[test]
15343 fn parse_pipe_forward() {
15344 let p = parse_ok("@a |> map { $_ * 2 }");
15345 assert_eq!(p.statements.len(), 1);
15346 }
15347
15348 #[test]
15349 fn parse_expression_from_str_simple() {
15350 let e = parse_expression_from_str("$x + 1", "-e").unwrap();
15351 assert!(matches!(e.kind, ExprKind::BinOp { .. }));
15352 }
15353
15354 #[test]
15355 fn parse_expression_from_str_extra_tokens_error() {
15356 let err = parse_expression_from_str("$x; $y", "-e").unwrap_err();
15357 assert!(err.message.contains("Extra tokens"));
15358 }
15359
15360 #[test]
15361 fn parse_slice_indices_from_str_basic() {
15362 let indices = parse_slice_indices_from_str("0, 1, 2", "-e").unwrap();
15363 assert_eq!(indices.len(), 3);
15364 }
15365
15366 #[test]
15367 fn parse_format_value_line_empty() {
15368 let exprs = parse_format_value_line("").unwrap();
15369 assert!(exprs.is_empty());
15370 }
15371
15372 #[test]
15373 fn parse_format_value_line_single() {
15374 let exprs = parse_format_value_line("$x").unwrap();
15375 assert_eq!(exprs.len(), 1);
15376 }
15377
15378 #[test]
15379 fn parse_format_value_line_multiple() {
15380 let exprs = parse_format_value_line("$a, $b, $c").unwrap();
15381 assert_eq!(exprs.len(), 3);
15382 }
15383
15384 #[test]
15385 fn parse_unclosed_brace_error() {
15386 let err = parse_err("fn foo {");
15387 assert!(!err.is_empty());
15388 }
15389
15390 #[test]
15391 fn parse_unclosed_paren_error() {
15392 let err = parse_err("print (1, 2");
15393 assert!(!err.is_empty());
15394 }
15395
15396 #[test]
15397 fn parse_invalid_statement_error() {
15398 let err = parse_err("???");
15399 assert!(!err.is_empty());
15400 }
15401
15402 #[test]
15403 fn merge_expr_list_single() {
15404 let e = Expr {
15405 kind: ExprKind::Integer(1),
15406 line: 1,
15407 };
15408 let merged = merge_expr_list(vec![e.clone()]);
15409 matches!(merged.kind, ExprKind::Integer(1));
15410 }
15411
15412 #[test]
15413 fn merge_expr_list_multiple() {
15414 let e1 = Expr {
15415 kind: ExprKind::Integer(1),
15416 line: 1,
15417 };
15418 let e2 = Expr {
15419 kind: ExprKind::Integer(2),
15420 line: 1,
15421 };
15422 let merged = merge_expr_list(vec![e1, e2]);
15423 matches!(merged.kind, ExprKind::List(_));
15424 }
15425}