1#![allow(unused_variables)]
15
16use crate::ast::*;
17use crate::fmt;
18use std::cell::RefCell;
19
20const INDENT: &str = " ";
21
22thread_local! {
23 static OUTPUT_DELIM: RefCell<Option<char>> = const { RefCell::new(None) };
24}
25
26#[derive(Debug, Clone, Default)]
28pub struct ConvertOptions {
29 pub output_delim: Option<char>,
31}
32
33fn get_output_delim() -> Option<char> {
34 OUTPUT_DELIM.with(|d| *d.borrow())
35}
36
37fn set_output_delim(delim: Option<char>) {
38 OUTPUT_DELIM.with(|d| *d.borrow_mut() = delim);
39}
40
41fn choose_delim(original: char) -> char {
43 get_output_delim().unwrap_or(original)
44}
45
46pub fn convert_program(p: &Program) -> String {
50 convert_program_with_options(p, &ConvertOptions::default())
51}
52
53pub fn convert_program_with_options(p: &Program, opts: &ConvertOptions) -> String {
55 set_output_delim(opts.output_delim);
56 let body = convert_statements(&p.statements, 0);
57 set_output_delim(None);
58 format!("#!/usr/bin/env stryke\n{}", body)
59}
60
61fn convert_block(b: &Block, depth: usize) -> String {
64 convert_statements(b, depth)
65}
66
67fn convert_statements(stmts: &[Statement], depth: usize) -> String {
69 let mut out = Vec::new();
70 let mut i = 0;
71 while i < stmts.len() {
72 if let Some(merged) = try_merge_say_print(&stmts[i..], depth) {
74 out.push(merged);
75 i += 2; } else {
77 out.push(convert_statement(&stmts[i], depth));
78 i += 1;
79 }
80 }
81 out.join("\n")
82}
83
84fn try_merge_say_print(stmts: &[Statement], depth: usize) -> Option<String> {
87 if stmts.len() < 2 {
88 return None;
89 }
90 let pfx = indent(depth);
91
92 let (is_say, handle) = match &stmts[0].kind {
94 StmtKind::Expression(e) => match &e.kind {
95 ExprKind::Say { handle, args } if args.is_empty() => (true, handle),
96 ExprKind::Print { handle, args } if args.is_empty() => (false, handle),
97 _ => return None,
98 },
99 _ => return None,
100 };
101
102 if handle.is_some() {
104 return None;
105 }
106
107 let str_expr = match &stmts[1].kind {
109 StmtKind::Expression(e) => e,
110 _ => return None,
111 };
112
113 let cmd = if is_say { "p" } else { "print" };
115 let arg = convert_expr_top(str_expr);
116 Some(format!("{}{} {}", pfx, cmd, arg))
117}
118
119fn indent(depth: usize) -> String {
121 INDENT.repeat(depth)
122}
123
124fn convert_statement(s: &Statement, depth: usize) -> String {
125 let lab = s
126 .label
127 .as_ref()
128 .map(|l| format!("{}: ", l))
129 .unwrap_or_default();
130 let pfx = indent(depth);
131 let body = match &s.kind {
132 StmtKind::Expression(e) => convert_expr_top(e),
133 StmtKind::If {
134 condition,
135 body,
136 elsifs,
137 else_block,
138 } => {
139 let mut s = format!(
140 "if ({}) {{\n{}\n{}}}",
141 convert_expr_top(condition),
142 convert_block(body, depth + 1),
143 pfx
144 );
145 for (c, b) in elsifs {
146 s.push_str(&format!(
147 " elsif ({}) {{\n{}\n{}}}",
148 convert_expr_top(c),
149 convert_block(b, depth + 1),
150 pfx
151 ));
152 }
153 if let Some(eb) = else_block {
154 s.push_str(&format!(
155 " else {{\n{}\n{}}}",
156 convert_block(eb, depth + 1),
157 pfx
158 ));
159 }
160 s
161 }
162 StmtKind::Unless {
163 condition,
164 body,
165 else_block,
166 } => {
167 let mut s = format!(
168 "unless ({}) {{\n{}\n{}}}",
169 convert_expr_top(condition),
170 convert_block(body, depth + 1),
171 pfx
172 );
173 if let Some(eb) = else_block {
174 s.push_str(&format!(
175 " else {{\n{}\n{}}}",
176 convert_block(eb, depth + 1),
177 pfx
178 ));
179 }
180 s
181 }
182 StmtKind::While {
183 condition,
184 body,
185 label,
186 continue_block,
187 } => {
188 let lb = label
189 .as_ref()
190 .map(|l| format!("{}: ", l))
191 .unwrap_or_default();
192 let mut s = format!(
193 "{}while ({}) {{\n{}\n{}}}",
194 lb,
195 convert_expr_top(condition),
196 convert_block(body, depth + 1),
197 pfx
198 );
199 if let Some(cb) = continue_block {
200 s.push_str(&format!(
201 " continue {{\n{}\n{}}}",
202 convert_block(cb, depth + 1),
203 pfx
204 ));
205 }
206 s
207 }
208 StmtKind::Until {
209 condition,
210 body,
211 label,
212 continue_block,
213 } => {
214 let lb = label
215 .as_ref()
216 .map(|l| format!("{}: ", l))
217 .unwrap_or_default();
218 let mut s = format!(
219 "{}until ({}) {{\n{}\n{}}}",
220 lb,
221 convert_expr_top(condition),
222 convert_block(body, depth + 1),
223 pfx
224 );
225 if let Some(cb) = continue_block {
226 s.push_str(&format!(
227 " continue {{\n{}\n{}}}",
228 convert_block(cb, depth + 1),
229 pfx
230 ));
231 }
232 s
233 }
234 StmtKind::DoWhile { body, condition } => {
235 format!(
236 "do {{\n{}\n{}}} while ({})",
237 convert_block(body, depth + 1),
238 pfx,
239 convert_expr_top(condition)
240 )
241 }
242 StmtKind::For {
243 init,
244 condition,
245 step,
246 body,
247 label,
248 continue_block,
249 } => {
250 let lb = label
251 .as_ref()
252 .map(|l| format!("{}: ", l))
253 .unwrap_or_default();
254 let ini = init
255 .as_ref()
256 .map(|s| convert_statement_body(s))
257 .unwrap_or_default();
258 let cond = condition.as_ref().map(convert_expr).unwrap_or_default();
259 let st = step.as_ref().map(convert_expr).unwrap_or_default();
260 let mut s = format!(
261 "{}for ({}; {}; {}) {{\n{}\n{}}}",
262 lb,
263 ini,
264 cond,
265 st,
266 convert_block(body, depth + 1),
267 pfx
268 );
269 if let Some(cb) = continue_block {
270 s.push_str(&format!(
271 " continue {{\n{}\n{}}}",
272 convert_block(cb, depth + 1),
273 pfx
274 ));
275 }
276 s
277 }
278 StmtKind::Foreach {
279 var,
280 list,
281 body,
282 label,
283 continue_block,
284 } => {
285 let lb = label
286 .as_ref()
287 .map(|l| format!("{}: ", l))
288 .unwrap_or_default();
289 let mut s = format!(
290 "{}for ${} ({}) {{\n{}\n{}}}",
291 lb,
292 var,
293 convert_expr(list),
294 convert_block(body, depth + 1),
295 pfx
296 );
297 if let Some(cb) = continue_block {
298 s.push_str(&format!(
299 " continue {{\n{}\n{}}}",
300 convert_block(cb, depth + 1),
301 pfx
302 ));
303 }
304 s
305 }
306 StmtKind::SubDecl {
307 name,
308 params,
309 body,
310 prototype,
311 } => {
312 let sig = if !params.is_empty() {
313 format!(
314 " ({})",
315 params
316 .iter()
317 .map(fmt::format_sub_sig_param)
318 .collect::<Vec<_>>()
319 .join(", ")
320 )
321 } else {
322 prototype
323 .as_ref()
324 .map(|p| format!(" ({})", p))
325 .unwrap_or_default()
326 };
327 format!(
328 "fn {}{} {{\n{}\n{}}}",
329 name,
330 sig,
331 convert_block(body, depth + 1),
332 pfx
333 )
334 }
335 StmtKind::Package { name } => format!("package {}", name),
336 StmtKind::UsePerlVersion { version } => {
337 if version.fract() == 0.0 && *version >= 0.0 {
338 format!("use {}", *version as i64)
339 } else {
340 format!("use {}", version)
341 }
342 }
343 StmtKind::Use { module, imports } => {
344 if imports.is_empty() {
345 format!("use {}", module)
346 } else {
347 format!("use {} {}", module, convert_expr_list(imports))
348 }
349 }
350 StmtKind::UseOverload { pairs } => {
351 let inner = pairs
352 .iter()
353 .map(|(k, v)| {
354 format!(
355 "'{}' => '{}'",
356 k.replace('\'', "\\'"),
357 v.replace('\'', "\\'")
358 )
359 })
360 .collect::<Vec<_>>()
361 .join(", ");
362 format!("use overload {inner}")
363 }
364 StmtKind::No { module, imports } => {
365 if imports.is_empty() {
366 format!("no {}", module)
367 } else {
368 format!("no {} {}", module, convert_expr_list(imports))
369 }
370 }
371 StmtKind::Return(e) => e
372 .as_ref()
373 .map(|x| format!("return {}", convert_expr_top(x)))
374 .unwrap_or_else(|| "return".to_string()),
375 StmtKind::Last(l) => l
376 .as_ref()
377 .map(|x| format!("last {}", x))
378 .unwrap_or_else(|| "last".to_string()),
379 StmtKind::Next(l) => l
380 .as_ref()
381 .map(|x| format!("next {}", x))
382 .unwrap_or_else(|| "next".to_string()),
383 StmtKind::Redo(l) => l
384 .as_ref()
385 .map(|x| format!("redo {}", x))
386 .unwrap_or_else(|| "redo".to_string()),
387 StmtKind::My(decls) => format!("my {}", convert_var_decls(decls)),
388 StmtKind::Our(decls) => format!("our {}", convert_var_decls(decls)),
389 StmtKind::Local(decls) => format!("local {}", convert_var_decls(decls)),
390 StmtKind::State(decls) => format!("state {}", convert_var_decls(decls)),
391 StmtKind::LocalExpr {
392 target,
393 initializer,
394 } => {
395 let mut s = format!("local {}", convert_expr(target));
396 if let Some(init) = initializer {
397 s.push_str(&format!(" = {}", convert_expr_top(init)));
398 }
399 s
400 }
401 StmtKind::MySync(decls) => format!("mysync {}", convert_var_decls(decls)),
402 StmtKind::StmtGroup(b) => convert_block(b, depth),
403 StmtKind::Block(b) => format!("{{\n{}\n{}}}", convert_block(b, depth + 1), pfx),
404 StmtKind::Begin(b) => format!("BEGIN {{\n{}\n{}}}", convert_block(b, depth + 1), pfx),
405 StmtKind::UnitCheck(b) => {
406 format!("UNITCHECK {{\n{}\n{}}}", convert_block(b, depth + 1), pfx)
407 }
408 StmtKind::Check(b) => format!("CHECK {{\n{}\n{}}}", convert_block(b, depth + 1), pfx),
409 StmtKind::Init(b) => format!("INIT {{\n{}\n{}}}", convert_block(b, depth + 1), pfx),
410 StmtKind::End(b) => format!("END {{\n{}\n{}}}", convert_block(b, depth + 1), pfx),
411 StmtKind::Empty => String::new(),
412 StmtKind::Goto { target } => format!("goto {}", convert_expr(target)),
413 StmtKind::Continue(b) => format!("continue {{\n{}\n{}}}", convert_block(b, depth + 1), pfx),
414 StmtKind::StructDecl { def } => {
415 let fields = def
416 .fields
417 .iter()
418 .map(|f| format!("{} => {}", f.name, f.ty.display_name()))
419 .collect::<Vec<_>>()
420 .join(", ");
421 format!("struct {} {{ {} }}", def.name, fields)
422 }
423 StmtKind::EnumDecl { def } => {
424 let variants = def
425 .variants
426 .iter()
427 .map(|v| {
428 if let Some(ty) = &v.ty {
429 format!("{} => {}", v.name, ty.display_name())
430 } else {
431 v.name.clone()
432 }
433 })
434 .collect::<Vec<_>>()
435 .join(", ");
436 format!("enum {} {{ {} }}", def.name, variants)
437 }
438 StmtKind::ClassDecl { def } => {
439 let prefix = if def.is_abstract {
440 "abstract "
441 } else if def.is_final {
442 "final "
443 } else {
444 ""
445 };
446 let mut parts = vec![format!("{}class {}", prefix, def.name)];
447 if !def.extends.is_empty() {
448 parts.push(format!("extends {}", def.extends.join(", ")));
449 }
450 if !def.implements.is_empty() {
451 parts.push(format!("impl {}", def.implements.join(", ")));
452 }
453 let fields = def
454 .fields
455 .iter()
456 .map(|f| {
457 let vis = match f.visibility {
458 crate::ast::Visibility::Private => "priv ",
459 crate::ast::Visibility::Protected => "prot ",
460 crate::ast::Visibility::Public => "",
461 };
462 format!("{}{}: {}", vis, f.name, f.ty.display_name())
463 })
464 .collect::<Vec<_>>()
465 .join("; ");
466 format!("{} {{ {} }}", parts.join(" "), fields)
467 }
468 StmtKind::TraitDecl { def } => {
469 let methods = def
470 .methods
471 .iter()
472 .map(|m| format!("fn {}", m.name))
473 .collect::<Vec<_>>()
474 .join("; ");
475 format!("trait {} {{ {} }}", def.name, methods)
476 }
477 StmtKind::EvalTimeout { timeout, body } => {
478 format!(
479 "eval_timeout {} {{\n{}\n{}}}",
480 convert_expr(timeout),
481 convert_block(body, depth + 1),
482 pfx
483 )
484 }
485 StmtKind::TryCatch {
486 try_block,
487 catch_var,
488 catch_block,
489 finally_block,
490 } => {
491 let fin = finally_block
492 .as_ref()
493 .map(|b| {
494 format!(
495 "\n{}finally {{\n{}\n{}}}",
496 pfx,
497 convert_block(b, depth + 1),
498 pfx
499 )
500 })
501 .unwrap_or_default();
502 format!(
503 "try {{\n{}\n{}}} catch (${}) {{\n{}\n{}}}{}",
504 convert_block(try_block, depth + 1),
505 pfx,
506 catch_var,
507 convert_block(catch_block, depth + 1),
508 pfx,
509 fin
510 )
511 }
512 StmtKind::Given { topic, body } => {
513 format!(
514 "given ({}) {{\n{}\n{}}}",
515 convert_expr(topic),
516 convert_block(body, depth + 1),
517 pfx
518 )
519 }
520 StmtKind::When { cond, body } => {
521 format!(
522 "when ({}) {{\n{}\n{}}}",
523 convert_expr(cond),
524 convert_block(body, depth + 1),
525 pfx
526 )
527 }
528 StmtKind::DefaultCase { body } => {
529 format!("default {{\n{}\n{}}}", convert_block(body, depth + 1), pfx)
530 }
531 StmtKind::FormatDecl { name, lines } => {
532 let mut s = format!("format {} =\n", name);
533 for ln in lines {
534 s.push_str(ln);
535 s.push('\n');
536 }
537 s.push('.');
538 s
539 }
540 StmtKind::Tie {
541 target,
542 class,
543 args,
544 } => {
545 let target_s = match target {
546 crate::ast::TieTarget::Hash(h) => format!("%{}", h),
547 crate::ast::TieTarget::Array(a) => format!("@{}", a),
548 crate::ast::TieTarget::Scalar(s) => format!("${}", s),
549 };
550 let mut s = format!("tie {} {}", target_s, convert_expr(class));
551 for a in args {
552 s.push_str(&format!(", {}", convert_expr(a)));
553 }
554 s
555 }
556 };
557 format!("{}{}{}", pfx, lab, body)
558}
559
560fn convert_statement_body(s: &Statement) -> String {
562 let lab = s
563 .label
564 .as_ref()
565 .map(|l| format!("{}: ", l))
566 .unwrap_or_default();
567 let body = match &s.kind {
568 StmtKind::Expression(e) => convert_expr_top(e),
569 StmtKind::My(decls) => format!("my {}", convert_var_decls(decls)),
570 _ => convert_statement(s, 0).trim().to_string(),
571 };
572 format!("{}{}", lab, body)
573}
574
575fn convert_var_decls(decls: &[VarDecl]) -> String {
578 decls
579 .iter()
580 .map(|d| {
581 let sig = match d.sigil {
582 Sigil::Scalar => "$",
583 Sigil::Array => "@",
584 Sigil::Hash => "%",
585 Sigil::Typeglob => "*",
586 };
587 let mut s = format!("{}{}", sig, d.name);
588 if let Some(ref t) = d.type_annotation {
589 s.push_str(&format!(" : {}", t.display_name()));
590 }
591 if let Some(ref init) = d.initializer {
592 s.push_str(&format!(" = {}", convert_expr_top(init)));
593 }
594 s
595 })
596 .collect::<Vec<_>>()
597 .join(", ")
598}
599
600fn convert_expr_list(es: &[Expr]) -> String {
603 es.iter().map(convert_expr).collect::<Vec<_>>().join(", ")
604}
605
606fn convert_string_part(p: &StringPart) -> String {
609 match p {
610 StringPart::Literal(s) => fmt::escape_interpolated_literal(s),
611 StringPart::ScalarVar(n) => {
612 if needs_braces(n) {
614 format!("${{{}}}", n)
615 } else {
616 format!("${}", n)
617 }
618 }
619 StringPart::ArrayVar(n) => {
620 if needs_braces(n) {
621 format!("@{{{}}}", n)
622 } else {
623 format!("@{}", n)
624 }
625 }
626 StringPart::Expr(e) => fmt::format_expr(e),
627 }
628}
629
630fn needs_braces(name: &str) -> bool {
632 if name.is_empty() || name.chars().next().is_some_and(|c| c.is_ascii_digit()) {
634 return true;
635 }
636 !name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
638}
639
640fn convert_expr_top(e: &Expr) -> String {
643 convert_expr_impl(e, true)
644}
645
646fn convert_expr(e: &Expr) -> String {
649 convert_expr_impl(e, false)
650}
651
652fn convert_expr_impl(e: &Expr, top: bool) -> String {
653 let mut segments: Vec<String> = Vec::new();
654 let source = extract_pipe_source(e, &mut segments);
655 if !segments.is_empty() {
656 segments.reverse();
657 if segments.len() <= 2 {
660 let result = format!("{} {}", segments.join(" "), source);
661 if !top {
662 return format!("({})", result);
663 }
664 return result;
665 }
666 let stages = segments.join(" ");
668 let source = if source.starts_with("(t ") || source.starts_with("((") {
670 source[1..source.len() - 1].to_string()
671 } else {
672 source
673 };
674 let result = format!("t {} {}", source, stages);
675 if !top {
676 return format!("({})", result);
677 }
678 return result;
679 }
680 convert_expr_direct(e, top)
682}
683
684fn extract_pipe_source(e: &Expr, segments: &mut Vec<String>) -> String {
691 match &e.kind {
692 ExprKind::Uc(inner) => {
694 segments.push("uc".into());
695 extract_pipe_source(inner, segments)
696 }
697 ExprKind::Lc(inner) => {
698 segments.push("lc".into());
699 extract_pipe_source(inner, segments)
700 }
701 ExprKind::Ucfirst(inner) => {
702 segments.push("ucfirst".into());
703 extract_pipe_source(inner, segments)
704 }
705 ExprKind::Lcfirst(inner) => {
706 segments.push("lcfirst".into());
707 extract_pipe_source(inner, segments)
708 }
709 ExprKind::Fc(inner) => {
710 segments.push("fc".into());
711 extract_pipe_source(inner, segments)
712 }
713 ExprKind::Chomp(inner) => {
714 segments.push("chomp".into());
715 extract_pipe_source(inner, segments)
716 }
717 ExprKind::Chop(inner) => {
718 segments.push("chop".into());
719 extract_pipe_source(inner, segments)
720 }
721 ExprKind::Length(inner) => {
722 segments.push("length".into());
723 extract_pipe_source(inner, segments)
724 }
725 ExprKind::Abs(inner) => {
726 segments.push("abs".into());
727 extract_pipe_source(inner, segments)
728 }
729 ExprKind::Int(inner) => {
730 segments.push("int".into());
731 extract_pipe_source(inner, segments)
732 }
733 ExprKind::Sqrt(inner) => {
734 segments.push("sqrt".into());
735 extract_pipe_source(inner, segments)
736 }
737 ExprKind::Sin(inner) => {
738 segments.push("sin".into());
739 extract_pipe_source(inner, segments)
740 }
741 ExprKind::Cos(inner) => {
742 segments.push("cos".into());
743 extract_pipe_source(inner, segments)
744 }
745 ExprKind::Exp(inner) => {
746 segments.push("exp".into());
747 extract_pipe_source(inner, segments)
748 }
749 ExprKind::Log(inner) => {
750 segments.push("log".into());
751 extract_pipe_source(inner, segments)
752 }
753 ExprKind::Hex(inner) => {
754 segments.push("hex".into());
755 extract_pipe_source(inner, segments)
756 }
757 ExprKind::Oct(inner) => {
758 segments.push("oct".into());
759 extract_pipe_source(inner, segments)
760 }
761 ExprKind::Chr(inner) => {
762 segments.push("chr".into());
763 extract_pipe_source(inner, segments)
764 }
765 ExprKind::Ord(inner) => {
766 segments.push("ord".into());
767 extract_pipe_source(inner, segments)
768 }
769 ExprKind::Defined(inner) => {
770 segments.push("defined".into());
771 extract_pipe_source(inner, segments)
772 }
773 ExprKind::Ref(inner) => {
774 segments.push("ref".into());
775 extract_pipe_source(inner, segments)
776 }
777 ExprKind::ScalarContext(inner) => {
778 segments.push("scalar".into());
779 extract_pipe_source(inner, segments)
780 }
781 ExprKind::Keys(inner) => {
782 segments.push("keys".into());
783 extract_pipe_source(inner, segments)
784 }
785 ExprKind::Values(inner) => {
786 segments.push("values".into());
787 extract_pipe_source(inner, segments)
788 }
789 ExprKind::Each(inner) => {
790 segments.push("each".into());
791 extract_pipe_source(inner, segments)
792 }
793 ExprKind::Pop(inner) => {
794 segments.push("pop".into());
795 extract_pipe_source(inner, segments)
796 }
797 ExprKind::Shift(inner) => {
798 segments.push("shift".into());
799 extract_pipe_source(inner, segments)
800 }
801 ExprKind::ReverseExpr(inner) => {
802 segments.push("reverse".into());
803 extract_pipe_source(inner, segments)
804 }
805 ExprKind::Slurp(inner) => {
806 segments.push("slurp".into());
807 extract_pipe_source(inner, segments)
808 }
809 ExprKind::Chdir(inner) => {
810 segments.push("chdir".into());
811 extract_pipe_source(inner, segments)
812 }
813 ExprKind::Stat(inner) => {
814 segments.push("stat".into());
815 extract_pipe_source(inner, segments)
816 }
817 ExprKind::Lstat(inner) => {
818 segments.push("lstat".into());
819 extract_pipe_source(inner, segments)
820 }
821 ExprKind::Readlink(inner) => {
822 segments.push("readlink".into());
823 extract_pipe_source(inner, segments)
824 }
825 ExprKind::Study(inner) => {
826 segments.push("study".into());
827 extract_pipe_source(inner, segments)
828 }
829 ExprKind::Close(inner) => {
830 segments.push("close".into());
831 extract_pipe_source(inner, segments)
832 }
833 ExprKind::Readdir(inner) => {
834 segments.push("readdir".into());
835 extract_pipe_source(inner, segments)
836 }
837 ExprKind::Eval(inner) => {
838 segments.push("eval".into());
839 extract_pipe_source(inner, segments)
840 }
841 ExprKind::Require(inner) => {
842 segments.push("require".into());
843 extract_pipe_source(inner, segments)
844 }
845
846 ExprKind::MapExpr {
848 block,
849 list,
850 flatten_array_refs,
851 stream,
852 } => {
853 let kw = match (*flatten_array_refs, *stream) {
854 (true, true) => "flat_maps",
855 (true, false) => "flat_map",
856 (false, true) => "maps",
857 (false, false) => "map",
858 };
859 segments.push(format!("{} {{\n{}\n}}", kw, convert_block(block, 0)));
860 extract_pipe_source(list, segments)
861 }
862 ExprKind::MapExprComma {
863 expr,
864 list,
865 flatten_array_refs,
866 stream,
867 } => {
868 let kw = match (*flatten_array_refs, *stream) {
869 (true, true) => "flat_maps",
870 (true, false) => "flat_map",
871 (false, true) => "maps",
872 (false, false) => "map",
873 };
874 segments.push(format!("{} {{ {} }}", kw, convert_expr_top(expr)));
876 extract_pipe_source(list, segments)
877 }
878 ExprKind::GrepExpr {
879 block,
880 list,
881 keyword,
882 } => {
883 segments.push(format!(
884 "{} {{\n{}\n}}",
885 keyword.as_str(),
886 convert_block(block, 0)
887 ));
888 extract_pipe_source(list, segments)
889 }
890 ExprKind::GrepExprComma {
891 expr,
892 list,
893 keyword,
894 } => {
895 segments.push(format!(
896 "{} {{ {} }}",
897 keyword.as_str(),
898 convert_expr_top(expr)
899 ));
900 extract_pipe_source(list, segments)
901 }
902 ExprKind::SortExpr { cmp, list } => {
903 let seg = match cmp {
904 Some(SortComparator::Block(b)) => {
905 format!("sort {{\n{}\n}}", convert_block(b, 0))
906 }
907 Some(SortComparator::Code(e)) => {
908 format!("sort {}", convert_expr(e))
909 }
910 None => "sort".to_string(),
911 };
912 segments.push(seg);
913 extract_pipe_source(list, segments)
914 }
915 ExprKind::JoinExpr { separator, list } => {
916 segments.push(format!("join {}", convert_expr(separator)));
917 extract_pipe_source(list, segments)
918 }
919 ExprKind::ReduceExpr { block, list } => {
920 segments.push(format!("reduce {{\n{}\n}}", convert_block(block, 0)));
921 extract_pipe_source(list, segments)
922 }
923 ExprKind::ForEachExpr { block, list } => {
924 segments.push(format!("fore {{\n{}\n}}", convert_block(block, 0)));
925 extract_pipe_source(list, segments)
926 }
927
928 ExprKind::PMapExpr {
930 block,
931 list,
932 progress,
933 flat_outputs,
934 on_cluster,
935 stream: _,
936 } if progress.is_none() && on_cluster.is_none() => {
937 let kw = if *flat_outputs { "pflat_map" } else { "pmap" };
938 segments.push(format!("{} {{\n{}\n}}", kw, convert_block(block, 0)));
939 extract_pipe_source(list, segments)
940 }
941 ExprKind::PGrepExpr {
942 block,
943 list,
944 progress,
945 stream: _,
946 } if progress.is_none() => {
947 segments.push(format!("pgrep {{\n{}\n}}", convert_block(block, 0)));
948 extract_pipe_source(list, segments)
949 }
950 ExprKind::PSortExpr {
951 cmp,
952 list,
953 progress,
954 } if progress.is_none() => {
955 let seg = match cmp {
956 Some(b) => format!("psort {{\n{}\n}}", convert_block(b, 0)),
957 None => "psort".to_string(),
958 };
959 segments.push(seg);
960 extract_pipe_source(list, segments)
961 }
962
963 ExprKind::Say { handle: None, args } if args.len() == 1 => {
966 segments.push("p".into());
967 extract_pipe_source(&args[0], segments)
968 }
969 ExprKind::Print { handle: None, args } if args.len() == 1 => {
970 segments.push("print".into());
971 extract_pipe_source(&args[0], segments)
972 }
973
974 ExprKind::FuncCall { name, args } if !args.is_empty() => {
976 let seg = if args.len() == 1 {
977 name.clone()
978 } else {
979 let rest = args[1..]
980 .iter()
981 .map(convert_expr)
982 .collect::<Vec<_>>()
983 .join(", ");
984 format!("{} {}", name, rest)
985 };
986 segments.push(seg);
987 extract_pipe_source(&args[0], segments)
988 }
989
990 ExprKind::Substitution {
992 expr,
993 pattern,
994 replacement,
995 flags,
996 delim,
997 } if flags.contains('r') => {
998 let d = choose_delim(*delim);
1002 segments.push(format!(
1003 "s{}{}{}{}{}{}",
1004 d,
1005 fmt::escape_regex_part(pattern),
1006 d,
1007 fmt::escape_regex_part(replacement),
1008 d,
1009 flags
1010 ));
1011 extract_pipe_source(expr, segments)
1012 }
1013
1014 ExprKind::Transliterate {
1016 expr,
1017 from,
1018 to,
1019 flags,
1020 delim,
1021 } if flags.contains('r') => {
1022 let d = choose_delim(*delim);
1023 segments.push(format!(
1024 "tr{}{}{}{}{}{}",
1025 d,
1026 fmt::escape_regex_part(from),
1027 d,
1028 fmt::escape_regex_part(to),
1029 d,
1030 flags
1031 ));
1032 extract_pipe_source(expr, segments)
1033 }
1034
1035 ExprKind::List(elems) if elems.len() == 1 => extract_pipe_source(&elems[0], segments),
1037
1038 _ => convert_expr_direct(e, false),
1040 }
1041}
1042
1043fn convert_expr_direct(e: &Expr, top: bool) -> String {
1049 match &e.kind {
1050 ExprKind::Integer(_)
1052 | ExprKind::Float(_)
1053 | ExprKind::String(_)
1054 | ExprKind::Bareword(_)
1055 | ExprKind::Regex(..)
1056 | ExprKind::QW(_)
1057 | ExprKind::Undef
1058 | ExprKind::MagicConst(_)
1059 | ExprKind::ScalarVar(_)
1060 | ExprKind::ArrayVar(_)
1061 | ExprKind::HashVar(_)
1062 | ExprKind::Typeglob(_)
1063 | ExprKind::Wantarray
1064 | ExprKind::SubroutineRef(_)
1065 | ExprKind::SubroutineCodeRef(_) => fmt::format_expr(e),
1066
1067 ExprKind::InterpolatedString(parts) => {
1069 format!(
1070 "\"{}\"",
1071 parts.iter().map(convert_string_part).collect::<String>()
1072 )
1073 }
1074
1075 ExprKind::BinOp { left, op, right } => {
1077 format!(
1078 "{} {} {}",
1079 convert_expr(left),
1080 fmt::format_binop(*op),
1081 convert_expr(right)
1082 )
1083 }
1084
1085 ExprKind::UnaryOp { op, expr } => {
1087 format!("{}{}", fmt::format_unary(*op), convert_expr(expr))
1088 }
1089 ExprKind::PostfixOp { expr, op } => {
1090 format!("{}{}", convert_expr(expr), fmt::format_postfix(*op))
1091 }
1092
1093 ExprKind::Assign { target, value } => {
1095 format!("{} = {}", convert_expr(target), convert_expr_top(value))
1096 }
1097 ExprKind::CompoundAssign { target, op, value } => format!(
1098 "{} {}= {}",
1099 convert_expr(target),
1100 fmt::format_binop(*op),
1101 convert_expr_top(value)
1102 ),
1103
1104 ExprKind::Ternary {
1106 condition,
1107 then_expr,
1108 else_expr,
1109 } => format!(
1110 "{} ? {} : {}",
1111 convert_expr(condition),
1112 convert_expr(then_expr),
1113 convert_expr(else_expr)
1114 ),
1115
1116 ExprKind::Range {
1118 from,
1119 to,
1120 exclusive,
1121 } => {
1122 let op = if *exclusive { "..." } else { ".." };
1123 format!("{} {} {}", convert_expr(from), op, convert_expr(to))
1124 }
1125 ExprKind::Repeat { expr, count } => {
1126 format!("{} x {}", convert_expr(expr), convert_expr(count))
1127 }
1128
1129 ExprKind::FuncCall { name, args } => format!("{}({})", name, convert_expr_list(args)),
1131 ExprKind::MethodCall {
1132 object,
1133 method,
1134 args,
1135 super_call,
1136 } => {
1137 let m = if *super_call {
1138 format!("SUPER::{}", method)
1139 } else {
1140 method.clone()
1141 };
1142 format!(
1143 "{}->{}({})",
1144 convert_expr(object),
1145 m,
1146 convert_expr_list(args)
1147 )
1148 }
1149 ExprKind::IndirectCall {
1150 target,
1151 args,
1152 ampersand,
1153 pass_caller_arglist,
1154 } => {
1155 if *pass_caller_arglist && args.is_empty() {
1156 format!("&{}", convert_expr(target))
1157 } else {
1158 let inner = format!("{}({})", convert_expr(target), convert_expr_list(args));
1159 if *ampersand {
1160 format!("&{}", inner)
1161 } else {
1162 inner
1163 }
1164 }
1165 }
1166
1167 ExprKind::List(exprs) => format!("({})", convert_expr_list(exprs)),
1169 ExprKind::ArrayRef(elems) => format!("[{}]", convert_expr_list(elems)),
1170 ExprKind::HashRef(pairs) => {
1171 let inner = pairs
1172 .iter()
1173 .map(|(k, v)| format!("{} => {}", convert_expr(k), convert_expr(v)))
1174 .collect::<Vec<_>>()
1175 .join(", ");
1176 format!("{{{}}}", inner)
1177 }
1178 ExprKind::CodeRef { params, body } => {
1179 if params.is_empty() {
1180 format!("fn {{\n{}\n}}", convert_block(body, 0))
1181 } else {
1182 let sig = params
1183 .iter()
1184 .map(fmt::format_sub_sig_param)
1185 .collect::<Vec<_>>()
1186 .join(", ");
1187 format!("fn ({}) {{\n{}\n}}", sig, convert_block(body, 0))
1188 }
1189 }
1190
1191 ExprKind::ArrayElement { array, index } => {
1193 format!("${}[{}]", array, convert_expr(index))
1194 }
1195 ExprKind::HashElement { hash, key } => {
1196 format!("${}{{{}}}", hash, convert_expr(key))
1197 }
1198 ExprKind::ScalarRef(inner) => format!("\\{}", convert_expr(inner)),
1199 ExprKind::ArrowDeref { expr, index, kind } => match kind {
1200 DerefKind::Array => {
1201 format!("({})->[{}]", convert_expr(expr), convert_expr(index))
1202 }
1203 DerefKind::Hash => {
1204 format!("({})->{{{}}}", convert_expr(expr), convert_expr(index))
1205 }
1206 DerefKind::Call => {
1207 format!("({})->({})", convert_expr(expr), convert_expr(index))
1208 }
1209 },
1210 ExprKind::Deref { expr, kind } => match kind {
1211 Sigil::Scalar => format!("${{{}}}", convert_expr(expr)),
1212 Sigil::Array => format!("@{{${}}}", convert_expr(expr)),
1213 Sigil::Hash => format!("%{{${}}}", convert_expr(expr)),
1214 Sigil::Typeglob => format!("*{{${}}}", convert_expr(expr)),
1215 },
1216
1217 ExprKind::Print { handle, args } => {
1220 let h = handle
1221 .as_ref()
1222 .map(|h| format!("{} ", h))
1223 .unwrap_or_default();
1224 format!("print {}{}", h, convert_expr_list(args))
1225 }
1226 ExprKind::Say { handle, args } => {
1227 if let Some(h) = handle {
1228 format!("say {} {}", h, convert_expr_list(args))
1229 } else {
1230 format!("p {}", convert_expr_list(args))
1231 }
1232 }
1233 ExprKind::Printf { handle, args } => {
1234 let h = handle
1235 .as_ref()
1236 .map(|h| format!("{} ", h))
1237 .unwrap_or_default();
1238 format!("printf {}{}", h, convert_expr_list(args))
1239 }
1240 ExprKind::Die(args) => {
1241 if args.is_empty() {
1242 "die".to_string()
1243 } else {
1244 format!("die {}", convert_expr_list(args))
1245 }
1246 }
1247 ExprKind::Warn(args) => {
1248 if args.is_empty() {
1249 "warn".to_string()
1250 } else {
1251 format!("warn {}", convert_expr_list(args))
1252 }
1253 }
1254
1255 ExprKind::Match {
1257 expr,
1258 pattern,
1259 flags,
1260 delim,
1261 ..
1262 } => {
1263 let d = choose_delim(*delim);
1264 format!(
1265 "{} =~ {}{}{}{}",
1266 convert_expr(expr),
1267 d,
1268 fmt::escape_regex_part(pattern),
1269 d,
1270 flags
1271 )
1272 }
1273 ExprKind::Substitution {
1274 expr,
1275 pattern,
1276 replacement,
1277 flags,
1278 delim,
1279 } => {
1280 let d = choose_delim(*delim);
1281 format!(
1282 "{} =~ s{}{}{}{}{}{}",
1283 convert_expr(expr),
1284 d,
1285 fmt::escape_regex_part(pattern),
1286 d,
1287 fmt::escape_regex_part(replacement),
1288 d,
1289 flags
1290 )
1291 }
1292 ExprKind::Transliterate {
1293 expr,
1294 from,
1295 to,
1296 flags,
1297 delim,
1298 } => {
1299 let d = choose_delim(*delim);
1300 format!(
1301 "{} =~ tr{}{}{}{}{}{}",
1302 convert_expr(expr),
1303 d,
1304 fmt::escape_regex_part(from),
1305 d,
1306 fmt::escape_regex_part(to),
1307 d,
1308 flags
1309 )
1310 }
1311
1312 ExprKind::PostfixIf { expr, condition } => {
1314 format!("{} if {}", convert_expr_top(expr), convert_expr(condition))
1315 }
1316 ExprKind::PostfixUnless { expr, condition } => {
1317 format!(
1318 "{} unless {}",
1319 convert_expr_top(expr),
1320 convert_expr(condition)
1321 )
1322 }
1323 ExprKind::PostfixWhile { expr, condition } => {
1324 format!(
1325 "{} while {}",
1326 convert_expr_top(expr),
1327 convert_expr(condition)
1328 )
1329 }
1330 ExprKind::PostfixUntil { expr, condition } => {
1331 format!(
1332 "{} until {}",
1333 convert_expr_top(expr),
1334 convert_expr(condition)
1335 )
1336 }
1337 ExprKind::PostfixForeach { expr, list } => {
1338 format!("{} for {}", convert_expr_top(expr), convert_expr(list))
1339 }
1340
1341 ExprKind::MapExpr {
1343 block,
1344 list,
1345 flatten_array_refs,
1346 stream,
1347 } => {
1348 let kw = match (*flatten_array_refs, *stream) {
1349 (true, true) => "flat_maps",
1350 (true, false) => "flat_map",
1351 (false, true) => "maps",
1352 (false, false) => "map",
1353 };
1354 format!(
1355 "{} {{\n{}\n}} {}",
1356 kw,
1357 convert_block(block, 0),
1358 convert_expr(list)
1359 )
1360 }
1361 ExprKind::GrepExpr {
1362 block,
1363 list,
1364 keyword,
1365 } => {
1366 format!(
1367 "{} {{\n{}\n}} {}",
1368 keyword.as_str(),
1369 convert_block(block, 0),
1370 convert_expr(list)
1371 )
1372 }
1373 ExprKind::SortExpr { cmp, list } => match cmp {
1374 Some(SortComparator::Block(b)) => {
1375 format!(
1376 "sort {{\n{}\n}} {}",
1377 convert_block(b, 0),
1378 convert_expr(list)
1379 )
1380 }
1381 Some(SortComparator::Code(e)) => {
1382 format!("sort {} {}", convert_expr(e), convert_expr(list))
1383 }
1384 None => format!("sort {}", convert_expr(list)),
1385 },
1386 ExprKind::JoinExpr { separator, list } => {
1387 format!("join({}, {})", convert_expr(separator), convert_expr(list))
1388 }
1389 ExprKind::SplitExpr {
1390 pattern,
1391 string,
1392 limit,
1393 } => match limit {
1394 Some(l) => format!(
1395 "split({}, {}, {})",
1396 convert_expr(pattern),
1397 convert_expr(string),
1398 convert_expr(l)
1399 ),
1400 None => format!("split({}, {})", convert_expr(pattern), convert_expr(string)),
1401 },
1402
1403 ExprKind::Bless { ref_expr, class } => match class {
1405 Some(c) => format!("bless({}, {})", convert_expr(ref_expr), convert_expr(c)),
1406 None => format!("bless({})", convert_expr(ref_expr)),
1407 },
1408
1409 ExprKind::Push { array, values } => {
1411 format!(
1412 "push({}, {})",
1413 convert_expr(array),
1414 convert_expr_list(values)
1415 )
1416 }
1417 ExprKind::Unshift { array, values } => {
1418 format!(
1419 "unshift({}, {})",
1420 convert_expr(array),
1421 convert_expr_list(values)
1422 )
1423 }
1424
1425 ExprKind::AlgebraicMatch { subject, arms } => {
1427 let arms_s = arms
1428 .iter()
1429 .map(|a| {
1430 let guard_s = a
1431 .guard
1432 .as_ref()
1433 .map(|g| format!(" if {}", convert_expr(g)))
1434 .unwrap_or_default();
1435 format!(
1436 "{}{} => {}",
1437 fmt::format_match_pattern(&a.pattern),
1438 guard_s,
1439 convert_expr(&a.body)
1440 )
1441 })
1442 .collect::<Vec<_>>()
1443 .join(", ");
1444 format!("match ({}) {{ {} }}", convert_expr(subject), arms_s)
1445 }
1446
1447 _ => fmt::format_expr(e),
1449 }
1450}
1451
1452#[cfg(test)]
1455mod tests {
1456 use super::*;
1457 use crate::parse;
1458
1459 fn convert(code: &str) -> String {
1461 let p = parse(code).expect("parse failed");
1462 let out = convert_program(&p);
1463 out.strip_prefix("#!/usr/bin/env stryke\n")
1465 .unwrap_or(&out)
1466 .to_string()
1467 }
1468
1469 #[test]
1470 fn unary_builtin_direct() {
1471 assert_eq!(convert("uc($x)"), "uc $x");
1473 assert_eq!(convert("length($str)"), "length $str");
1474 }
1475
1476 #[test]
1477 fn nested_unary_direct() {
1478 let out = convert("uc(lc($x))");
1480 assert_eq!(out, "lc uc $x");
1481 }
1482
1483 #[test]
1484 fn nested_builtin_chain_thread() {
1485 let out = convert("chomp(lc(uc($x)))");
1486 assert_eq!(out, "t $x uc lc chomp");
1487 }
1488
1489 #[test]
1490 fn deeply_nested_thread() {
1491 let out = convert("length(chomp(lc(uc($x))))");
1492 assert_eq!(out, "t $x uc lc chomp length");
1493 }
1494
1495 #[test]
1496 fn map_grep_sort_thread() {
1497 let out = convert("sort { $a <=> $b } map { $_ * 2 } grep { $_ > 0 } @numbers");
1498 assert!(out.contains("t @numbers grep"));
1499 assert!(out.contains(" map"));
1500 assert!(out.contains(" sort"));
1501 }
1502
1503 #[test]
1504 fn join_direct() {
1505 let out = convert(r#"join(",", sort(@arr))"#);
1506 assert!(out.contains("sort join \",\" @arr"));
1508 }
1509
1510 #[test]
1511 fn no_semicolons() {
1512 let out = convert("my $x = 1;\nmy $y = 2");
1513 assert!(!out.contains(';'));
1514 assert!(out.contains("my $x = 1"));
1515 assert!(out.contains("my $y = 2"));
1516 }
1517
1518 #[test]
1519 fn assignment_rhs_direct() {
1520 let out = convert("my $x = uc(lc($str))");
1521 assert_eq!(out, "my $x = lc uc $str");
1523 }
1524
1525 #[test]
1526 fn chain_in_subexpression_parenthesized() {
1527 let out = convert("$x + uc(lc($str))");
1528 assert!(out.contains("(lc uc $str)"));
1530 }
1531
1532 #[test]
1533 fn fn_body_indented() {
1534 let out = convert("sub foo { return uc(lc($x)); }");
1535 assert!(out.contains("fn foo"));
1536 assert!(out.contains("lc uc $x"));
1538 assert!(out.contains(" return"));
1540 }
1541
1542 #[test]
1543 fn if_condition_converted() {
1544 let out = convert("if (defined(length($x))) { 1; }");
1545 assert!(out.contains("length defined $x"));
1547 }
1548
1549 #[test]
1550 fn method_call_preserved() {
1551 let out = convert("$obj->method($x)");
1552 assert!(out.contains("->method"));
1553 }
1554
1555 #[test]
1556 fn substitution_r_flag_direct() {
1557 let out = convert(r#"($str =~ s/old/new/r)"#);
1559 assert!(out.contains("s/old/new/r $str"));
1560 }
1561
1562 #[test]
1563 fn user_func_call_direct() {
1564 let out = convert("sub trim { } trim(uc($x))");
1565 assert!(out.contains("fn trim"));
1566 assert!(out.contains("uc trim $x"));
1568 }
1569
1570 #[test]
1571 fn user_func_extra_args_direct() {
1572 let out = convert("sub process { } process(uc($x), 42)");
1573 assert!(out.contains("fn process"));
1574 assert!(out.contains("uc process 42 $x"));
1576 }
1577
1578 #[test]
1579 fn map_grep_sort_chain_thread() {
1580 let out = convert("join(',', sort { $a <=> $b } map { $_ * 2 } grep { $_ > 0 } @nums)");
1581 assert!(out.contains("t @nums grep"));
1582 assert!(out.contains(" map"));
1583 assert!(out.contains(" sort"));
1584 assert!(out.contains(" join"));
1585 }
1586
1587 #[test]
1588 fn reduce_direct() {
1589 let out = convert("use List::Util 'reduce';\nreduce { $a + $b } @nums");
1591 assert!(out.contains("reduce {\n$a + $b\n} @nums"));
1592 }
1593
1594 #[test]
1595 fn shebang_prepended() {
1596 let p = parse("print 1").expect("parse failed");
1597 let out = convert_program(&p);
1598 assert!(out.starts_with("#!/usr/bin/env stryke\n"));
1599 }
1600
1601 #[test]
1602 fn indentation_in_blocks() {
1603 let out = convert("if ($x) { print 1; print 2; }");
1604 assert!(out.contains("\n print 1\n print 2\n"));
1606 }
1607
1608 #[test]
1609 fn binop_no_parens_at_top() {
1610 let out = convert("my $x = $a + $b");
1611 assert!(out.contains("= $a + $b"));
1613 assert!(!out.contains("= ($a + $b)"));
1614 }
1615
1616 fn convert_with_delim(code: &str, delim: char) -> String {
1617 let p = parse(code).expect("parse failed");
1618 let opts = ConvertOptions {
1619 output_delim: Some(delim),
1620 };
1621 let out = convert_program_with_options(&p, &opts);
1622 out.strip_prefix("#!/usr/bin/env stryke\n")
1623 .unwrap_or(&out)
1624 .to_string()
1625 }
1626
1627 #[test]
1628 fn output_delim_substitution() {
1629 let out = convert_with_delim("$x =~ s/foo/bar/g;", '|');
1630 assert_eq!(out, "$x =~ s|foo|bar|g");
1631 }
1632
1633 #[test]
1634 fn output_delim_transliterate() {
1635 let out = convert_with_delim("$y =~ tr/a-z/A-Z/;", '#');
1636 assert_eq!(out, "$y =~ tr#a-z#A-Z#");
1637 }
1638
1639 #[test]
1640 fn output_delim_match() {
1641 let out = convert_with_delim("$z =~ m/pattern/i;", '!');
1642 assert_eq!(out, "$z =~ !pattern!i");
1643 }
1644
1645 #[test]
1646 fn output_delim_preserves_original_when_none() {
1647 let out = convert("$x =~ s#old#new#g");
1648 assert_eq!(out, "$x =~ s#old#new#g");
1649 }
1650}