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