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 step,
1122 } => {
1123 let op = if *exclusive { "..." } else { ".." };
1124 if let Some(s) = step {
1125 format!(
1126 "{} {} {}:{}",
1127 convert_expr(from),
1128 op,
1129 convert_expr(to),
1130 convert_expr(s)
1131 )
1132 } else {
1133 format!("{} {} {}", convert_expr(from), op, convert_expr(to))
1134 }
1135 }
1136 ExprKind::Repeat { expr, count } => {
1137 format!("{} x {}", convert_expr(expr), convert_expr(count))
1138 }
1139
1140 ExprKind::FuncCall { name, args } => format!("{}({})", name, convert_expr_list(args)),
1142 ExprKind::MethodCall {
1143 object,
1144 method,
1145 args,
1146 super_call,
1147 } => {
1148 let m = if *super_call {
1149 format!("SUPER::{}", method)
1150 } else {
1151 method.clone()
1152 };
1153 format!(
1154 "{}->{}({})",
1155 convert_expr(object),
1156 m,
1157 convert_expr_list(args)
1158 )
1159 }
1160 ExprKind::IndirectCall {
1161 target,
1162 args,
1163 ampersand,
1164 pass_caller_arglist,
1165 } => {
1166 if *pass_caller_arglist && args.is_empty() {
1167 format!("&{}", convert_expr(target))
1168 } else {
1169 let inner = format!("{}({})", convert_expr(target), convert_expr_list(args));
1170 if *ampersand {
1171 format!("&{}", inner)
1172 } else {
1173 inner
1174 }
1175 }
1176 }
1177
1178 ExprKind::List(exprs) => format!("({})", convert_expr_list(exprs)),
1180 ExprKind::ArrayRef(elems) => format!("[{}]", convert_expr_list(elems)),
1181 ExprKind::HashRef(pairs) => {
1182 let inner = pairs
1183 .iter()
1184 .map(|(k, v)| format!("{} => {}", convert_expr(k), convert_expr(v)))
1185 .collect::<Vec<_>>()
1186 .join(", ");
1187 format!("{{{}}}", inner)
1188 }
1189 ExprKind::CodeRef { params, body } => {
1190 if params.is_empty() {
1191 format!("fn {{\n{}\n}}", convert_block(body, 0))
1192 } else {
1193 let sig = params
1194 .iter()
1195 .map(fmt::format_sub_sig_param)
1196 .collect::<Vec<_>>()
1197 .join(", ");
1198 format!("fn ({}) {{\n{}\n}}", sig, convert_block(body, 0))
1199 }
1200 }
1201
1202 ExprKind::ArrayElement { array, index } => {
1204 format!("${}[{}]", array, convert_expr(index))
1205 }
1206 ExprKind::HashElement { hash, key } => {
1207 format!("${}{{{}}}", hash, convert_expr(key))
1208 }
1209 ExprKind::ScalarRef(inner) => format!("\\{}", convert_expr(inner)),
1210 ExprKind::ArrowDeref { expr, index, kind } => match kind {
1211 DerefKind::Array => {
1212 format!("({})->[{}]", convert_expr(expr), convert_expr(index))
1213 }
1214 DerefKind::Hash => {
1215 format!("({})->{{{}}}", convert_expr(expr), convert_expr(index))
1216 }
1217 DerefKind::Call => {
1218 format!("({})->({})", convert_expr(expr), convert_expr(index))
1219 }
1220 },
1221 ExprKind::Deref { expr, kind } => match kind {
1222 Sigil::Scalar => format!("${{{}}}", convert_expr(expr)),
1223 Sigil::Array => format!("@{{${}}}", convert_expr(expr)),
1224 Sigil::Hash => format!("%{{${}}}", convert_expr(expr)),
1225 Sigil::Typeglob => format!("*{{${}}}", convert_expr(expr)),
1226 },
1227
1228 ExprKind::Print { handle, args } => {
1231 let h = handle
1232 .as_ref()
1233 .map(|h| format!("{} ", h))
1234 .unwrap_or_default();
1235 format!("print {}{}", h, convert_expr_list(args))
1236 }
1237 ExprKind::Say { handle, args } => {
1238 if let Some(h) = handle {
1239 format!("say {} {}", h, convert_expr_list(args))
1240 } else {
1241 format!("p {}", convert_expr_list(args))
1242 }
1243 }
1244 ExprKind::Printf { handle, args } => {
1245 let h = handle
1246 .as_ref()
1247 .map(|h| format!("{} ", h))
1248 .unwrap_or_default();
1249 format!("printf {}{}", h, convert_expr_list(args))
1250 }
1251 ExprKind::Die(args) => {
1252 if args.is_empty() {
1253 "die".to_string()
1254 } else {
1255 format!("die {}", convert_expr_list(args))
1256 }
1257 }
1258 ExprKind::Warn(args) => {
1259 if args.is_empty() {
1260 "warn".to_string()
1261 } else {
1262 format!("warn {}", convert_expr_list(args))
1263 }
1264 }
1265
1266 ExprKind::Match {
1268 expr,
1269 pattern,
1270 flags,
1271 delim,
1272 ..
1273 } => {
1274 let d = choose_delim(*delim);
1275 format!(
1276 "{} =~ {}{}{}{}",
1277 convert_expr(expr),
1278 d,
1279 fmt::escape_regex_part(pattern),
1280 d,
1281 flags
1282 )
1283 }
1284 ExprKind::Substitution {
1285 expr,
1286 pattern,
1287 replacement,
1288 flags,
1289 delim,
1290 } => {
1291 let d = choose_delim(*delim);
1292 format!(
1293 "{} =~ s{}{}{}{}{}{}",
1294 convert_expr(expr),
1295 d,
1296 fmt::escape_regex_part(pattern),
1297 d,
1298 fmt::escape_regex_part(replacement),
1299 d,
1300 flags
1301 )
1302 }
1303 ExprKind::Transliterate {
1304 expr,
1305 from,
1306 to,
1307 flags,
1308 delim,
1309 } => {
1310 let d = choose_delim(*delim);
1311 format!(
1312 "{} =~ tr{}{}{}{}{}{}",
1313 convert_expr(expr),
1314 d,
1315 fmt::escape_regex_part(from),
1316 d,
1317 fmt::escape_regex_part(to),
1318 d,
1319 flags
1320 )
1321 }
1322
1323 ExprKind::PostfixIf { expr, condition } => {
1325 format!("{} if {}", convert_expr_top(expr), convert_expr(condition))
1326 }
1327 ExprKind::PostfixUnless { expr, condition } => {
1328 format!(
1329 "{} unless {}",
1330 convert_expr_top(expr),
1331 convert_expr(condition)
1332 )
1333 }
1334 ExprKind::PostfixWhile { expr, condition } => {
1335 format!(
1336 "{} while {}",
1337 convert_expr_top(expr),
1338 convert_expr(condition)
1339 )
1340 }
1341 ExprKind::PostfixUntil { expr, condition } => {
1342 format!(
1343 "{} until {}",
1344 convert_expr_top(expr),
1345 convert_expr(condition)
1346 )
1347 }
1348 ExprKind::PostfixForeach { expr, list } => {
1349 format!("{} for {}", convert_expr_top(expr), convert_expr(list))
1350 }
1351
1352 ExprKind::MapExpr {
1354 block,
1355 list,
1356 flatten_array_refs,
1357 stream,
1358 } => {
1359 let kw = match (*flatten_array_refs, *stream) {
1360 (true, true) => "flat_maps",
1361 (true, false) => "flat_map",
1362 (false, true) => "maps",
1363 (false, false) => "map",
1364 };
1365 format!(
1366 "{} {{\n{}\n}} {}",
1367 kw,
1368 convert_block(block, 0),
1369 convert_expr(list)
1370 )
1371 }
1372 ExprKind::GrepExpr {
1373 block,
1374 list,
1375 keyword,
1376 } => {
1377 format!(
1378 "{} {{\n{}\n}} {}",
1379 keyword.as_str(),
1380 convert_block(block, 0),
1381 convert_expr(list)
1382 )
1383 }
1384 ExprKind::SortExpr { cmp, list } => match cmp {
1385 Some(SortComparator::Block(b)) => {
1386 format!(
1387 "sort {{\n{}\n}} {}",
1388 convert_block(b, 0),
1389 convert_expr(list)
1390 )
1391 }
1392 Some(SortComparator::Code(e)) => {
1393 format!("sort {} {}", convert_expr(e), convert_expr(list))
1394 }
1395 None => format!("sort {}", convert_expr(list)),
1396 },
1397 ExprKind::JoinExpr { separator, list } => {
1398 format!("join({}, {})", convert_expr(separator), convert_expr(list))
1399 }
1400 ExprKind::SplitExpr {
1401 pattern,
1402 string,
1403 limit,
1404 } => match limit {
1405 Some(l) => format!(
1406 "split({}, {}, {})",
1407 convert_expr(pattern),
1408 convert_expr(string),
1409 convert_expr(l)
1410 ),
1411 None => format!("split({}, {})", convert_expr(pattern), convert_expr(string)),
1412 },
1413
1414 ExprKind::Bless { ref_expr, class } => match class {
1416 Some(c) => format!("bless({}, {})", convert_expr(ref_expr), convert_expr(c)),
1417 None => format!("bless({})", convert_expr(ref_expr)),
1418 },
1419
1420 ExprKind::Push { array, values } => {
1422 format!(
1423 "push({}, {})",
1424 convert_expr(array),
1425 convert_expr_list(values)
1426 )
1427 }
1428 ExprKind::Unshift { array, values } => {
1429 format!(
1430 "unshift({}, {})",
1431 convert_expr(array),
1432 convert_expr_list(values)
1433 )
1434 }
1435
1436 ExprKind::AlgebraicMatch { subject, arms } => {
1438 let arms_s = arms
1439 .iter()
1440 .map(|a| {
1441 let guard_s = a
1442 .guard
1443 .as_ref()
1444 .map(|g| format!(" if {}", convert_expr(g)))
1445 .unwrap_or_default();
1446 format!(
1447 "{}{} => {}",
1448 fmt::format_match_pattern(&a.pattern),
1449 guard_s,
1450 convert_expr(&a.body)
1451 )
1452 })
1453 .collect::<Vec<_>>()
1454 .join(", ");
1455 format!("match ({}) {{ {} }}", convert_expr(subject), arms_s)
1456 }
1457
1458 _ => fmt::format_expr(e),
1460 }
1461}
1462
1463#[cfg(test)]
1466mod tests {
1467 use super::*;
1468 use crate::parse;
1469
1470 fn convert(code: &str) -> String {
1472 let p = parse(code).expect("parse failed");
1473 let out = convert_program(&p);
1474 out.strip_prefix("#!/usr/bin/env stryke\n")
1476 .unwrap_or(&out)
1477 .to_string()
1478 }
1479
1480 #[test]
1481 fn unary_builtin_direct() {
1482 assert_eq!(convert("uc($x)"), "uc $x");
1484 assert_eq!(convert("length($str)"), "length $str");
1485 }
1486
1487 #[test]
1488 fn nested_unary_direct() {
1489 let out = convert("uc(lc($x))");
1491 assert_eq!(out, "lc uc $x");
1492 }
1493
1494 #[test]
1495 fn nested_builtin_chain_thread() {
1496 let out = convert("chomp(lc(uc($x)))");
1497 assert_eq!(out, "t $x uc lc chomp");
1498 }
1499
1500 #[test]
1501 fn deeply_nested_thread() {
1502 let out = convert("length(chomp(lc(uc($x))))");
1503 assert_eq!(out, "t $x uc lc chomp length");
1504 }
1505
1506 #[test]
1507 fn map_grep_sort_thread() {
1508 let out = convert("sort { $a <=> $b } map { $_ * 2 } grep { $_ > 0 } @numbers");
1509 assert!(out.contains("t @numbers grep"));
1510 assert!(out.contains(" map"));
1511 assert!(out.contains(" sort"));
1512 }
1513
1514 #[test]
1515 fn join_direct() {
1516 let out = convert(r#"join(",", sort(@arr))"#);
1517 assert!(out.contains("sort join \",\" @arr"));
1519 }
1520
1521 #[test]
1522 fn no_semicolons() {
1523 let out = convert("my $x = 1;\nmy $y = 2");
1524 assert!(!out.contains(';'));
1525 assert!(out.contains("my $x = 1"));
1526 assert!(out.contains("my $y = 2"));
1527 }
1528
1529 #[test]
1530 fn assignment_rhs_direct() {
1531 let out = convert("my $x = uc(lc($str))");
1532 assert_eq!(out, "my $x = lc uc $str");
1534 }
1535
1536 #[test]
1537 fn chain_in_subexpression_parenthesized() {
1538 let out = convert("$x + uc(lc($str))");
1539 assert!(out.contains("(lc uc $str)"));
1541 }
1542
1543 #[test]
1544 fn fn_body_indented() {
1545 let out = convert("fn foo { return uc(lc($x)); }");
1546 assert!(out.contains("fn foo"));
1547 assert!(out.contains("lc uc $x"));
1549 assert!(out.contains(" return"));
1551 }
1552
1553 #[test]
1554 fn if_condition_converted() {
1555 let out = convert("if (defined(length($x))) { 1; }");
1556 assert!(out.contains("length defined $x"));
1558 }
1559
1560 #[test]
1561 fn method_call_preserved() {
1562 let out = convert("$obj->method($x)");
1563 assert!(out.contains("->method"));
1564 }
1565
1566 #[test]
1567 fn substitution_r_flag_direct() {
1568 let out = convert(r#"($str =~ s/old/new/r)"#);
1570 assert!(out.contains("s/old/new/r $str"));
1571 }
1572
1573 #[test]
1574 fn user_func_call_direct() {
1575 let out = convert("fn my_trim { } my_trim(uc($x))");
1576 assert!(out.contains("fn my_trim"));
1577 assert!(out.contains("uc my_trim $x"));
1579 }
1580
1581 #[test]
1582 fn user_func_extra_args_direct() {
1583 let out = convert("fn process { } process(uc($x), 42)");
1584 assert!(out.contains("fn process"));
1585 assert!(out.contains("uc process 42 $x"));
1587 }
1588
1589 #[test]
1590 fn map_grep_sort_chain_thread() {
1591 let out = convert("join(',', sort { $a <=> $b } map { $_ * 2 } grep { $_ > 0 } @nums)");
1592 assert!(out.contains("t @nums grep"));
1593 assert!(out.contains(" map"));
1594 assert!(out.contains(" sort"));
1595 assert!(out.contains(" join"));
1596 }
1597
1598 #[test]
1599 fn reduce_direct() {
1600 let out = convert("use List::Util 'reduce';\nreduce { $a + $b } @nums");
1602 assert!(out.contains("reduce {\n$a + $b\n} @nums"));
1603 }
1604
1605 #[test]
1606 fn shebang_prepended() {
1607 let p = parse("print 1").expect("parse failed");
1608 let out = convert_program(&p);
1609 assert!(out.starts_with("#!/usr/bin/env stryke\n"));
1610 }
1611
1612 #[test]
1613 fn indentation_in_blocks() {
1614 let out = convert("if ($x) { print 1; print 2; }");
1615 assert!(out.contains("\n print 1\n print 2\n"));
1617 }
1618
1619 #[test]
1620 fn binop_no_parens_at_top() {
1621 let out = convert("my $x = $a + $b");
1622 assert!(out.contains("= $a + $b"));
1624 assert!(!out.contains("= ($a + $b)"));
1625 }
1626
1627 fn convert_with_delim(code: &str, delim: char) -> String {
1628 let p = parse(code).expect("parse failed");
1629 let opts = ConvertOptions {
1630 output_delim: Some(delim),
1631 };
1632 let out = convert_program_with_options(&p, &opts);
1633 out.strip_prefix("#!/usr/bin/env stryke\n")
1634 .unwrap_or(&out)
1635 .to_string()
1636 }
1637
1638 #[test]
1639 fn output_delim_substitution() {
1640 let out = convert_with_delim("$x =~ s/foo/bar/g;", '|');
1641 assert_eq!(out, "$x =~ s|foo|bar|g");
1642 }
1643
1644 #[test]
1645 fn output_delim_transliterate() {
1646 let out = convert_with_delim("$y =~ tr/a-z/A-Z/;", '#');
1647 assert_eq!(out, "$y =~ tr#a-z#A-Z#");
1648 }
1649
1650 #[test]
1651 fn output_delim_match() {
1652 let out = convert_with_delim("$z =~ m/pattern/i;", '!');
1653 assert_eq!(out, "$z =~ !pattern!i");
1654 }
1655
1656 #[test]
1657 fn output_delim_preserves_original_when_none() {
1658 let out = convert("$x =~ s#old#new#g");
1659 assert_eq!(out, "$x =~ s#old#new#g");
1660 }
1661}