Skip to main content

stryke/
fmt.rs

1//! Pretty-print parsed Perl back to source (`stryke --fmt`).
2//! Regenerate with `python3 tools/gen_fmt.py` after `ast.rs` changes.
3
4#![allow(unused_variables)] // generated `match` arms name fields not always used
5
6use crate::ast::*;
7
8const INDENT: &str = "    ";
9
10/// Format a whole program as Perl-like source.
11pub fn format_program(p: &Program) -> String {
12    p.statements
13        .iter()
14        .map(|s| format_statement_indent(s, 0))
15        .collect::<Vec<_>>()
16        .join("\n")
17}
18
19pub(crate) fn format_sub_sig_param(p: &SubSigParam) -> String {
20    use crate::ast::MatchArrayElem;
21    match p {
22        SubSigParam::Scalar(name, ty, default) => {
23            let mut s = format!("${}", name);
24            if let Some(t) = ty {
25                s.push_str(": ");
26                s.push_str(&t.display_name());
27            }
28            if let Some(d) = default {
29                s.push_str(" = ");
30                s.push_str(&format_expr(d));
31            }
32            s
33        }
34        SubSigParam::Array(name, default) => {
35            let mut s = format!("@{}", name);
36            if let Some(d) = default {
37                s.push_str(" = ");
38                s.push_str(&format_expr(d));
39            }
40            s
41        }
42        SubSigParam::Hash(name, default) => {
43            let mut s = format!("%{}", name);
44            if let Some(d) = default {
45                s.push_str(" = ");
46                s.push_str(&format_expr(d));
47            }
48            s
49        }
50        SubSigParam::ArrayDestruct(elems) => {
51            let inner = elems
52                .iter()
53                .map(|x| match x {
54                    MatchArrayElem::Expr(e) => format_expr(e),
55                    MatchArrayElem::CaptureScalar(name) => format!("${}", name),
56                    MatchArrayElem::Rest => "*".to_string(),
57                    MatchArrayElem::RestBind(name) => format!("@{}", name),
58                })
59                .collect::<Vec<_>>()
60                .join(", ");
61            format!("[{inner}]")
62        }
63        SubSigParam::HashDestruct(pairs) => {
64            let inner = pairs
65                .iter()
66                .map(|(k, v)| format!("{} => ${}", k, v))
67                .collect::<Vec<_>>()
68                .join(", ");
69            format!("{{ {inner} }}")
70        }
71    }
72}
73
74#[allow(dead_code)]
75fn format_statement(s: &Statement) -> String {
76    format_statement_indent(s, 0)
77}
78
79fn format_statement_indent(s: &Statement, depth: usize) -> String {
80    let prefix = INDENT.repeat(depth);
81    let lab = s
82        .label
83        .as_ref()
84        .map(|l| format!("{}: ", l))
85        .unwrap_or_default();
86    let body = match &s.kind {
87        StmtKind::Expression(e) => format_expr(e),
88        StmtKind::If {
89            condition,
90            body,
91            elsifs,
92            else_block,
93        } => {
94            let mut s = format!(
95                "if ({}) {{\n{}\n{}}}",
96                format_expr(condition),
97                format_block_indent(body, depth + 1),
98                prefix
99            );
100            for (c, b) in elsifs {
101                s.push_str(&format!(
102                    " elsif ({}) {{\n{}\n{}}}",
103                    format_expr(c),
104                    format_block_indent(b, depth + 1),
105                    prefix
106                ));
107            }
108            if let Some(eb) = else_block {
109                s.push_str(&format!(
110                    " else {{\n{}\n{}}}",
111                    format_block_indent(eb, depth + 1),
112                    prefix
113                ));
114            }
115            s
116        }
117        StmtKind::Unless {
118            condition,
119            body,
120            else_block,
121        } => {
122            let mut s = format!(
123                "unless ({}) {{\n{}\n{}}}",
124                format_expr(condition),
125                format_block_indent(body, depth + 1),
126                prefix
127            );
128            if let Some(eb) = else_block {
129                s.push_str(&format!(
130                    " else {{\n{}\n{}}}",
131                    format_block_indent(eb, depth + 1),
132                    prefix
133                ));
134            }
135            s
136        }
137        StmtKind::While {
138            condition,
139            body,
140            label,
141            continue_block,
142        } => {
143            let lb = label
144                .as_ref()
145                .map(|l| format!("{}: ", l))
146                .unwrap_or_default();
147            let mut s = format!(
148                "{}while ({}) {{\n{}\n{}}}",
149                lb,
150                format_expr(condition),
151                format_block_indent(body, depth + 1),
152                prefix
153            );
154            if let Some(cb) = continue_block {
155                s.push_str(&format!(
156                    " continue {{\n{}\n{}}}",
157                    format_block_indent(cb, depth + 1),
158                    prefix
159                ));
160            }
161            s
162        }
163        StmtKind::Until {
164            condition,
165            body,
166            label,
167            continue_block,
168        } => {
169            let lb = label
170                .as_ref()
171                .map(|l| format!("{}: ", l))
172                .unwrap_or_default();
173            let mut s = format!(
174                "{}until ({}) {{\n{}\n{}}}",
175                lb,
176                format_expr(condition),
177                format_block_indent(body, depth + 1),
178                prefix
179            );
180            if let Some(cb) = continue_block {
181                s.push_str(&format!(
182                    " continue {{\n{}\n{}}}",
183                    format_block_indent(cb, depth + 1),
184                    prefix
185                ));
186            }
187            s
188        }
189        StmtKind::DoWhile { body, condition } => {
190            format!(
191                "do {{\n{}\n{}}} while ({})",
192                format_block_indent(body, depth + 1),
193                prefix,
194                format_expr(condition)
195            )
196        }
197        StmtKind::For {
198            init,
199            condition,
200            step,
201            body,
202            label,
203            continue_block,
204        } => {
205            let lb = label
206                .as_ref()
207                .map(|l| format!("{}: ", l))
208                .unwrap_or_default();
209            let ini = init
210                .as_ref()
211                .map(|s| format_statement_indent(s, 0))
212                .unwrap_or_default();
213            let cond = condition.as_ref().map(format_expr).unwrap_or_default();
214            let st = step.as_ref().map(format_expr).unwrap_or_default();
215            let mut s = format!(
216                "{}for ({}; {}; {}) {{\n{}\n{}}}",
217                lb,
218                ini,
219                cond,
220                st,
221                format_block_indent(body, depth + 1),
222                prefix
223            );
224            if let Some(cb) = continue_block {
225                s.push_str(&format!(
226                    " continue {{\n{}\n{}}}",
227                    format_block_indent(cb, depth + 1),
228                    prefix
229                ));
230            }
231            s
232        }
233        StmtKind::Foreach {
234            var,
235            list,
236            body,
237            label,
238            continue_block,
239        } => {
240            let lb = label
241                .as_ref()
242                .map(|l| format!("{}: ", l))
243                .unwrap_or_default();
244            let mut s = format!(
245                "{}for ${} ({}) {{\n{}\n{}}}",
246                lb,
247                var,
248                format_expr(list),
249                format_block_indent(body, depth + 1),
250                prefix
251            );
252            if let Some(cb) = continue_block {
253                s.push_str(&format!(
254                    " continue {{\n{}\n{}}}",
255                    format_block_indent(cb, depth + 1),
256                    prefix
257                ));
258            }
259            s
260        }
261        StmtKind::SubDecl {
262            name,
263            params,
264            body,
265            prototype,
266        } => {
267            let sig = if !params.is_empty() {
268                format!(
269                    " ({})",
270                    params
271                        .iter()
272                        .map(format_sub_sig_param)
273                        .collect::<Vec<_>>()
274                        .join(", ")
275                )
276            } else {
277                prototype
278                    .as_ref()
279                    .map(|p| format!(" ({})", p))
280                    .unwrap_or_default()
281            };
282            format!(
283                "fn {}{} {{\n{}\n{}}}",
284                name,
285                sig,
286                format_block_indent(body, depth + 1),
287                prefix
288            )
289        }
290        StmtKind::Package { name } => format!("package {}", name),
291        StmtKind::UsePerlVersion { version } => {
292            if version.fract() == 0.0 && *version >= 0.0 {
293                format!("use {}", *version as i64)
294            } else {
295                format!("use {}", version)
296            }
297        }
298        StmtKind::Use { module, imports, version } => {
299            let ver = version.as_deref().map(|v| format!(" {}", v)).unwrap_or_default();
300            if imports.is_empty() {
301                format!("use {}{}", module, ver)
302            } else {
303                format!("use {}{} {}", module, ver, format_expr_list(imports))
304            }
305        }
306        StmtKind::UseOverload { pairs } => {
307            let inner = pairs
308                .iter()
309                .map(|(k, v)| {
310                    format!(
311                        "'{}' => '{}'",
312                        k.replace('\'', "\\'"),
313                        v.replace('\'', "\\'")
314                    )
315                })
316                .collect::<Vec<_>>()
317                .join(", ");
318            format!("use overload {inner}")
319        }
320        StmtKind::No { module, imports } => {
321            if imports.is_empty() {
322                format!("no {}", module)
323            } else {
324                format!("no {} {}", module, format_expr_list(imports))
325            }
326        }
327        StmtKind::Return(e) => e
328            .as_ref()
329            .map(|x| format!("return {}", format_expr(x)))
330            .unwrap_or_else(|| "return".to_string()),
331        StmtKind::Last(l) => l
332            .as_ref()
333            .map(|x| format!("last {}", x))
334            .unwrap_or_else(|| "last".to_string()),
335        StmtKind::Next(l) => l
336            .as_ref()
337            .map(|x| format!("next {}", x))
338            .unwrap_or_else(|| "next".to_string()),
339        StmtKind::Redo(l) => l
340            .as_ref()
341            .map(|x| format!("redo {}", x))
342            .unwrap_or_else(|| "redo".to_string()),
343        StmtKind::My(decls) => format!("my {}", format_var_decls(decls)),
344        StmtKind::Our(decls) => format!("our {}", format_var_decls(decls)),
345        StmtKind::Local(decls) => format!("local {}", format_var_decls(decls)),
346        StmtKind::State(decls) => format!("state {}", format_var_decls(decls)),
347        StmtKind::LocalExpr {
348            target,
349            initializer,
350        } => {
351            let mut s = format!("local {}", format_expr(target));
352            if let Some(init) = initializer {
353                s.push_str(&format!(" = {}", format_expr(init)));
354            }
355            s
356        }
357        StmtKind::MySync(decls) => format!("mysync {}", format_var_decls(decls)),
358        StmtKind::OurSync(decls) => format!("oursync {}", format_var_decls(decls)),
359        StmtKind::StmtGroup(b) => format_block_indent(b, depth),
360        StmtKind::Block(b) => format!("{{\n{}\n{}}}", format_block_indent(b, depth + 1), prefix),
361        StmtKind::Begin(b) => format!(
362            "BEGIN {{\n{}\n{}}}",
363            format_block_indent(b, depth + 1),
364            prefix
365        ),
366        StmtKind::UnitCheck(b) => format!(
367            "UNITCHECK {{\n{}\n{}}}",
368            format_block_indent(b, depth + 1),
369            prefix
370        ),
371        StmtKind::Check(b) => format!(
372            "CHECK {{\n{}\n{}}}",
373            format_block_indent(b, depth + 1),
374            prefix
375        ),
376        StmtKind::Init(b) => format!(
377            "INIT {{\n{}\n{}}}",
378            format_block_indent(b, depth + 1),
379            prefix
380        ),
381        StmtKind::End(b) => format!(
382            "END {{\n{}\n{}}}",
383            format_block_indent(b, depth + 1),
384            prefix
385        ),
386        StmtKind::Empty => String::new(),
387        StmtKind::Goto { target } => format!("goto {}", format_expr(target)),
388        StmtKind::Continue(b) => format!(
389            "continue {{\n{}\n{}}}",
390            format_block_indent(b, depth + 1),
391            prefix
392        ),
393        StmtKind::StructDecl { def } => {
394            let fields = def
395                .fields
396                .iter()
397                .map(|f| format!("{} => {}", f.name, f.ty.display_name()))
398                .collect::<Vec<_>>()
399                .join(", ");
400            format!("struct {} {{ {} }}", def.name, fields)
401        }
402        StmtKind::EnumDecl { def } => {
403            let variants = def
404                .variants
405                .iter()
406                .map(|v| {
407                    if let Some(ty) = &v.ty {
408                        format!("{} => {}", v.name, ty.display_name())
409                    } else {
410                        v.name.clone()
411                    }
412                })
413                .collect::<Vec<_>>()
414                .join(", ");
415            format!("enum {} {{ {} }}", def.name, variants)
416        }
417        StmtKind::ClassDecl { def } => {
418            let prefix = if def.is_abstract {
419                "abstract "
420            } else if def.is_final {
421                "final "
422            } else {
423                ""
424            };
425            let mut header = format!("{}class {}", prefix, def.name);
426            if !def.extends.is_empty() {
427                header.push_str(&format!(" extends {}", def.extends.join(", ")));
428            }
429            if !def.implements.is_empty() {
430                header.push_str(&format!(" impl {}", def.implements.join(", ")));
431            }
432            let fields = def
433                .fields
434                .iter()
435                .map(|f| {
436                    let vis = match f.visibility {
437                        crate::ast::Visibility::Private => "priv ",
438                        crate::ast::Visibility::Protected => "prot ",
439                        crate::ast::Visibility::Public => "",
440                    };
441                    format!("{}{}: {}", vis, f.name, f.ty.display_name())
442                })
443                .collect::<Vec<_>>()
444                .join("; ");
445            format!("{} {{ {} }}", header, fields)
446        }
447        StmtKind::TraitDecl { def } => {
448            let methods = def
449                .methods
450                .iter()
451                .map(|m| format!("fn {}", m.name))
452                .collect::<Vec<_>>()
453                .join("; ");
454            format!("trait {} {{ {} }}", def.name, methods)
455        }
456        StmtKind::EvalTimeout { timeout, body } => {
457            format!(
458                "eval_timeout {} {{\n{}\n{}}}",
459                format_expr(timeout),
460                format_block_indent(body, depth + 1),
461                prefix
462            )
463        }
464        StmtKind::TryCatch {
465            try_block,
466            catch_var,
467            catch_block,
468            finally_block,
469        } => {
470            let fin = finally_block
471                .as_ref()
472                .map(|b| {
473                    format!(
474                        " finally {{\n{}\n{}}}",
475                        format_block_indent(b, depth + 1),
476                        prefix
477                    )
478                })
479                .unwrap_or_default();
480            format!(
481                "try {{\n{}\n{}}} catch (${}) {{\n{}\n{}}}{}",
482                format_block_indent(try_block, depth + 1),
483                prefix,
484                catch_var,
485                format_block_indent(catch_block, depth + 1),
486                prefix,
487                fin
488            )
489        }
490        StmtKind::Given { topic, body } => {
491            format!(
492                "given ({}) {{\n{}\n{}}}",
493                format_expr(topic),
494                format_block_indent(body, depth + 1),
495                prefix
496            )
497        }
498        StmtKind::When { cond, body } => {
499            format!(
500                "when ({}) {{\n{}\n{}}}",
501                format_expr(cond),
502                format_block_indent(body, depth + 1),
503                prefix
504            )
505        }
506        StmtKind::DefaultCase { body } => format!(
507            "default {{\n{}\n{}}}",
508            format_block_indent(body, depth + 1),
509            prefix
510        ),
511        StmtKind::FormatDecl { name, lines } => {
512            let mut s = format!("format {} =\n", name);
513            for ln in lines {
514                s.push_str(ln);
515                s.push('\n');
516            }
517            s.push('.');
518            s
519        }
520        StmtKind::Tie {
521            target,
522            class,
523            args,
524        } => {
525            let target_s = match target {
526                crate::ast::TieTarget::Hash(h) => format!("%{}", h),
527                crate::ast::TieTarget::Array(a) => format!("@{}", a),
528                crate::ast::TieTarget::Scalar(s) => format!("${}", s),
529            };
530            let mut s = format!("tie {} {}", target_s, format_expr(class));
531            for a in args {
532                s.push_str(&format!(", {}", format_expr(a)));
533            }
534            s
535        }
536        StmtKind::AdviceDecl {
537            kind,
538            pattern,
539            body,
540        } => {
541            let kw = match kind {
542                crate::ast::AdviceKind::Before => "before",
543                crate::ast::AdviceKind::After => "after",
544                crate::ast::AdviceKind::Around => "around",
545            };
546            format!(
547                "{} \"{}\" {{\n{}\n{}}}",
548                kw,
549                pattern,
550                format_block_indent(body, depth + 1),
551                prefix
552            )
553        }
554    };
555    format!("{}{}{}", prefix, lab, body)
556}
557/// `format_block` — see implementation.
558pub fn format_block(b: &Block) -> String {
559    format_block_indent(b, 0)
560}
561
562fn format_block_indent(b: &Block, depth: usize) -> String {
563    b.iter()
564        .map(|s| format_statement_indent(s, depth))
565        .collect::<Vec<_>>()
566        .join("\n")
567}
568
569/// Format a block as a single line for inline use (short blocks in expressions).
570fn format_block_inline(b: &Block) -> String {
571    b.iter()
572        .map(|s| format_statement_indent(s, 0))
573        .collect::<Vec<_>>()
574        .join("; ")
575}
576
577fn format_var_decls(decls: &[VarDecl]) -> String {
578    decls
579        .iter()
580        .map(|d| {
581            let sig = match d.sigil {
582                Sigil::Scalar => "$",
583                Sigil::Array => "@",
584                Sigil::Hash => "%",
585                Sigil::Typeglob => "*",
586            };
587            let mut s = format!("{}{}", sig, d.name);
588            if let Some(ref t) = d.type_annotation {
589                s.push_str(&format!(" : {}", t.display_name()));
590            }
591            if let Some(ref init) = d.initializer {
592                s.push_str(&format!(" = {}", format_expr(init)));
593            }
594            s
595        })
596        .collect::<Vec<_>>()
597        .join(", ")
598}
599
600pub(crate) fn format_expr_list(es: &[Expr]) -> String {
601    es.iter().map(format_expr).collect::<Vec<_>>().join(", ")
602}
603
604pub(crate) fn format_binop(op: BinOp) -> &'static str {
605    match op {
606        BinOp::Add => "+",
607        BinOp::Sub => "-",
608        BinOp::Mul => "*",
609        BinOp::Div => "/",
610        BinOp::Mod => "%",
611        BinOp::Pow => "**",
612        BinOp::Concat => ".",
613        BinOp::NumEq => "==",
614        BinOp::NumNe => "!=",
615        BinOp::NumLt => "<",
616        BinOp::NumGt => ">",
617        BinOp::NumLe => "<=",
618        BinOp::NumGe => ">=",
619        BinOp::Spaceship => "<=>",
620        BinOp::StrEq => "eq",
621        BinOp::StrNe => "ne",
622        BinOp::StrLt => "lt",
623        BinOp::StrGt => "gt",
624        BinOp::StrLe => "le",
625        BinOp::StrGe => "ge",
626        BinOp::StrCmp => "cmp",
627        BinOp::LogAnd => "&&",
628        BinOp::LogOr => "||",
629        BinOp::DefinedOr => "//",
630        BinOp::BitAnd => "&",
631        BinOp::BitOr => "|",
632        BinOp::BitXor => "^",
633        BinOp::ShiftLeft => "<<",
634        BinOp::ShiftRight => ">>",
635        BinOp::LogAndWord => "and",
636        BinOp::LogOrWord => "or",
637        BinOp::BindMatch => "=~",
638        BinOp::BindNotMatch => "!~",
639    }
640}
641
642pub(crate) fn format_unary(op: UnaryOp) -> &'static str {
643    match op {
644        UnaryOp::Negate => "-",
645        UnaryOp::LogNot => "!",
646        UnaryOp::BitNot => "~",
647        UnaryOp::LogNotWord => "not",
648        UnaryOp::PreIncrement => "++",
649        UnaryOp::PreDecrement => "--",
650        UnaryOp::Ref => "\\",
651    }
652}
653
654pub(crate) fn format_postfix(op: PostfixOp) -> &'static str {
655    match op {
656        PostfixOp::Increment => "++",
657        PostfixOp::Decrement => "--",
658    }
659}
660
661pub(crate) fn format_string_part(p: &StringPart) -> String {
662    match p {
663        StringPart::Literal(s) => escape_interpolated_literal(s),
664        StringPart::ScalarVar(n) => format!("${{{}}}", n),
665        StringPart::ArrayVar(n) => format!("@{{{}}}", n),
666        StringPart::Expr(e) => format_expr(e),
667    }
668}
669
670/// Escape special characters inside the literal portions of an interpolated string.
671pub(crate) fn escape_interpolated_literal(s: &str) -> String {
672    let mut out = String::new();
673    for c in s.chars() {
674        match c {
675            '\\' => out.push_str("\\\\"),
676            '"' => out.push_str("\\\""),
677            '\n' => out.push_str("\\n"),
678            '\r' => out.push_str("\\r"),
679            '\t' => out.push_str("\\t"),
680            '\x1b' => out.push_str("\\e"),
681            c if c.is_control() => {
682                out.push_str(&format!("\\x{{{:02x}}}", c as u32));
683            }
684            _ => out.push(c),
685        }
686    }
687    out
688}
689
690/// Escape control characters in regex pattern/replacement strings.
691/// Does not escape `/` since that's handled by the delimiter.
692pub(crate) fn escape_regex_part(s: &str) -> String {
693    let mut out = String::new();
694    for c in s.chars() {
695        match c {
696            '\n' => out.push_str("\\n"),
697            '\r' => out.push_str("\\r"),
698            '\t' => out.push_str("\\t"),
699            '\x1b' => out.push_str("\\x1b"),
700            c if c.is_control() => {
701                out.push_str(&format!("\\x{:02x}", c as u32));
702            }
703            _ => out.push(c),
704        }
705    }
706    out
707}
708
709pub(crate) fn format_string_literal(s: &str) -> String {
710    let mut out = String::new();
711    out.push('"');
712    for c in s.chars() {
713        match c {
714            '\\' => out.push_str("\\\\"),
715            '"' => out.push_str("\\\""),
716            '\n' => out.push_str("\\n"),
717            '\r' => out.push_str("\\r"),
718            '\t' => out.push_str("\\t"),
719            '\x1b' => out.push_str("\\e"),
720            c if c.is_control() => {
721                out.push_str(&format!("\\x{{{:02x}}}", c as u32));
722            }
723            _ => out.push(c),
724        }
725    }
726    out.push('"');
727    out
728}
729
730/// Format an expression; aims for readable Perl-like output.
731pub fn format_expr(e: &Expr) -> String {
732    match &e.kind {
733        ExprKind::Integer(n) => n.to_string(),
734        ExprKind::Float(f) => format!("{}", f),
735        ExprKind::String(s) => format_string_literal(s),
736        ExprKind::Bareword(s) => s.clone(),
737        ExprKind::Regex(p, fl) => format!("/{}/{}/", p, fl),
738        ExprKind::QW(ws) => format!("qw({})", ws.join(" ")),
739        ExprKind::Undef => "undef".to_string(),
740        ExprKind::MagicConst(crate::ast::MagicConstKind::File) => "__FILE__".to_string(),
741        ExprKind::MagicConst(crate::ast::MagicConstKind::Line) => "__LINE__".to_string(),
742        ExprKind::MagicConst(crate::ast::MagicConstKind::Sub) => "__SUB__".to_string(),
743        ExprKind::InterpolatedString(parts) => {
744            format!(
745                "\"{}\"",
746                parts.iter().map(format_string_part).collect::<String>()
747            )
748        }
749        ExprKind::ScalarVar(name) => format!("${}", name),
750        ExprKind::ArrayVar(name) => format!("@{}", name),
751        ExprKind::HashVar(name) => format!("%{}", name),
752        ExprKind::Typeglob(name) => format!("*{}", name),
753        ExprKind::TypeglobExpr(e) => format!("*{{ {} }}", format_expr(e)),
754        ExprKind::ArrayElement { array, index } => format!("${}[{}]", array, format_expr(index)),
755        ExprKind::HashElement { hash, key } => format!("${}{{{}}}", hash, format_expr(key)),
756        ExprKind::ArraySlice { array, indices } => format!(
757            "@{}[{}]",
758            array,
759            indices
760                .iter()
761                .map(format_expr)
762                .collect::<Vec<_>>()
763                .join(", ")
764        ),
765        ExprKind::HashSlice { hash, keys } => format!(
766            "@{}{{{}}}",
767            hash,
768            keys.iter().map(format_expr).collect::<Vec<_>>().join(", ")
769        ),
770        ExprKind::HashKvSlice { hash, keys } => format!(
771            "%{}{{{}}}",
772            hash,
773            keys.iter().map(format_expr).collect::<Vec<_>>().join(", ")
774        ),
775        ExprKind::HashSliceDeref { container, keys } => format!(
776            "@{}{{{}}}",
777            format_expr(container),
778            keys.iter().map(format_expr).collect::<Vec<_>>().join(", ")
779        ),
780        ExprKind::AnonymousListSlice { source, indices } => format!(
781            "({})[{}]",
782            format_expr(source),
783            indices
784                .iter()
785                .map(format_expr)
786                .collect::<Vec<_>>()
787                .join(", ")
788        ),
789        ExprKind::ScalarRef(inner) => format!("\\{}", format_expr(inner)),
790        ExprKind::ArrayRef(elems) => format!("[{}]", format_expr_list(elems)),
791        ExprKind::HashRef(pairs) => {
792            let inner = pairs
793                .iter()
794                .map(|(k, v)| format!("{} => {}", format_expr(k), format_expr(v)))
795                .collect::<Vec<_>>()
796                .join(", ");
797            format!("{{{}}}", inner)
798        }
799        ExprKind::CodeRef { params, body } => {
800            if params.is_empty() {
801                format!("fn {{ {} }}", format_block_inline(body))
802            } else {
803                let sig = params
804                    .iter()
805                    .map(format_sub_sig_param)
806                    .collect::<Vec<_>>()
807                    .join(", ");
808                format!("fn ({}) {{ {} }}", sig, format_block_inline(body))
809            }
810        }
811        ExprKind::SubroutineRef(name) => format!("&{}", name),
812        ExprKind::SubroutineCodeRef(name) => format!("\\&{}", name),
813        ExprKind::DynamicSubCodeRef(e) => format!("\\&{{ {} }}", format_expr(e)),
814        ExprKind::Deref { expr, kind } => match kind {
815            Sigil::Scalar => format!("${{{}}}", format_expr(expr)),
816            Sigil::Array => format!("@{{${}}}", format_expr(expr)),
817            Sigil::Hash => format!("%{{${}}}", format_expr(expr)),
818            Sigil::Typeglob => format!("*{{${}}}", format_expr(expr)),
819        },
820        ExprKind::ArrowDeref { expr, index, kind } => match kind {
821            DerefKind::Array => format!("({})->[{}]", format_expr(expr), format_expr(index)),
822            DerefKind::Hash => format!("({})->{{{}}}", format_expr(expr), format_expr(index)),
823            DerefKind::Call => format!("({})->({})", format_expr(expr), format_expr(index)),
824        },
825        ExprKind::BinOp { left, op, right } => format!(
826            "{} {} {}",
827            format_expr(left),
828            format_binop(*op),
829            format_expr(right)
830        ),
831        ExprKind::UnaryOp { op, expr } => format!("{}{}", format_unary(*op), format_expr(expr)),
832        ExprKind::PostfixOp { expr, op } => {
833            format!("{}{}", format_expr(expr), format_postfix(*op))
834        }
835        ExprKind::Assign { target, value } => {
836            format!("{} = {}", format_expr(target), format_expr(value))
837        }
838        ExprKind::CompoundAssign { target, op, value } => format!(
839            "{} {}= {}",
840            format_expr(target),
841            format_binop(*op),
842            format_expr(value)
843        ),
844        ExprKind::Ternary {
845            condition,
846            then_expr,
847            else_expr,
848        } => format!(
849            "{} ? {} : {}",
850            format_expr(condition),
851            format_expr(then_expr),
852            format_expr(else_expr)
853        ),
854        ExprKind::Repeat {
855            expr,
856            count,
857            list_repeat,
858        } => {
859            if *list_repeat && !matches!(expr.kind, ExprKind::List(_) | ExprKind::QW(_)) {
860                format!("({}) x {}", format_expr(expr), format_expr(count))
861            } else {
862                format!("{} x {}", format_expr(expr), format_expr(count))
863            }
864        }
865        ExprKind::Range {
866            from,
867            to,
868            exclusive,
869            step,
870        } => {
871            let op = if *exclusive { "..." } else { ".." };
872            if let Some(s) = step {
873                format!(
874                    "{} {} {}:{}",
875                    format_expr(from),
876                    op,
877                    format_expr(to),
878                    format_expr(s)
879                )
880            } else {
881                format!("{} {} {}", format_expr(from), op, format_expr(to))
882            }
883        }
884        ExprKind::SliceRange { from, to, step } => {
885            let f = from.as_ref().map(|e| format_expr(e)).unwrap_or_default();
886            let t = to.as_ref().map(|e| format_expr(e)).unwrap_or_default();
887            match step {
888                Some(s) => format!("{}:{}:{}", f, t, format_expr(s)),
889                None => format!("{}:{}", f, t),
890            }
891        }
892        ExprKind::FuncCall { name, args } => format!(
893            "{}({})",
894            name,
895            args.iter().map(format_expr).collect::<Vec<_>>().join(", ")
896        ),
897        ExprKind::MethodCall {
898            object,
899            method,
900            args,
901            super_call,
902        } => {
903            let m = if *super_call {
904                format!("SUPER::{}", method)
905            } else {
906                method.clone()
907            };
908            format!(
909                "{}->{}({})",
910                format_expr(object),
911                m,
912                args.iter().map(format_expr).collect::<Vec<_>>().join(", ")
913            )
914        }
915        ExprKind::IndirectCall {
916            target,
917            args,
918            ampersand,
919            pass_caller_arglist,
920        } => {
921            if *pass_caller_arglist && args.is_empty() {
922                format!("&{}", format_expr(target))
923            } else {
924                let inner = format!(
925                    "{}({})",
926                    format_expr(target),
927                    args.iter().map(format_expr).collect::<Vec<_>>().join(", ")
928                );
929                if *ampersand {
930                    format!("&{}", inner)
931                } else {
932                    inner
933                }
934            }
935        }
936        ExprKind::Print { handle, args } => {
937            let h = handle
938                .as_ref()
939                .map(|h| format!("{} ", h))
940                .unwrap_or_default();
941            format!("print {}{}", h, format_expr_list(args))
942        }
943        ExprKind::Say { handle, args } => {
944            let h = handle
945                .as_ref()
946                .map(|h| format!("{} ", h))
947                .unwrap_or_default();
948            format!("say {}{}", h, format_expr_list(args))
949        }
950        ExprKind::Printf { handle, args } => {
951            let h = handle
952                .as_ref()
953                .map(|h| format!("{} ", h))
954                .unwrap_or_default();
955            format!("printf {}{}", h, format_expr_list(args))
956        }
957        ExprKind::Die(args) => {
958            if args.is_empty() {
959                "die".to_string()
960            } else {
961                format!("die {}", format_expr_list(args))
962            }
963        }
964        ExprKind::Warn(args) => {
965            if args.is_empty() {
966                "warn".to_string()
967            } else {
968                format!("warn {}", format_expr_list(args))
969            }
970        }
971        ExprKind::Match {
972            expr,
973            pattern,
974            flags,
975            scalar_g: _,
976            delim: _,
977        } => format!("{} =~ /{}/{}", format_expr(expr), pattern, flags),
978        ExprKind::Substitution {
979            expr,
980            pattern,
981            replacement,
982            flags,
983            delim: _,
984        } => format!(
985            "{} =~ s/{}/{}/{}",
986            format_expr(expr),
987            pattern,
988            replacement,
989            flags
990        ),
991        ExprKind::Transliterate {
992            expr,
993            from,
994            to,
995            flags,
996            delim: _,
997        } => format!("{} =~ tr/{}/{}/{}", format_expr(expr), from, to, flags),
998        ExprKind::MapExpr {
999            block,
1000            list,
1001            flatten_array_refs,
1002            stream,
1003        } => {
1004            let kw = match (*flatten_array_refs, *stream) {
1005                (true, true) => "flat_maps",
1006                (true, false) => "flat_map",
1007                (false, true) => "maps",
1008                (false, false) => "map",
1009            };
1010            format!(
1011                "{kw} {{ {} }} {}",
1012                format_block_inline(block),
1013                format_expr(list)
1014            )
1015        }
1016        ExprKind::MapExprComma {
1017            expr,
1018            list,
1019            flatten_array_refs,
1020            stream,
1021        } => {
1022            let kw = match (*flatten_array_refs, *stream) {
1023                (true, true) => "flat_maps",
1024                (true, false) => "flat_map",
1025                (false, true) => "maps",
1026                (false, false) => "map",
1027            };
1028            format!("{kw} {}, {}", format_expr(expr), format_expr(list))
1029        }
1030        ExprKind::GrepExpr {
1031            block,
1032            list,
1033            keyword,
1034        } => {
1035            format!(
1036                "{} {{ {} }} {}",
1037                keyword.as_str(),
1038                format_block_inline(block),
1039                format_expr(list)
1040            )
1041        }
1042        ExprKind::GrepExprComma {
1043            expr,
1044            list,
1045            keyword,
1046        } => {
1047            format!(
1048                "{} {}, {}",
1049                keyword.as_str(),
1050                format_expr(expr),
1051                format_expr(list)
1052            )
1053        }
1054        ExprKind::ForEachExpr { block, list } => {
1055            format!(
1056                "fore {{ {} }} {}",
1057                format_block_inline(block),
1058                format_expr(list)
1059            )
1060        }
1061        ExprKind::SortExpr { cmp, list } => match cmp {
1062            Some(crate::ast::SortComparator::Block(b)) => {
1063                format!(
1064                    "sort {{ {} }} {}",
1065                    format_block_inline(b),
1066                    format_expr(list)
1067                )
1068            }
1069            Some(crate::ast::SortComparator::Code(e)) => {
1070                format!("sort {} {}", format_expr(e), format_expr(list))
1071            }
1072            None => format!("sort {}", format_expr(list)),
1073        },
1074        ExprKind::ReverseExpr(e) => format!("reverse {}", format_expr(e)),
1075        ExprKind::Rev(e) => format!("rev {}", format_expr(e)),
1076        ExprKind::JoinExpr { separator, list } => {
1077            format!("join({}, {})", format_expr(separator), format_expr(list))
1078        }
1079        ExprKind::SplitExpr {
1080            pattern,
1081            string,
1082            limit,
1083        } => match limit {
1084            Some(l) => format!(
1085                "split({}, {}, {})",
1086                format_expr(pattern),
1087                format_expr(string),
1088                format_expr(l)
1089            ),
1090            None => format!("split({}, {})", format_expr(pattern), format_expr(string)),
1091        },
1092        ExprKind::PMapExpr {
1093            block,
1094            list,
1095            progress,
1096            flat_outputs,
1097            on_cluster,
1098            stream: _,
1099        } => {
1100            let kw = match (flat_outputs, on_cluster.is_some()) {
1101                (true, true) => "pflat_map_on",
1102                (true, false) => "pflat_map",
1103                (false, true) => "pmap_on",
1104                (false, false) => "pmap",
1105            };
1106            let base = if let Some(c) = on_cluster {
1107                format!(
1108                    "{kw} {} {{ {} }} {}",
1109                    format_expr(c),
1110                    format_block_inline(block),
1111                    format_expr(list)
1112                )
1113            } else {
1114                format!(
1115                    "{kw} {{ {} }} {}",
1116                    format_block_inline(block),
1117                    format_expr(list)
1118                )
1119            };
1120            match progress {
1121                Some(p) => format!("{}, progress => {}", base, format_expr(p)),
1122                None => base,
1123            }
1124        }
1125        ExprKind::PMapChunkedExpr {
1126            chunk_size,
1127            block,
1128            list,
1129            progress,
1130        } => {
1131            let base = format!(
1132                "pmap_chunked {} {{ {} }} {}",
1133                format_expr(chunk_size),
1134                format_block_inline(block),
1135                format_expr(list)
1136            );
1137            match progress {
1138                Some(p) => format!("{}, progress => {}", base, format_expr(p)),
1139                None => base,
1140            }
1141        }
1142        ExprKind::PGrepExpr {
1143            block,
1144            list,
1145            progress,
1146            stream: _,
1147        } => {
1148            let base = format!(
1149                "pgrep {{ {} }} {}",
1150                format_block_inline(block),
1151                format_expr(list)
1152            );
1153            match progress {
1154                Some(p) => format!("{}, progress => {}", base, format_expr(p)),
1155                None => base,
1156            }
1157        }
1158        ExprKind::ParExpr { block, list } => format!(
1159            "par {{ {} }} {}",
1160            format_block_inline(block),
1161            format_expr(list)
1162        ),
1163        ExprKind::ParReduceExpr {
1164            extract_block,
1165            reduce_block,
1166            list,
1167        } => match reduce_block {
1168            Some(rb) => format!(
1169                "par_reduce {{ {} }} {{ {} }} {}",
1170                format_block_inline(extract_block),
1171                format_block_inline(rb),
1172                format_expr(list)
1173            ),
1174            None => format!(
1175                "par_reduce {{ {} }} {}",
1176                format_block_inline(extract_block),
1177                format_expr(list)
1178            ),
1179        },
1180        ExprKind::DistReduceExpr {
1181            cluster,
1182            extract_block,
1183            list,
1184        } => format!(
1185            "dist_reduce on {} {{ {} }} {}",
1186            format_expr(cluster),
1187            format_block_inline(extract_block),
1188            format_expr(list)
1189        ),
1190        ExprKind::PForExpr {
1191            block,
1192            list,
1193            progress,
1194        } => {
1195            let base = format!(
1196                "pfor {{ {} }} {}",
1197                format_block_inline(block),
1198                format_expr(list)
1199            );
1200            match progress {
1201                Some(p) => format!("{}, progress => {}", base, format_expr(p)),
1202                None => base,
1203            }
1204        }
1205        ExprKind::ParLinesExpr {
1206            path,
1207            callback,
1208            progress,
1209        } => match progress {
1210            Some(p) => format!(
1211                "par_lines({}, {}, progress => {})",
1212                format_expr(path),
1213                format_expr(callback),
1214                format_expr(p)
1215            ),
1216            None => format!(
1217                "par_lines({}, {})",
1218                format_expr(path),
1219                format_expr(callback)
1220            ),
1221        },
1222        ExprKind::ParWalkExpr {
1223            path,
1224            callback,
1225            progress,
1226        } => match progress {
1227            Some(p) => format!(
1228                "par_walk({}, {}, progress => {})",
1229                format_expr(path),
1230                format_expr(callback),
1231                format_expr(p)
1232            ),
1233            None => format!("par_walk({}, {})", format_expr(path), format_expr(callback)),
1234        },
1235        ExprKind::PwatchExpr { path, callback } => {
1236            format!("pwatch({}, {})", format_expr(path), format_expr(callback))
1237        }
1238        ExprKind::PSortExpr {
1239            cmp,
1240            list,
1241            progress,
1242        } => {
1243            let base = match cmp {
1244                Some(b) => format!(
1245                    "psort {{ {} }} {}",
1246                    format_block_inline(b),
1247                    format_expr(list)
1248                ),
1249                None => format!("psort {}", format_expr(list)),
1250            };
1251            match progress {
1252                Some(p) => format!("{}, progress => {}", base, format_expr(p)),
1253                None => base,
1254            }
1255        }
1256        ExprKind::ReduceExpr { block, list } => format!(
1257            "reduce {{ {} }} {}",
1258            format_block_inline(block),
1259            format_expr(list)
1260        ),
1261        ExprKind::PReduceExpr {
1262            block,
1263            list,
1264            progress,
1265        } => {
1266            let base = format!(
1267                "preduce {{ {} }} {}",
1268                format_block_inline(block),
1269                format_expr(list)
1270            );
1271            match progress {
1272                Some(p) => format!("{}, progress => {}", base, format_expr(p)),
1273                None => base,
1274            }
1275        }
1276        ExprKind::PReduceInitExpr {
1277            init,
1278            block,
1279            list,
1280            progress,
1281        } => {
1282            let base = format!(
1283                "preduce_init {}, {{ {} }} {}",
1284                format_expr(init),
1285                format_block_inline(block),
1286                format_expr(list)
1287            );
1288            match progress {
1289                Some(p) => format!("{}, progress => {}", base, format_expr(p)),
1290                None => base,
1291            }
1292        }
1293        ExprKind::PMapReduceExpr {
1294            map_block,
1295            reduce_block,
1296            list,
1297            progress,
1298        } => {
1299            let base = format!(
1300                "pmap_reduce {{ {} }} {{ {} }} {}",
1301                format_block_inline(map_block),
1302                format_block_inline(reduce_block),
1303                format_expr(list)
1304            );
1305            match progress {
1306                Some(p) => format!("{}, progress => {}", base, format_expr(p)),
1307                None => base,
1308            }
1309        }
1310        ExprKind::PcacheExpr {
1311            block,
1312            list,
1313            progress,
1314        } => {
1315            let base = format!(
1316                "pcache {{ {} }} {}",
1317                format_block_inline(block),
1318                format_expr(list)
1319            );
1320            match progress {
1321                Some(p) => format!("{}, progress => {}", base, format_expr(p)),
1322                None => base,
1323            }
1324        }
1325        ExprKind::PselectExpr { receivers, timeout } => {
1326            let inner = receivers
1327                .iter()
1328                .map(format_expr)
1329                .collect::<Vec<_>>()
1330                .join(", ");
1331            match timeout {
1332                Some(t) => format!("pselect({}, timeout => {})", inner, format_expr(t)),
1333                None => format!("pselect({})", inner),
1334            }
1335        }
1336        ExprKind::FanExpr {
1337            count,
1338            block,
1339            progress,
1340            capture,
1341        } => {
1342            let kw = if *capture { "fan_cap" } else { "fan" };
1343            let base = match count {
1344                Some(c) => format!(
1345                    "{} {} {{ {} }}",
1346                    kw,
1347                    format_expr(c),
1348                    format_block_inline(block)
1349                ),
1350                None => format!("{} {{ {} }}", kw, format_block_inline(block)),
1351            };
1352            match progress {
1353                Some(p) => format!("{}, progress => {}", base, format_expr(p)),
1354                None => base,
1355            }
1356        }
1357        ExprKind::AsyncBlock { body } => format!("async {{ {} }}", format_block_inline(body)),
1358        ExprKind::SpawnBlock { body } => format!("spawn {{ {} }}", format_block_inline(body)),
1359        ExprKind::Trace { body } => format!("trace {{ {} }}", format_block_inline(body)),
1360        ExprKind::Timer { body } => format!("timer {{ {} }}", format_block_inline(body)),
1361        ExprKind::Bench { body, times } => format!(
1362            "bench {{ {} }} {}",
1363            format_block_inline(body),
1364            format_expr(times)
1365        ),
1366        ExprKind::Await(e) => format!("await {}", format_expr(e)),
1367        ExprKind::Slurp(e) => format!("slurp {}", format_expr(e)),
1368        ExprKind::Swallow(e) => format!("swallow {}", format_expr(e)),
1369        ExprKind::Burp(e) => format!("burp {}", format_expr(e)),
1370        ExprKind::God(e) => format!("god {}", format_expr(e)),
1371        ExprKind::Ingest(e) => format!("ingest {}", format_expr(e)),
1372        ExprKind::Capture(e) => format!("capture {}", format_expr(e)),
1373        ExprKind::Qx(e) => format!("qx {}", format_expr(e)),
1374        ExprKind::FetchUrl(e) => format!("fetch_url {}", format_expr(e)),
1375        ExprKind::Pchannel { capacity } => match capacity {
1376            Some(c) => format!("pchannel({})", format_expr(c)),
1377            None => "pchannel()".to_string(),
1378        },
1379        ExprKind::Push { array, values } => {
1380            format!("push({}, {})", format_expr(array), format_expr_list(values))
1381        }
1382        ExprKind::Pop(e) => format!("pop {}", format_expr(e)),
1383        ExprKind::Shift(e) => format!("shift {}", format_expr(e)),
1384        ExprKind::Unshift { array, values } => format!(
1385            "unshift({}, {})",
1386            format_expr(array),
1387            format_expr_list(values)
1388        ),
1389        ExprKind::Splice {
1390            array,
1391            offset,
1392            length,
1393            replacement,
1394        } => {
1395            let mut parts = vec![format_expr(array)];
1396            if let Some(o) = offset {
1397                parts.push(format_expr(o));
1398            }
1399            if let Some(l) = length {
1400                parts.push(format_expr(l));
1401            }
1402            if !replacement.is_empty() {
1403                parts.push(format_expr_list(replacement));
1404            }
1405            format!("splice({})", parts.join(", "))
1406        }
1407        ExprKind::Delete(e) => format!("delete {}", format_expr(e)),
1408        ExprKind::Exists(e) => format!("exists {}", format_expr(e)),
1409        ExprKind::Keys(e) => format!("keys {}", format_expr(e)),
1410        ExprKind::Values(e) => format!("values {}", format_expr(e)),
1411        ExprKind::Each(e) => format!("each {}", format_expr(e)),
1412        ExprKind::Chomp(e) => format!("chomp {}", format_expr(e)),
1413        ExprKind::Chop(e) => format!("chop {}", format_expr(e)),
1414        ExprKind::Length(e) => format!("length {}", format_expr(e)),
1415        ExprKind::Substr {
1416            string,
1417            offset,
1418            length,
1419            replacement,
1420        } => {
1421            let mut parts = vec![format_expr(string), format_expr(offset)];
1422            if let Some(l) = length {
1423                parts.push(format_expr(l));
1424            }
1425            if let Some(r) = replacement {
1426                parts.push(format_expr(r));
1427            }
1428            format!("substr({})", parts.join(", "))
1429        }
1430        ExprKind::Index {
1431            string,
1432            substr,
1433            position,
1434        } => match position {
1435            Some(p) => format!(
1436                "index({}, {}, {})",
1437                format_expr(string),
1438                format_expr(substr),
1439                format_expr(p)
1440            ),
1441            None => format!("index({}, {})", format_expr(string), format_expr(substr)),
1442        },
1443        ExprKind::Rindex {
1444            string,
1445            substr,
1446            position,
1447        } => match position {
1448            Some(p) => format!(
1449                "rindex({}, {}, {})",
1450                format_expr(string),
1451                format_expr(substr),
1452                format_expr(p)
1453            ),
1454            None => format!("rindex({}, {})", format_expr(string), format_expr(substr)),
1455        },
1456        ExprKind::Sprintf { format, args } => format!(
1457            "sprintf({}, {})",
1458            format_expr(format),
1459            format_expr_list(args)
1460        ),
1461        ExprKind::Abs(e) => format!("abs {}", format_expr(e)),
1462        ExprKind::Int(e) => format!("int {}", format_expr(e)),
1463        ExprKind::Sqrt(e) => format!("sqrt {}", format_expr(e)),
1464        ExprKind::Sin(e) => format!("sin {}", format_expr(e)),
1465        ExprKind::Cos(e) => format!("cos {}", format_expr(e)),
1466        ExprKind::Atan2 { y, x } => format!("atan2({}, {})", format_expr(y), format_expr(x)),
1467        ExprKind::Exp(e) => format!("exp {}", format_expr(e)),
1468        ExprKind::Log(e) => format!("log {}", format_expr(e)),
1469        ExprKind::Rand(opt) => match opt {
1470            Some(e) => format!("rand({})", format_expr(e)),
1471            None => "rand".to_string(),
1472        },
1473        ExprKind::Srand(opt) => match opt {
1474            Some(e) => format!("srand({})", format_expr(e)),
1475            None => "srand".to_string(),
1476        },
1477        ExprKind::Hex(e) => format!("hex {}", format_expr(e)),
1478        ExprKind::Oct(e) => format!("oct {}", format_expr(e)),
1479        ExprKind::Lc(e) => format!("lc {}", format_expr(e)),
1480        ExprKind::Uc(e) => format!("uc {}", format_expr(e)),
1481        ExprKind::Lcfirst(e) => format!("lcfirst {}", format_expr(e)),
1482        ExprKind::Ucfirst(e) => format!("ucfirst {}", format_expr(e)),
1483        ExprKind::Fc(e) => format!("fc {}", format_expr(e)),
1484        ExprKind::Quotemeta(e) => format!("quotemeta {}", format_expr(e)),
1485        ExprKind::Crypt { plaintext, salt } => {
1486            format!("crypt({}, {})", format_expr(plaintext), format_expr(salt))
1487        }
1488        ExprKind::Pos(opt) => match opt {
1489            Some(e) => format!("pos({})", format_expr(e)),
1490            None => "pos".to_string(),
1491        },
1492        ExprKind::Study(e) => format!("study {}", format_expr(e)),
1493        ExprKind::Defined(e) => format!("defined {}", format_expr(e)),
1494        ExprKind::Ref(e) => format!("ref {}", format_expr(e)),
1495        ExprKind::ScalarContext(e) => format!("scalar {}", format_expr(e)),
1496        ExprKind::Chr(e) => format!("chr {}", format_expr(e)),
1497        ExprKind::Ord(e) => format!("ord {}", format_expr(e)),
1498        ExprKind::OpenMyHandle { name } => format!("my ${}", name),
1499        ExprKind::OpendirMyHandle { name } => format!("my ${}", name),
1500        ExprKind::Open { handle, mode, file } => match file {
1501            Some(f) => format!(
1502                "open({}, {}, {})",
1503                format_expr(handle),
1504                format_expr(mode),
1505                format_expr(f)
1506            ),
1507            None => format!("open({}, {})", format_expr(handle), format_expr(mode)),
1508        },
1509        ExprKind::Close(e) => format!("close {}", format_expr(e)),
1510        ExprKind::ReadLine(handle) => match handle {
1511            Some(h) => {
1512                if h.starts_with(|c: char| c.is_uppercase()) {
1513                    format!("<{}>", h)
1514                } else {
1515                    format!("<${}>", h)
1516                }
1517            }
1518            None => "<STDIN>".to_string(),
1519        },
1520        ExprKind::Eof(opt) => match opt {
1521            Some(e) => format!("eof({})", format_expr(e)),
1522            None => "eof".to_string(),
1523        },
1524        ExprKind::Opendir { handle, path } => {
1525            format!("opendir({}, {})", format_expr(handle), format_expr(path))
1526        }
1527        ExprKind::Readdir(e) => format!("readdir {}", format_expr(e)),
1528        ExprKind::Closedir(e) => format!("closedir {}", format_expr(e)),
1529        ExprKind::Rewinddir(e) => format!("rewinddir {}", format_expr(e)),
1530        ExprKind::Telldir(e) => format!("telldir {}", format_expr(e)),
1531        ExprKind::Seekdir { handle, position } => format!(
1532            "seekdir({}, {})",
1533            format_expr(handle),
1534            format_expr(position)
1535        ),
1536        ExprKind::FileTest { op, expr } => format!("-{}{}", op, format_expr(expr)),
1537        ExprKind::System(args) => format!("system({})", format_expr_list(args)),
1538        ExprKind::Exec(args) => format!("exec({})", format_expr_list(args)),
1539        ExprKind::Eval(e) => format!("eval {}", format_expr(e)),
1540        ExprKind::Do(e) => format!("do {}", format_expr(e)),
1541        ExprKind::Require(e) => format!("require {}", format_expr(e)),
1542        ExprKind::Exit(opt) => match opt {
1543            Some(e) => format!("exit({})", format_expr(e)),
1544            None => "exit".to_string(),
1545        },
1546        ExprKind::Chdir(e) => format!("chdir {}", format_expr(e)),
1547        ExprKind::Mkdir { path, mode } => match mode {
1548            Some(m) => format!("mkdir({}, {})", format_expr(path), format_expr(m)),
1549            None => format!("mkdir({})", format_expr(path)),
1550        },
1551        ExprKind::Unlink(args) => format!("unlink({})", format_expr_list(args)),
1552        ExprKind::Rename { old, new } => {
1553            format!("rename({}, {})", format_expr(old), format_expr(new))
1554        }
1555        ExprKind::Chmod(args) => format!("chmod({})", format_expr_list(args)),
1556        ExprKind::Chown(args) => format!("chown({})", format_expr_list(args)),
1557        ExprKind::Stat(e) => format!("stat {}", format_expr(e)),
1558        ExprKind::Lstat(e) => format!("lstat {}", format_expr(e)),
1559        ExprKind::Link { old, new } => format!("link({}, {})", format_expr(old), format_expr(new)),
1560        ExprKind::Symlink { old, new } => {
1561            format!("symlink({}, {})", format_expr(old), format_expr(new))
1562        }
1563        ExprKind::Readlink(e) => format!("readlink {}", format_expr(e)),
1564        ExprKind::Glob(args) => format!("glob({})", format_expr_list(args)),
1565        ExprKind::Files(args) => format!("files({})", format_expr_list(args)),
1566        ExprKind::Filesf(args) => format!("filesf({})", format_expr_list(args)),
1567        ExprKind::FilesfRecursive(args) => format!("fr({})", format_expr_list(args)),
1568        ExprKind::Dirs(args) => format!("dirs({})", format_expr_list(args)),
1569        ExprKind::DirsRecursive(args) => format!("dr({})", format_expr_list(args)),
1570        ExprKind::SymLinks(args) => format!("sym_links({})", format_expr_list(args)),
1571        ExprKind::Sockets(args) => format!("sockets({})", format_expr_list(args)),
1572        ExprKind::Pipes(args) => format!("pipes({})", format_expr_list(args)),
1573        ExprKind::BlockDevices(args) => format!("block_devices({})", format_expr_list(args)),
1574        ExprKind::CharDevices(args) => format!("char_devices({})", format_expr_list(args)),
1575        ExprKind::Executables(args) => format!("exe({})", format_expr_list(args)),
1576        ExprKind::GlobPar { args, progress } => {
1577            let base = format!("glob_par({})", format_expr_list(args));
1578            match progress {
1579                Some(p) => format!("{}, progress => {}", base, format_expr(p)),
1580                None => base,
1581            }
1582        }
1583        ExprKind::ParSed { args, progress } => {
1584            let base = format!("par_sed({})", format_expr_list(args));
1585            match progress {
1586                Some(p) => format!("{}, progress => {}", base, format_expr(p)),
1587                None => base,
1588            }
1589        }
1590        ExprKind::Bless { ref_expr, class } => match class {
1591            Some(c) => format!("bless({}, {})", format_expr(ref_expr), format_expr(c)),
1592            None => format!("bless({})", format_expr(ref_expr)),
1593        },
1594        ExprKind::Caller(opt) => match opt {
1595            Some(e) => format!("caller({})", format_expr(e)),
1596            None => "caller".to_string(),
1597        },
1598        ExprKind::Wantarray => "wantarray".to_string(),
1599        ExprKind::List(exprs) => format!("({})", format_expr_list(exprs)),
1600        ExprKind::PostfixIf { expr, condition } => {
1601            format!("{} if {}", format_expr(expr), format_expr(condition))
1602        }
1603        ExprKind::PostfixUnless { expr, condition } => {
1604            format!("{} unless {}", format_expr(expr), format_expr(condition))
1605        }
1606        ExprKind::PostfixWhile { expr, condition } => {
1607            format!("{} while {}", format_expr(expr), format_expr(condition))
1608        }
1609        ExprKind::PostfixUntil { expr, condition } => {
1610            format!("{} until {}", format_expr(expr), format_expr(condition))
1611        }
1612        ExprKind::PostfixForeach { expr, list } => {
1613            format!("{} foreach {}", format_expr(expr), format_expr(list))
1614        }
1615        ExprKind::AlgebraicMatch { subject, arms } => {
1616            let arms_s = arms
1617                .iter()
1618                .map(|a| {
1619                    let guard_s = a
1620                        .guard
1621                        .as_ref()
1622                        .map(|g| format!(" if {}", format_expr(g)))
1623                        .unwrap_or_default();
1624                    format!(
1625                        "{}{} => {}",
1626                        format_match_pattern(&a.pattern),
1627                        guard_s,
1628                        format_expr(&a.body)
1629                    )
1630                })
1631                .collect::<Vec<_>>()
1632                .join(", ");
1633            format!("match ({}) {{ {} }}", format_expr(subject), arms_s)
1634        }
1635        ExprKind::RetryBlock {
1636            body,
1637            times,
1638            backoff,
1639        } => {
1640            let bo = match backoff {
1641                crate::ast::RetryBackoff::None => "none",
1642                crate::ast::RetryBackoff::Linear => "linear",
1643                crate::ast::RetryBackoff::Exponential => "exponential",
1644            };
1645            format!(
1646                "retry {{ {} }} times => {}, backoff => {}",
1647                format_block_inline(body),
1648                format_expr(times),
1649                bo
1650            )
1651        }
1652        ExprKind::RateLimitBlock {
1653            max, window, body, ..
1654        } => {
1655            format!(
1656                "rate_limit({}, {}) {{ {} }}",
1657                format_expr(max),
1658                format_expr(window),
1659                format_block_inline(body)
1660            )
1661        }
1662        ExprKind::EveryBlock { interval, body } => {
1663            format!(
1664                "every({}) {{ {} }}",
1665                format_expr(interval),
1666                format_block_inline(body)
1667            )
1668        }
1669        ExprKind::GenBlock { body } => {
1670            format!("gen {{ {} }}", format_block_inline(body))
1671        }
1672        ExprKind::Yield(e) => {
1673            format!("yield {}", format_expr(e))
1674        }
1675        ExprKind::Spinner { message, body } => {
1676            format!(
1677                "spinner {} {{ {} }}",
1678                format_expr(message),
1679                body.iter()
1680                    .map(format_statement)
1681                    .collect::<Vec<_>>()
1682                    .join("; ")
1683            )
1684        }
1685        ExprKind::MyExpr { keyword, decls } => {
1686            // Render `my $x = …` etc. inline. Single-decl is the common case
1687            // (e.g. `if (my $x = …)`); list-decl reuses the same formatter.
1688            let parts: Vec<String> = decls
1689                .iter()
1690                .map(|d| {
1691                    let sigil = match d.sigil {
1692                        crate::ast::Sigil::Scalar => '$',
1693                        crate::ast::Sigil::Array => '@',
1694                        crate::ast::Sigil::Hash => '%',
1695                        crate::ast::Sigil::Typeglob => '*',
1696                    };
1697                    let mut s = format!("{}{}", sigil, d.name);
1698                    if let Some(init) = &d.initializer {
1699                        s.push_str(" = ");
1700                        s.push_str(&format_expr(init));
1701                    }
1702                    s
1703                })
1704                .collect();
1705            if parts.len() == 1 {
1706                format!("{} {}", keyword, parts[0])
1707            } else {
1708                format!("{} ({})", keyword, parts.join(", "))
1709            }
1710        }
1711    }
1712}
1713
1714pub(crate) fn format_match_pattern(p: &crate::ast::MatchPattern) -> String {
1715    use crate::ast::{MatchArrayElem, MatchHashPair, MatchPattern};
1716    match p {
1717        MatchPattern::Any => "_".to_string(),
1718        MatchPattern::Regex { pattern, flags } => {
1719            if flags.is_empty() {
1720                format!("/{}/", pattern)
1721            } else {
1722                format!("/{}/{}/", pattern, flags)
1723            }
1724        }
1725        MatchPattern::Value(e) => format_expr(e),
1726        MatchPattern::Array(elems) => {
1727            let inner = elems
1728                .iter()
1729                .map(|x| match x {
1730                    MatchArrayElem::Expr(e) => format_expr(e),
1731                    MatchArrayElem::CaptureScalar(name) => format!("${}", name),
1732                    MatchArrayElem::Rest => "*".to_string(),
1733                    MatchArrayElem::RestBind(name) => format!("@{}", name),
1734                })
1735                .collect::<Vec<_>>()
1736                .join(", ");
1737            format!("[{}]", inner)
1738        }
1739        MatchPattern::Hash(pairs) => {
1740            let inner = pairs
1741                .iter()
1742                .map(|pair| match pair {
1743                    MatchHashPair::KeyOnly { key } => {
1744                        format!("{} => _", format_expr(key))
1745                    }
1746                    MatchHashPair::Capture { key, name } => {
1747                        format!("{} => ${}", format_expr(key), name)
1748                    }
1749                })
1750                .collect::<Vec<_>>()
1751                .join(", ");
1752            format!("{{ {} }}", inner)
1753        }
1754        MatchPattern::OptionSome(name) => format!("Some({})", name),
1755    }
1756}
1757
1758#[cfg(test)]
1759mod tests {
1760    use super::*;
1761    use crate::parse;
1762
1763    #[test]
1764    fn format_program_expression_statement_includes_binop() {
1765        let p = parse("2 + 3").expect("parse");
1766        let out = format_program(&p);
1767        assert!(
1768            out.contains("2") && out.contains("3") && out.contains("+"),
1769            "unexpected format: {out}"
1770        );
1771    }
1772
1773    #[test]
1774    fn format_program_if_block() {
1775        let p = parse("if (1) { 2; }").expect("parse");
1776        let out = format_program(&p);
1777        assert!(out.contains("if") && out.contains('1'));
1778    }
1779
1780    #[test]
1781    fn format_program_package_line() {
1782        let p = parse("package Foo::Bar").expect("parse");
1783        let out = format_program(&p);
1784        assert!(out.contains("package Foo::Bar"));
1785    }
1786
1787    #[test]
1788    fn format_program_string_literal_escapes_quote() {
1789        let p = parse(r#"my $s = "a\"b""#).expect("parse");
1790        let out = format_program(&p);
1791        assert!(out.contains("\\\""), "expected escaped quote in: {out}");
1792    }
1793}