1#![allow(unused_variables)] use crate::ast::*;
7
8const INDENT: &str = " ";
9
10pub 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
568fn 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
669pub(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
689pub(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
729pub 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::DistReduceExpr {
1180 cluster,
1181 extract_block,
1182 list,
1183 } => format!(
1184 "dist_reduce on {} {{ {} }} {}",
1185 format_expr(cluster),
1186 format_block_inline(extract_block),
1187 format_expr(list)
1188 ),
1189 ExprKind::PForExpr {
1190 block,
1191 list,
1192 progress,
1193 } => {
1194 let base = format!(
1195 "pfor {{ {} }} {}",
1196 format_block_inline(block),
1197 format_expr(list)
1198 );
1199 match progress {
1200 Some(p) => format!("{}, progress => {}", base, format_expr(p)),
1201 None => base,
1202 }
1203 }
1204 ExprKind::ParLinesExpr {
1205 path,
1206 callback,
1207 progress,
1208 } => match progress {
1209 Some(p) => format!(
1210 "par_lines({}, {}, progress => {})",
1211 format_expr(path),
1212 format_expr(callback),
1213 format_expr(p)
1214 ),
1215 None => format!(
1216 "par_lines({}, {})",
1217 format_expr(path),
1218 format_expr(callback)
1219 ),
1220 },
1221 ExprKind::ParWalkExpr {
1222 path,
1223 callback,
1224 progress,
1225 } => match progress {
1226 Some(p) => format!(
1227 "par_walk({}, {}, progress => {})",
1228 format_expr(path),
1229 format_expr(callback),
1230 format_expr(p)
1231 ),
1232 None => format!("par_walk({}, {})", format_expr(path), format_expr(callback)),
1233 },
1234 ExprKind::PwatchExpr { path, callback } => {
1235 format!("pwatch({}, {})", format_expr(path), format_expr(callback))
1236 }
1237 ExprKind::PSortExpr {
1238 cmp,
1239 list,
1240 progress,
1241 } => {
1242 let base = match cmp {
1243 Some(b) => format!(
1244 "psort {{ {} }} {}",
1245 format_block_inline(b),
1246 format_expr(list)
1247 ),
1248 None => format!("psort {}", format_expr(list)),
1249 };
1250 match progress {
1251 Some(p) => format!("{}, progress => {}", base, format_expr(p)),
1252 None => base,
1253 }
1254 }
1255 ExprKind::ReduceExpr { block, list } => format!(
1256 "reduce {{ {} }} {}",
1257 format_block_inline(block),
1258 format_expr(list)
1259 ),
1260 ExprKind::PReduceExpr {
1261 block,
1262 list,
1263 progress,
1264 } => {
1265 let base = format!(
1266 "preduce {{ {} }} {}",
1267 format_block_inline(block),
1268 format_expr(list)
1269 );
1270 match progress {
1271 Some(p) => format!("{}, progress => {}", base, format_expr(p)),
1272 None => base,
1273 }
1274 }
1275 ExprKind::PReduceInitExpr {
1276 init,
1277 block,
1278 list,
1279 progress,
1280 } => {
1281 let base = format!(
1282 "preduce_init {}, {{ {} }} {}",
1283 format_expr(init),
1284 format_block_inline(block),
1285 format_expr(list)
1286 );
1287 match progress {
1288 Some(p) => format!("{}, progress => {}", base, format_expr(p)),
1289 None => base,
1290 }
1291 }
1292 ExprKind::PMapReduceExpr {
1293 map_block,
1294 reduce_block,
1295 list,
1296 progress,
1297 } => {
1298 let base = format!(
1299 "pmap_reduce {{ {} }} {{ {} }} {}",
1300 format_block_inline(map_block),
1301 format_block_inline(reduce_block),
1302 format_expr(list)
1303 );
1304 match progress {
1305 Some(p) => format!("{}, progress => {}", base, format_expr(p)),
1306 None => base,
1307 }
1308 }
1309 ExprKind::PcacheExpr {
1310 block,
1311 list,
1312 progress,
1313 } => {
1314 let base = format!(
1315 "pcache {{ {} }} {}",
1316 format_block_inline(block),
1317 format_expr(list)
1318 );
1319 match progress {
1320 Some(p) => format!("{}, progress => {}", base, format_expr(p)),
1321 None => base,
1322 }
1323 }
1324 ExprKind::PselectExpr { receivers, timeout } => {
1325 let inner = receivers
1326 .iter()
1327 .map(format_expr)
1328 .collect::<Vec<_>>()
1329 .join(", ");
1330 match timeout {
1331 Some(t) => format!("pselect({}, timeout => {})", inner, format_expr(t)),
1332 None => format!("pselect({})", inner),
1333 }
1334 }
1335 ExprKind::FanExpr {
1336 count,
1337 block,
1338 progress,
1339 capture,
1340 } => {
1341 let kw = if *capture { "fan_cap" } else { "fan" };
1342 let base = match count {
1343 Some(c) => format!(
1344 "{} {} {{ {} }}",
1345 kw,
1346 format_expr(c),
1347 format_block_inline(block)
1348 ),
1349 None => format!("{} {{ {} }}", kw, format_block_inline(block)),
1350 };
1351 match progress {
1352 Some(p) => format!("{}, progress => {}", base, format_expr(p)),
1353 None => base,
1354 }
1355 }
1356 ExprKind::AsyncBlock { body } => format!("async {{ {} }}", format_block_inline(body)),
1357 ExprKind::SpawnBlock { body } => format!("spawn {{ {} }}", format_block_inline(body)),
1358 ExprKind::Trace { body } => format!("trace {{ {} }}", format_block_inline(body)),
1359 ExprKind::Timer { body } => format!("timer {{ {} }}", format_block_inline(body)),
1360 ExprKind::Bench { body, times } => format!(
1361 "bench {{ {} }} {}",
1362 format_block_inline(body),
1363 format_expr(times)
1364 ),
1365 ExprKind::Await(e) => format!("await {}", format_expr(e)),
1366 ExprKind::Slurp(e) => format!("slurp {}", format_expr(e)),
1367 ExprKind::Capture(e) => format!("capture {}", format_expr(e)),
1368 ExprKind::Qx(e) => format!("qx {}", format_expr(e)),
1369 ExprKind::FetchUrl(e) => format!("fetch_url {}", format_expr(e)),
1370 ExprKind::Pchannel { capacity } => match capacity {
1371 Some(c) => format!("pchannel({})", format_expr(c)),
1372 None => "pchannel()".to_string(),
1373 },
1374 ExprKind::Push { array, values } => {
1375 format!("push({}, {})", format_expr(array), format_expr_list(values))
1376 }
1377 ExprKind::Pop(e) => format!("pop {}", format_expr(e)),
1378 ExprKind::Shift(e) => format!("shift {}", format_expr(e)),
1379 ExprKind::Unshift { array, values } => format!(
1380 "unshift({}, {})",
1381 format_expr(array),
1382 format_expr_list(values)
1383 ),
1384 ExprKind::Splice {
1385 array,
1386 offset,
1387 length,
1388 replacement,
1389 } => {
1390 let mut parts = vec![format_expr(array)];
1391 if let Some(o) = offset {
1392 parts.push(format_expr(o));
1393 }
1394 if let Some(l) = length {
1395 parts.push(format_expr(l));
1396 }
1397 if !replacement.is_empty() {
1398 parts.push(format_expr_list(replacement));
1399 }
1400 format!("splice({})", parts.join(", "))
1401 }
1402 ExprKind::Delete(e) => format!("delete {}", format_expr(e)),
1403 ExprKind::Exists(e) => format!("exists {}", format_expr(e)),
1404 ExprKind::Keys(e) => format!("keys {}", format_expr(e)),
1405 ExprKind::Values(e) => format!("values {}", format_expr(e)),
1406 ExprKind::Each(e) => format!("each {}", format_expr(e)),
1407 ExprKind::Chomp(e) => format!("chomp {}", format_expr(e)),
1408 ExprKind::Chop(e) => format!("chop {}", format_expr(e)),
1409 ExprKind::Length(e) => format!("length {}", format_expr(e)),
1410 ExprKind::Substr {
1411 string,
1412 offset,
1413 length,
1414 replacement,
1415 } => {
1416 let mut parts = vec![format_expr(string), format_expr(offset)];
1417 if let Some(l) = length {
1418 parts.push(format_expr(l));
1419 }
1420 if let Some(r) = replacement {
1421 parts.push(format_expr(r));
1422 }
1423 format!("substr({})", parts.join(", "))
1424 }
1425 ExprKind::Index {
1426 string,
1427 substr,
1428 position,
1429 } => match position {
1430 Some(p) => format!(
1431 "index({}, {}, {})",
1432 format_expr(string),
1433 format_expr(substr),
1434 format_expr(p)
1435 ),
1436 None => format!("index({}, {})", format_expr(string), format_expr(substr)),
1437 },
1438 ExprKind::Rindex {
1439 string,
1440 substr,
1441 position,
1442 } => match position {
1443 Some(p) => format!(
1444 "rindex({}, {}, {})",
1445 format_expr(string),
1446 format_expr(substr),
1447 format_expr(p)
1448 ),
1449 None => format!("rindex({}, {})", format_expr(string), format_expr(substr)),
1450 },
1451 ExprKind::Sprintf { format, args } => format!(
1452 "sprintf({}, {})",
1453 format_expr(format),
1454 format_expr_list(args)
1455 ),
1456 ExprKind::Abs(e) => format!("abs {}", format_expr(e)),
1457 ExprKind::Int(e) => format!("int {}", format_expr(e)),
1458 ExprKind::Sqrt(e) => format!("sqrt {}", format_expr(e)),
1459 ExprKind::Sin(e) => format!("sin {}", format_expr(e)),
1460 ExprKind::Cos(e) => format!("cos {}", format_expr(e)),
1461 ExprKind::Atan2 { y, x } => format!("atan2({}, {})", format_expr(y), format_expr(x)),
1462 ExprKind::Exp(e) => format!("exp {}", format_expr(e)),
1463 ExprKind::Log(e) => format!("log {}", format_expr(e)),
1464 ExprKind::Rand(opt) => match opt {
1465 Some(e) => format!("rand({})", format_expr(e)),
1466 None => "rand".to_string(),
1467 },
1468 ExprKind::Srand(opt) => match opt {
1469 Some(e) => format!("srand({})", format_expr(e)),
1470 None => "srand".to_string(),
1471 },
1472 ExprKind::Hex(e) => format!("hex {}", format_expr(e)),
1473 ExprKind::Oct(e) => format!("oct {}", format_expr(e)),
1474 ExprKind::Lc(e) => format!("lc {}", format_expr(e)),
1475 ExprKind::Uc(e) => format!("uc {}", format_expr(e)),
1476 ExprKind::Lcfirst(e) => format!("lcfirst {}", format_expr(e)),
1477 ExprKind::Ucfirst(e) => format!("ucfirst {}", format_expr(e)),
1478 ExprKind::Fc(e) => format!("fc {}", format_expr(e)),
1479 ExprKind::Crypt { plaintext, salt } => {
1480 format!("crypt({}, {})", format_expr(plaintext), format_expr(salt))
1481 }
1482 ExprKind::Pos(opt) => match opt {
1483 Some(e) => format!("pos({})", format_expr(e)),
1484 None => "pos".to_string(),
1485 },
1486 ExprKind::Study(e) => format!("study {}", format_expr(e)),
1487 ExprKind::Defined(e) => format!("defined {}", format_expr(e)),
1488 ExprKind::Ref(e) => format!("ref {}", format_expr(e)),
1489 ExprKind::ScalarContext(e) => format!("scalar {}", format_expr(e)),
1490 ExprKind::Chr(e) => format!("chr {}", format_expr(e)),
1491 ExprKind::Ord(e) => format!("ord {}", format_expr(e)),
1492 ExprKind::OpenMyHandle { name } => format!("my ${}", name),
1493 ExprKind::Open { handle, mode, file } => match file {
1494 Some(f) => format!(
1495 "open({}, {}, {})",
1496 format_expr(handle),
1497 format_expr(mode),
1498 format_expr(f)
1499 ),
1500 None => format!("open({}, {})", format_expr(handle), format_expr(mode)),
1501 },
1502 ExprKind::Close(e) => format!("close {}", format_expr(e)),
1503 ExprKind::ReadLine(handle) => match handle {
1504 Some(h) => {
1505 if h.starts_with(|c: char| c.is_uppercase()) {
1506 format!("<{}>", h)
1507 } else {
1508 format!("<${}>", h)
1509 }
1510 }
1511 None => "<STDIN>".to_string(),
1512 },
1513 ExprKind::Eof(opt) => match opt {
1514 Some(e) => format!("eof({})", format_expr(e)),
1515 None => "eof".to_string(),
1516 },
1517 ExprKind::Opendir { handle, path } => {
1518 format!("opendir({}, {})", format_expr(handle), format_expr(path))
1519 }
1520 ExprKind::Readdir(e) => format!("readdir {}", format_expr(e)),
1521 ExprKind::Closedir(e) => format!("closedir {}", format_expr(e)),
1522 ExprKind::Rewinddir(e) => format!("rewinddir {}", format_expr(e)),
1523 ExprKind::Telldir(e) => format!("telldir {}", format_expr(e)),
1524 ExprKind::Seekdir { handle, position } => format!(
1525 "seekdir({}, {})",
1526 format_expr(handle),
1527 format_expr(position)
1528 ),
1529 ExprKind::FileTest { op, expr } => format!("-{}{}", op, format_expr(expr)),
1530 ExprKind::System(args) => format!("system({})", format_expr_list(args)),
1531 ExprKind::Exec(args) => format!("exec({})", format_expr_list(args)),
1532 ExprKind::Eval(e) => format!("eval {}", format_expr(e)),
1533 ExprKind::Do(e) => format!("do {}", format_expr(e)),
1534 ExprKind::Require(e) => format!("require {}", format_expr(e)),
1535 ExprKind::Exit(opt) => match opt {
1536 Some(e) => format!("exit({})", format_expr(e)),
1537 None => "exit".to_string(),
1538 },
1539 ExprKind::Chdir(e) => format!("chdir {}", format_expr(e)),
1540 ExprKind::Mkdir { path, mode } => match mode {
1541 Some(m) => format!("mkdir({}, {})", format_expr(path), format_expr(m)),
1542 None => format!("mkdir({})", format_expr(path)),
1543 },
1544 ExprKind::Unlink(args) => format!("unlink({})", format_expr_list(args)),
1545 ExprKind::Rename { old, new } => {
1546 format!("rename({}, {})", format_expr(old), format_expr(new))
1547 }
1548 ExprKind::Chmod(args) => format!("chmod({})", format_expr_list(args)),
1549 ExprKind::Chown(args) => format!("chown({})", format_expr_list(args)),
1550 ExprKind::Stat(e) => format!("stat {}", format_expr(e)),
1551 ExprKind::Lstat(e) => format!("lstat {}", format_expr(e)),
1552 ExprKind::Link { old, new } => format!("link({}, {})", format_expr(old), format_expr(new)),
1553 ExprKind::Symlink { old, new } => {
1554 format!("symlink({}, {})", format_expr(old), format_expr(new))
1555 }
1556 ExprKind::Readlink(e) => format!("readlink {}", format_expr(e)),
1557 ExprKind::Glob(args) => format!("glob({})", format_expr_list(args)),
1558 ExprKind::Files(args) => format!("files({})", format_expr_list(args)),
1559 ExprKind::Filesf(args) => format!("filesf({})", format_expr_list(args)),
1560 ExprKind::FilesfRecursive(args) => format!("fr({})", format_expr_list(args)),
1561 ExprKind::Dirs(args) => format!("dirs({})", format_expr_list(args)),
1562 ExprKind::DirsRecursive(args) => format!("dr({})", format_expr_list(args)),
1563 ExprKind::SymLinks(args) => format!("sym_links({})", format_expr_list(args)),
1564 ExprKind::Sockets(args) => format!("sockets({})", format_expr_list(args)),
1565 ExprKind::Pipes(args) => format!("pipes({})", format_expr_list(args)),
1566 ExprKind::BlockDevices(args) => format!("block_devices({})", format_expr_list(args)),
1567 ExprKind::CharDevices(args) => format!("char_devices({})", format_expr_list(args)),
1568 ExprKind::Executables(args) => format!("exe({})", format_expr_list(args)),
1569 ExprKind::GlobPar { args, progress } => {
1570 let base = format!("glob_par({})", format_expr_list(args));
1571 match progress {
1572 Some(p) => format!("{}, progress => {}", base, format_expr(p)),
1573 None => base,
1574 }
1575 }
1576 ExprKind::ParSed { args, progress } => {
1577 let base = format!("par_sed({})", format_expr_list(args));
1578 match progress {
1579 Some(p) => format!("{}, progress => {}", base, format_expr(p)),
1580 None => base,
1581 }
1582 }
1583 ExprKind::Bless { ref_expr, class } => match class {
1584 Some(c) => format!("bless({}, {})", format_expr(ref_expr), format_expr(c)),
1585 None => format!("bless({})", format_expr(ref_expr)),
1586 },
1587 ExprKind::Caller(opt) => match opt {
1588 Some(e) => format!("caller({})", format_expr(e)),
1589 None => "caller".to_string(),
1590 },
1591 ExprKind::Wantarray => "wantarray".to_string(),
1592 ExprKind::List(exprs) => format!("({})", format_expr_list(exprs)),
1593 ExprKind::PostfixIf { expr, condition } => {
1594 format!("{} if {}", format_expr(expr), format_expr(condition))
1595 }
1596 ExprKind::PostfixUnless { expr, condition } => {
1597 format!("{} unless {}", format_expr(expr), format_expr(condition))
1598 }
1599 ExprKind::PostfixWhile { expr, condition } => {
1600 format!("{} while {}", format_expr(expr), format_expr(condition))
1601 }
1602 ExprKind::PostfixUntil { expr, condition } => {
1603 format!("{} until {}", format_expr(expr), format_expr(condition))
1604 }
1605 ExprKind::PostfixForeach { expr, list } => {
1606 format!("{} foreach {}", format_expr(expr), format_expr(list))
1607 }
1608 ExprKind::AlgebraicMatch { subject, arms } => {
1609 let arms_s = arms
1610 .iter()
1611 .map(|a| {
1612 let guard_s = a
1613 .guard
1614 .as_ref()
1615 .map(|g| format!(" if {}", format_expr(g)))
1616 .unwrap_or_default();
1617 format!(
1618 "{}{} => {}",
1619 format_match_pattern(&a.pattern),
1620 guard_s,
1621 format_expr(&a.body)
1622 )
1623 })
1624 .collect::<Vec<_>>()
1625 .join(", ");
1626 format!("match ({}) {{ {} }}", format_expr(subject), arms_s)
1627 }
1628 ExprKind::RetryBlock {
1629 body,
1630 times,
1631 backoff,
1632 } => {
1633 let bo = match backoff {
1634 crate::ast::RetryBackoff::None => "none",
1635 crate::ast::RetryBackoff::Linear => "linear",
1636 crate::ast::RetryBackoff::Exponential => "exponential",
1637 };
1638 format!(
1639 "retry {{ {} }} times => {}, backoff => {}",
1640 format_block_inline(body),
1641 format_expr(times),
1642 bo
1643 )
1644 }
1645 ExprKind::RateLimitBlock {
1646 max, window, body, ..
1647 } => {
1648 format!(
1649 "rate_limit({}, {}) {{ {} }}",
1650 format_expr(max),
1651 format_expr(window),
1652 format_block_inline(body)
1653 )
1654 }
1655 ExprKind::EveryBlock { interval, body } => {
1656 format!(
1657 "every({}) {{ {} }}",
1658 format_expr(interval),
1659 format_block_inline(body)
1660 )
1661 }
1662 ExprKind::GenBlock { body } => {
1663 format!("gen {{ {} }}", format_block_inline(body))
1664 }
1665 ExprKind::Yield(e) => {
1666 format!("yield {}", format_expr(e))
1667 }
1668 ExprKind::Spinner { message, body } => {
1669 format!(
1670 "spinner {} {{ {} }}",
1671 format_expr(message),
1672 body.iter()
1673 .map(format_statement)
1674 .collect::<Vec<_>>()
1675 .join("; ")
1676 )
1677 }
1678 ExprKind::MyExpr { keyword, decls } => {
1679 let parts: Vec<String> = decls
1682 .iter()
1683 .map(|d| {
1684 let sigil = match d.sigil {
1685 crate::ast::Sigil::Scalar => '$',
1686 crate::ast::Sigil::Array => '@',
1687 crate::ast::Sigil::Hash => '%',
1688 crate::ast::Sigil::Typeglob => '*',
1689 };
1690 let mut s = format!("{}{}", sigil, d.name);
1691 if let Some(init) = &d.initializer {
1692 s.push_str(" = ");
1693 s.push_str(&format_expr(init));
1694 }
1695 s
1696 })
1697 .collect();
1698 if parts.len() == 1 {
1699 format!("{} {}", keyword, parts[0])
1700 } else {
1701 format!("{} ({})", keyword, parts.join(", "))
1702 }
1703 }
1704 }
1705}
1706
1707pub(crate) fn format_match_pattern(p: &crate::ast::MatchPattern) -> String {
1708 use crate::ast::{MatchArrayElem, MatchHashPair, MatchPattern};
1709 match p {
1710 MatchPattern::Any => "_".to_string(),
1711 MatchPattern::Regex { pattern, flags } => {
1712 if flags.is_empty() {
1713 format!("/{}/", pattern)
1714 } else {
1715 format!("/{}/{}/", pattern, flags)
1716 }
1717 }
1718 MatchPattern::Value(e) => format_expr(e),
1719 MatchPattern::Array(elems) => {
1720 let inner = elems
1721 .iter()
1722 .map(|x| match x {
1723 MatchArrayElem::Expr(e) => format_expr(e),
1724 MatchArrayElem::CaptureScalar(name) => format!("${}", name),
1725 MatchArrayElem::Rest => "*".to_string(),
1726 MatchArrayElem::RestBind(name) => format!("@{}", name),
1727 })
1728 .collect::<Vec<_>>()
1729 .join(", ");
1730 format!("[{}]", inner)
1731 }
1732 MatchPattern::Hash(pairs) => {
1733 let inner = pairs
1734 .iter()
1735 .map(|pair| match pair {
1736 MatchHashPair::KeyOnly { key } => {
1737 format!("{} => _", format_expr(key))
1738 }
1739 MatchHashPair::Capture { key, name } => {
1740 format!("{} => ${}", format_expr(key), name)
1741 }
1742 })
1743 .collect::<Vec<_>>()
1744 .join(", ");
1745 format!("{{ {} }}", inner)
1746 }
1747 MatchPattern::OptionSome(name) => format!("Some({})", name),
1748 }
1749}
1750
1751#[cfg(test)]
1752mod tests {
1753 use super::*;
1754 use crate::parse;
1755
1756 #[test]
1757 fn format_program_expression_statement_includes_binop() {
1758 let p = parse("2 + 3").expect("parse");
1759 let out = format_program(&p);
1760 assert!(
1761 out.contains("2") && out.contains("3") && out.contains("+"),
1762 "unexpected format: {out}"
1763 );
1764 }
1765
1766 #[test]
1767 fn format_program_if_block() {
1768 let p = parse("if (1) { 2; }").expect("parse");
1769 let out = format_program(&p);
1770 assert!(out.contains("if") && out.contains('1'));
1771 }
1772
1773 #[test]
1774 fn format_program_package_line() {
1775 let p = parse("package Foo::Bar").expect("parse");
1776 let out = format_program(&p);
1777 assert!(out.contains("package Foo::Bar"));
1778 }
1779
1780 #[test]
1781 fn format_program_string_literal_escapes_quote() {
1782 let p = parse(r#"my $s = "a\"b""#).expect("parse");
1783 let out = format_program(&p);
1784 assert!(out.contains("\\\""), "expected escaped quote in: {out}");
1785 }
1786}