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