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