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