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