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 static VAL_VAR_DECLS: RefCell<bool> = const { RefCell::new(false) };
25}
26
27#[derive(Debug, Clone, Default)]
29pub struct ConvertOptions {
30 pub output_delim: Option<char>,
32 pub val_var_decls: bool,
36}
37
38fn get_output_delim() -> Option<char> {
39 OUTPUT_DELIM.with(|d| *d.borrow())
40}
41
42fn set_output_delim(delim: Option<char>) {
43 OUTPUT_DELIM.with(|d| *d.borrow_mut() = delim);
44}
45
46fn val_var_decls_enabled() -> bool {
47 VAL_VAR_DECLS.with(|d| *d.borrow())
48}
49
50fn set_val_var_decls(on: bool) {
51 VAL_VAR_DECLS.with(|d| *d.borrow_mut() = on);
52}
53
54fn decl_keyword(decls: &[VarDecl]) -> &'static str {
57 if !val_var_decls_enabled() {
58 "my"
59 } else if !decls.is_empty() && decls.iter().all(|d| d.frozen) {
60 "val"
61 } else {
62 "var"
63 }
64}
65
66fn choose_delim(original: char) -> char {
68 get_output_delim().unwrap_or(original)
69}
70
71pub fn convert_program(p: &Program) -> String {
75 convert_program_with_options(p, &ConvertOptions::default())
76}
77
78pub fn convert_program_with_options(p: &Program, opts: &ConvertOptions) -> String {
80 set_output_delim(opts.output_delim);
81 set_val_var_decls(opts.val_var_decls);
82 let body = convert_statements(&p.statements, 0);
83 set_output_delim(None);
84 set_val_var_decls(false);
85 format!("#!/usr/bin/env stryke\n{}", body)
86}
87
88fn convert_block(b: &Block, depth: usize) -> String {
91 convert_statements(b, depth)
92}
93
94fn convert_statements(stmts: &[Statement], depth: usize) -> String {
96 let mut out = Vec::new();
97 let mut i = 0;
98 while i < stmts.len() {
99 if let Some(merged) = try_merge_say_print(&stmts[i..], depth) {
101 out.push(merged);
102 i += 2; } else {
104 out.push(convert_statement(&stmts[i], depth));
105 i += 1;
106 }
107 }
108 out.join("\n")
109}
110
111fn try_merge_say_print(stmts: &[Statement], depth: usize) -> Option<String> {
114 if stmts.len() < 2 {
115 return None;
116 }
117 let pfx = indent(depth);
118
119 let (is_say, handle) = match &stmts[0].kind {
121 StmtKind::Expression(e) => match &e.kind {
122 ExprKind::Say { handle, args } if args.is_empty() => (true, handle),
123 ExprKind::Print { handle, args } if args.is_empty() => (false, handle),
124 _ => return None,
125 },
126 _ => return None,
127 };
128
129 if handle.is_some() {
131 return None;
132 }
133
134 let str_expr = match &stmts[1].kind {
136 StmtKind::Expression(e) => e,
137 _ => return None,
138 };
139
140 let cmd = if is_say { "p" } else { "print" };
142 let arg = convert_expr_top(str_expr);
143 Some(format!("{}{} {}", pfx, cmd, arg))
144}
145
146fn indent(depth: usize) -> String {
148 INDENT.repeat(depth)
149}
150
151fn convert_statement(s: &Statement, depth: usize) -> String {
152 let lab = s
153 .label
154 .as_ref()
155 .map(|l| format!("{}: ", l))
156 .unwrap_or_default();
157 let pfx = indent(depth);
158 let body = match &s.kind {
159 StmtKind::Expression(e) => convert_expr_top(e),
160 StmtKind::If {
161 condition,
162 body,
163 elsifs,
164 else_block,
165 } => {
166 let mut s = format!(
167 "if ({}) {{\n{}\n{}}}",
168 convert_expr_top(condition),
169 convert_block(body, depth + 1),
170 pfx
171 );
172 for (c, b) in elsifs {
173 s.push_str(&format!(
174 " elsif ({}) {{\n{}\n{}}}",
175 convert_expr_top(c),
176 convert_block(b, depth + 1),
177 pfx
178 ));
179 }
180 if let Some(eb) = else_block {
181 s.push_str(&format!(
182 " else {{\n{}\n{}}}",
183 convert_block(eb, depth + 1),
184 pfx
185 ));
186 }
187 s
188 }
189 StmtKind::Unless {
190 condition,
191 body,
192 else_block,
193 } => {
194 let mut s = format!(
195 "unless ({}) {{\n{}\n{}}}",
196 convert_expr_top(condition),
197 convert_block(body, depth + 1),
198 pfx
199 );
200 if let Some(eb) = else_block {
201 s.push_str(&format!(
202 " else {{\n{}\n{}}}",
203 convert_block(eb, depth + 1),
204 pfx
205 ));
206 }
207 s
208 }
209 StmtKind::While {
210 condition,
211 body,
212 label,
213 continue_block,
214 } => {
215 let lb = label
216 .as_ref()
217 .map(|l| format!("{}: ", l))
218 .unwrap_or_default();
219 let mut s = format!(
220 "{}while ({}) {{\n{}\n{}}}",
221 lb,
222 convert_expr_top(condition),
223 convert_block(body, depth + 1),
224 pfx
225 );
226 if let Some(cb) = continue_block {
227 s.push_str(&format!(
228 " continue {{\n{}\n{}}}",
229 convert_block(cb, depth + 1),
230 pfx
231 ));
232 }
233 s
234 }
235 StmtKind::Until {
236 condition,
237 body,
238 label,
239 continue_block,
240 } => {
241 let lb = label
242 .as_ref()
243 .map(|l| format!("{}: ", l))
244 .unwrap_or_default();
245 let mut s = format!(
246 "{}until ({}) {{\n{}\n{}}}",
247 lb,
248 convert_expr_top(condition),
249 convert_block(body, depth + 1),
250 pfx
251 );
252 if let Some(cb) = continue_block {
253 s.push_str(&format!(
254 " continue {{\n{}\n{}}}",
255 convert_block(cb, depth + 1),
256 pfx
257 ));
258 }
259 s
260 }
261 StmtKind::DoWhile { body, condition } => {
262 format!(
263 "do {{\n{}\n{}}} while ({})",
264 convert_block(body, depth + 1),
265 pfx,
266 convert_expr_top(condition)
267 )
268 }
269 StmtKind::For {
270 init,
271 condition,
272 step,
273 body,
274 label,
275 continue_block,
276 } => {
277 let lb = label
278 .as_ref()
279 .map(|l| format!("{}: ", l))
280 .unwrap_or_default();
281 let ini = init
282 .as_ref()
283 .map(|s| convert_statement_body(s))
284 .unwrap_or_default();
285 let cond = condition.as_ref().map(convert_expr).unwrap_or_default();
286 let st = step.as_ref().map(convert_expr).unwrap_or_default();
287 let mut s = format!(
288 "{}for ({}; {}; {}) {{\n{}\n{}}}",
289 lb,
290 ini,
291 cond,
292 st,
293 convert_block(body, depth + 1),
294 pfx
295 );
296 if let Some(cb) = continue_block {
297 s.push_str(&format!(
298 " continue {{\n{}\n{}}}",
299 convert_block(cb, depth + 1),
300 pfx
301 ));
302 }
303 s
304 }
305 StmtKind::Foreach {
306 var,
307 list,
308 body,
309 label,
310 continue_block,
311 } => {
312 let lb = label
313 .as_ref()
314 .map(|l| format!("{}: ", l))
315 .unwrap_or_default();
316 let mut s = format!(
317 "{}for ${} ({}) {{\n{}\n{}}}",
318 lb,
319 var,
320 convert_expr(list),
321 convert_block(body, depth + 1),
322 pfx
323 );
324 if let Some(cb) = continue_block {
325 s.push_str(&format!(
326 " continue {{\n{}\n{}}}",
327 convert_block(cb, depth + 1),
328 pfx
329 ));
330 }
331 s
332 }
333 StmtKind::SubDecl {
334 name,
335 params,
336 body,
337 prototype,
338 } => {
339 let sig = if !params.is_empty() {
340 format!(
341 " ({})",
342 params
343 .iter()
344 .map(fmt::format_sub_sig_param)
345 .collect::<Vec<_>>()
346 .join(", ")
347 )
348 } else {
349 prototype
350 .as_ref()
351 .map(|p| format!(" ({})", p))
352 .unwrap_or_default()
353 };
354 format!(
355 "fn {}{} {{\n{}\n{}}}",
356 name,
357 sig,
358 convert_block(body, depth + 1),
359 pfx
360 )
361 }
362 StmtKind::Package { name } => format!("package {}", name),
363 StmtKind::UsePerlVersion { version } => {
364 if version.fract() == 0.0 && *version >= 0.0 {
365 format!("use {}", *version as i64)
366 } else {
367 format!("use {}", version)
368 }
369 }
370 StmtKind::Use { module, imports, version } => {
371 let ver = version.as_deref().map(|v| format!(" {}", v)).unwrap_or_default();
372 if imports.is_empty() {
373 format!("use {}{}", module, ver)
374 } else {
375 format!("use {}{} {}", module, ver, convert_expr_list(imports))
376 }
377 }
378 StmtKind::UseOverload { pairs } => {
379 let inner = pairs
380 .iter()
381 .map(|(k, v)| {
382 format!(
383 "'{}' => '{}'",
384 k.replace('\'', "\\'"),
385 v.replace('\'', "\\'")
386 )
387 })
388 .collect::<Vec<_>>()
389 .join(", ");
390 format!("use overload {inner}")
391 }
392 StmtKind::No { module, imports } => {
393 if imports.is_empty() {
394 format!("no {}", module)
395 } else {
396 format!("no {} {}", module, convert_expr_list(imports))
397 }
398 }
399 StmtKind::Return(e) => e
400 .as_ref()
401 .map(|x| format!("return {}", convert_expr_top(x)))
402 .unwrap_or_else(|| "return".to_string()),
403 StmtKind::Last(l) => l
404 .as_ref()
405 .map(|x| format!("last {}", x))
406 .unwrap_or_else(|| "last".to_string()),
407 StmtKind::Next(l) => l
408 .as_ref()
409 .map(|x| format!("next {}", x))
410 .unwrap_or_else(|| "next".to_string()),
411 StmtKind::Redo(l) => l
412 .as_ref()
413 .map(|x| format!("redo {}", x))
414 .unwrap_or_else(|| "redo".to_string()),
415 StmtKind::My(decls) => format!("{} {}", decl_keyword(decls), convert_var_decls(decls)),
416 StmtKind::Our(decls) => format!("our {}", convert_var_decls(decls)),
417 StmtKind::Local(decls) => format!("local {}", convert_var_decls(decls)),
418 StmtKind::State(decls) => format!("state {}", convert_var_decls(decls)),
419 StmtKind::LocalExpr {
420 target,
421 initializer,
422 } => {
423 let mut s = format!("local {}", convert_expr(target));
424 if let Some(init) = initializer {
425 s.push_str(&format!(" = {}", convert_expr_top(init)));
426 }
427 s
428 }
429 StmtKind::MySync(decls) => format!("mysync {}", convert_var_decls(decls)),
430 StmtKind::OurSync(decls) => format!("oursync {}", convert_var_decls(decls)),
431 StmtKind::StmtGroup(b) => convert_block(b, depth),
432 StmtKind::Block(b) => format!("{{\n{}\n{}}}", convert_block(b, depth + 1), pfx),
433 StmtKind::Begin(b) => format!("BEGIN {{\n{}\n{}}}", convert_block(b, depth + 1), pfx),
434 StmtKind::UnitCheck(b) => {
435 format!("UNITCHECK {{\n{}\n{}}}", convert_block(b, depth + 1), pfx)
436 }
437 StmtKind::Check(b) => format!("CHECK {{\n{}\n{}}}", convert_block(b, depth + 1), pfx),
438 StmtKind::Init(b) => format!("INIT {{\n{}\n{}}}", convert_block(b, depth + 1), pfx),
439 StmtKind::End(b) => format!("END {{\n{}\n{}}}", convert_block(b, depth + 1), pfx),
440 StmtKind::Empty => String::new(),
441 StmtKind::Goto { target } => format!("goto {}", convert_expr(target)),
442 StmtKind::Continue(b) => format!("continue {{\n{}\n{}}}", convert_block(b, depth + 1), pfx),
443 StmtKind::StructDecl { def } => {
444 let fields = def
445 .fields
446 .iter()
447 .map(|f| format!("{} => {}", f.name, f.ty.display_name()))
448 .collect::<Vec<_>>()
449 .join(", ");
450 format!("struct {} {{ {} }}", def.name, fields)
451 }
452 StmtKind::EnumDecl { def } => {
453 let variants = def
454 .variants
455 .iter()
456 .map(|v| {
457 if let Some(ty) = &v.ty {
458 format!("{} => {}", v.name, ty.display_name())
459 } else {
460 v.name.clone()
461 }
462 })
463 .collect::<Vec<_>>()
464 .join(", ");
465 format!("enum {} {{ {} }}", def.name, variants)
466 }
467 StmtKind::ClassDecl { def } => {
468 let prefix = if def.is_abstract {
469 "abstract "
470 } else if def.is_final {
471 "final "
472 } else {
473 ""
474 };
475 let mut parts = vec![format!("{}class {}", prefix, def.name)];
476 if !def.extends.is_empty() {
477 parts.push(format!("extends {}", def.extends.join(", ")));
478 }
479 if !def.implements.is_empty() {
480 parts.push(format!("impl {}", def.implements.join(", ")));
481 }
482 let fields = def
483 .fields
484 .iter()
485 .map(|f| {
486 let vis = match f.visibility {
487 crate::ast::Visibility::Private => "priv ",
488 crate::ast::Visibility::Protected => "prot ",
489 crate::ast::Visibility::Public => "",
490 };
491 format!("{}{}: {}", vis, f.name, f.ty.display_name())
492 })
493 .collect::<Vec<_>>()
494 .join("; ");
495 format!("{} {{ {} }}", parts.join(" "), fields)
496 }
497 StmtKind::TraitDecl { def } => {
498 let methods = def
499 .methods
500 .iter()
501 .map(|m| format!("fn {}", m.name))
502 .collect::<Vec<_>>()
503 .join("; ");
504 format!("trait {} {{ {} }}", def.name, methods)
505 }
506 StmtKind::EvalTimeout { timeout, body } => {
507 format!(
508 "eval_timeout {} {{\n{}\n{}}}",
509 convert_expr(timeout),
510 convert_block(body, depth + 1),
511 pfx
512 )
513 }
514 StmtKind::TryCatch {
515 try_block,
516 catch_var,
517 catch_block,
518 finally_block,
519 } => {
520 let fin = finally_block
521 .as_ref()
522 .map(|b| {
523 format!(
524 "\n{}finally {{\n{}\n{}}}",
525 pfx,
526 convert_block(b, depth + 1),
527 pfx
528 )
529 })
530 .unwrap_or_default();
531 format!(
532 "try {{\n{}\n{}}} catch (${}) {{\n{}\n{}}}{}",
533 convert_block(try_block, depth + 1),
534 pfx,
535 catch_var,
536 convert_block(catch_block, depth + 1),
537 pfx,
538 fin
539 )
540 }
541 StmtKind::Given { topic, body } => {
542 format!(
543 "given ({}) {{\n{}\n{}}}",
544 convert_expr(topic),
545 convert_block(body, depth + 1),
546 pfx
547 )
548 }
549 StmtKind::When { cond, body } => {
550 format!(
551 "when ({}) {{\n{}\n{}}}",
552 convert_expr(cond),
553 convert_block(body, depth + 1),
554 pfx
555 )
556 }
557 StmtKind::DefaultCase { body } => {
558 format!("default {{\n{}\n{}}}", convert_block(body, depth + 1), pfx)
559 }
560 StmtKind::FormatDecl { name, lines } => {
561 let mut s = format!("format {} =\n", name);
562 for ln in lines {
563 s.push_str(ln);
564 s.push('\n');
565 }
566 s.push('.');
567 s
568 }
569 StmtKind::AdviceDecl {
570 kind,
571 pattern,
572 body,
573 } => {
574 let kw = match kind {
575 crate::ast::AdviceKind::Before => "before",
576 crate::ast::AdviceKind::After => "after",
577 crate::ast::AdviceKind::Around => "around",
578 };
579 format!(
580 "{} \"{}\" {{\n{}\n{}}}",
581 kw,
582 pattern,
583 convert_block(body, depth + 1),
584 pfx
585 )
586 }
587 StmtKind::Tie {
588 target,
589 class,
590 args,
591 } => {
592 let target_s = match target {
593 crate::ast::TieTarget::Hash(h) => format!("%{}", h),
594 crate::ast::TieTarget::Array(a) => format!("@{}", a),
595 crate::ast::TieTarget::Scalar(s) => format!("${}", s),
596 };
597 let mut s = format!("tie {} {}", target_s, convert_expr(class));
598 for a in args {
599 s.push_str(&format!(", {}", convert_expr(a)));
600 }
601 s
602 }
603 };
604 format!("{}{}{}", pfx, lab, body)
605}
606
607fn convert_statement_body(s: &Statement) -> String {
609 let lab = s
610 .label
611 .as_ref()
612 .map(|l| format!("{}: ", l))
613 .unwrap_or_default();
614 let body = match &s.kind {
615 StmtKind::Expression(e) => convert_expr_top(e),
616 StmtKind::My(decls) => format!("{} {}", decl_keyword(decls), convert_var_decls(decls)),
617 _ => convert_statement(s, 0).trim().to_string(),
618 };
619 format!("{}{}", lab, body)
620}
621
622fn convert_var_decls(decls: &[VarDecl]) -> String {
625 decls
626 .iter()
627 .map(|d| {
628 let sig = match d.sigil {
629 Sigil::Scalar => "$",
630 Sigil::Array => "@",
631 Sigil::Hash => "%",
632 Sigil::Typeglob => "*",
633 };
634 let mut s = format!("{}{}", sig, d.name);
635 if let Some(ref t) = d.type_annotation {
636 s.push_str(&format!(" : {}", t.display_name()));
637 }
638 if let Some(ref init) = d.initializer {
639 s.push_str(&format!(" = {}", convert_expr_top(init)));
640 }
641 s
642 })
643 .collect::<Vec<_>>()
644 .join(", ")
645}
646
647fn convert_expr_list(es: &[Expr]) -> String {
650 es.iter().map(convert_expr).collect::<Vec<_>>().join(", ")
651}
652
653fn convert_string_part(p: &StringPart) -> String {
656 match p {
657 StringPart::Literal(s) => fmt::escape_interpolated_literal(s),
658 StringPart::ScalarVar(n) => {
659 if needs_braces(n) {
661 format!("${{{}}}", n)
662 } else {
663 format!("${}", n)
664 }
665 }
666 StringPart::ArrayVar(n) => {
667 if needs_braces(n) {
668 format!("@{{{}}}", n)
669 } else {
670 format!("@{}", n)
671 }
672 }
673 StringPart::Expr(e) => fmt::format_expr(e),
674 }
675}
676
677fn needs_braces(name: &str) -> bool {
679 if name.is_empty() || name.chars().next().is_some_and(|c| c.is_ascii_digit()) {
681 return true;
682 }
683 !name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
685}
686
687fn convert_expr_top(e: &Expr) -> String {
690 convert_expr_impl(e, true)
691}
692
693fn convert_expr(e: &Expr) -> String {
696 convert_expr_impl(e, false)
697}
698
699fn convert_expr_impl(e: &Expr, top: bool) -> String {
700 let mut segments: Vec<String> = Vec::new();
701 let source = extract_pipe_source(e, &mut segments);
702 if !segments.is_empty() {
703 segments.reverse();
704 if segments.len() <= 2 {
707 let result = format!("{} {}", segments.join(" "), source);
708 if !top {
709 return format!("({})", result);
710 }
711 return result;
712 }
713 let stages = segments.join(" ");
715 let source = if source.starts_with("(t ") || source.starts_with("((") {
717 source[1..source.len() - 1].to_string()
718 } else {
719 source
720 };
721 let result = format!("t {} {}", source, stages);
722 if !top {
723 return format!("({})", result);
724 }
725 return result;
726 }
727 convert_expr_direct(e, top)
729}
730
731fn extract_pipe_source(e: &Expr, segments: &mut Vec<String>) -> String {
738 match &e.kind {
739 ExprKind::Uc(inner) => {
741 segments.push("uc".into());
742 extract_pipe_source(inner, segments)
743 }
744 ExprKind::Lc(inner) => {
745 segments.push("lc".into());
746 extract_pipe_source(inner, segments)
747 }
748 ExprKind::Ucfirst(inner) => {
749 segments.push("ucfirst".into());
750 extract_pipe_source(inner, segments)
751 }
752 ExprKind::Lcfirst(inner) => {
753 segments.push("lcfirst".into());
754 extract_pipe_source(inner, segments)
755 }
756 ExprKind::Fc(inner) => {
757 segments.push("fc".into());
758 extract_pipe_source(inner, segments)
759 }
760 ExprKind::Chomp(inner) => {
761 segments.push("chomp".into());
762 extract_pipe_source(inner, segments)
763 }
764 ExprKind::Chop(inner) => {
765 segments.push("chop".into());
766 extract_pipe_source(inner, segments)
767 }
768 ExprKind::Length(inner) => {
769 segments.push("length".into());
770 extract_pipe_source(inner, segments)
771 }
772 ExprKind::Abs(inner) => {
773 segments.push("abs".into());
774 extract_pipe_source(inner, segments)
775 }
776 ExprKind::Int(inner) => {
777 segments.push("int".into());
778 extract_pipe_source(inner, segments)
779 }
780 ExprKind::Sqrt(inner) => {
781 segments.push("sqrt".into());
782 extract_pipe_source(inner, segments)
783 }
784 ExprKind::Sin(inner) => {
785 segments.push("sin".into());
786 extract_pipe_source(inner, segments)
787 }
788 ExprKind::Cos(inner) => {
789 segments.push("cos".into());
790 extract_pipe_source(inner, segments)
791 }
792 ExprKind::Exp(inner) => {
793 segments.push("exp".into());
794 extract_pipe_source(inner, segments)
795 }
796 ExprKind::Log(inner) => {
797 segments.push("log".into());
798 extract_pipe_source(inner, segments)
799 }
800 ExprKind::Hex(inner) => {
801 segments.push("hex".into());
802 extract_pipe_source(inner, segments)
803 }
804 ExprKind::Oct(inner) => {
805 segments.push("oct".into());
806 extract_pipe_source(inner, segments)
807 }
808 ExprKind::Chr(inner) => {
809 segments.push("chr".into());
810 extract_pipe_source(inner, segments)
811 }
812 ExprKind::Ord(inner) => {
813 segments.push("ord".into());
814 extract_pipe_source(inner, segments)
815 }
816 ExprKind::Defined(inner) => {
817 segments.push("defined".into());
818 extract_pipe_source(inner, segments)
819 }
820 ExprKind::Ref(inner) => {
821 segments.push("ref".into());
822 extract_pipe_source(inner, segments)
823 }
824 ExprKind::ScalarContext(inner) => {
825 segments.push("scalar".into());
826 extract_pipe_source(inner, segments)
827 }
828 ExprKind::Keys(inner) => {
829 segments.push("keys".into());
830 extract_pipe_source(inner, segments)
831 }
832 ExprKind::Values(inner) => {
833 segments.push("values".into());
834 extract_pipe_source(inner, segments)
835 }
836 ExprKind::Each(inner) => {
837 segments.push("each".into());
838 extract_pipe_source(inner, segments)
839 }
840 ExprKind::Pop(inner) => {
841 segments.push("pop".into());
842 extract_pipe_source(inner, segments)
843 }
844 ExprKind::Shift(inner) => {
845 segments.push("shift".into());
846 extract_pipe_source(inner, segments)
847 }
848 ExprKind::ReverseExpr(inner) => {
849 segments.push("reverse".into());
850 extract_pipe_source(inner, segments)
851 }
852 ExprKind::Slurp(inner) => {
853 segments.push("slurp".into());
854 extract_pipe_source(inner, segments)
855 }
856 ExprKind::Swallow(inner) => {
857 segments.push("swallow".into());
858 extract_pipe_source(inner, segments)
859 }
860 ExprKind::Burp(inner) => {
861 segments.push("burp".into());
862 extract_pipe_source(inner, segments)
863 }
864 ExprKind::God(inner) => {
865 segments.push("god".into());
866 extract_pipe_source(inner, segments)
867 }
868 ExprKind::Ingest(inner) => {
869 segments.push("ingest".into());
870 extract_pipe_source(inner, segments)
871 }
872 ExprKind::Chdir(inner) => {
873 segments.push("chdir".into());
874 extract_pipe_source(inner, segments)
875 }
876 ExprKind::Stat(inner) => {
877 segments.push("stat".into());
878 extract_pipe_source(inner, segments)
879 }
880 ExprKind::Lstat(inner) => {
881 segments.push("lstat".into());
882 extract_pipe_source(inner, segments)
883 }
884 ExprKind::Readlink(inner) => {
885 segments.push("readlink".into());
886 extract_pipe_source(inner, segments)
887 }
888 ExprKind::Study(inner) => {
889 segments.push("study".into());
890 extract_pipe_source(inner, segments)
891 }
892 ExprKind::Close(inner) => {
893 segments.push("close".into());
894 extract_pipe_source(inner, segments)
895 }
896 ExprKind::Readdir(inner) => {
897 segments.push("readdir".into());
898 extract_pipe_source(inner, segments)
899 }
900 ExprKind::Eval(inner) => {
901 segments.push("eval".into());
902 extract_pipe_source(inner, segments)
903 }
904 ExprKind::Require(inner) => {
905 segments.push("require".into());
906 extract_pipe_source(inner, segments)
907 }
908
909 ExprKind::MapExpr {
911 block,
912 list,
913 flatten_array_refs,
914 stream,
915 } => {
916 let kw = match (*flatten_array_refs, *stream) {
917 (true, true) => "flat_maps",
918 (true, false) => "flat_map",
919 (false, true) => "maps",
920 (false, false) => "map",
921 };
922 segments.push(format!("{} {{\n{}\n}}", kw, convert_block(block, 0)));
923 extract_pipe_source(list, segments)
924 }
925 ExprKind::MapExprComma {
926 expr,
927 list,
928 flatten_array_refs,
929 stream,
930 } => {
931 let kw = match (*flatten_array_refs, *stream) {
932 (true, true) => "flat_maps",
933 (true, false) => "flat_map",
934 (false, true) => "maps",
935 (false, false) => "map",
936 };
937 segments.push(format!("{} {{ {} }}", kw, convert_expr_top(expr)));
939 extract_pipe_source(list, segments)
940 }
941 ExprKind::GrepExpr {
942 block,
943 list,
944 keyword,
945 } => {
946 segments.push(format!(
947 "{} {{\n{}\n}}",
948 keyword.as_str(),
949 convert_block(block, 0)
950 ));
951 extract_pipe_source(list, segments)
952 }
953 ExprKind::GrepExprComma {
954 expr,
955 list,
956 keyword,
957 } => {
958 segments.push(format!(
959 "{} {{ {} }}",
960 keyword.as_str(),
961 convert_expr_top(expr)
962 ));
963 extract_pipe_source(list, segments)
964 }
965 ExprKind::SortExpr { cmp, list } => {
966 let seg = match cmp {
967 Some(SortComparator::Block(b)) => {
968 format!("sort {{\n{}\n}}", convert_block(b, 0))
969 }
970 Some(SortComparator::Code(e)) => {
971 format!("sort {}", convert_expr(e))
972 }
973 None => "sort".to_string(),
974 };
975 segments.push(seg);
976 extract_pipe_source(list, segments)
977 }
978 ExprKind::JoinExpr { separator, list } => {
979 segments.push(format!("join {}", convert_expr(separator)));
980 extract_pipe_source(list, segments)
981 }
982 ExprKind::ReduceExpr { block, list } => {
983 segments.push(format!("reduce {{\n{}\n}}", convert_block(block, 0)));
984 extract_pipe_source(list, segments)
985 }
986 ExprKind::ForEachExpr { block, list } => {
987 segments.push(format!("fore {{\n{}\n}}", convert_block(block, 0)));
988 extract_pipe_source(list, segments)
989 }
990
991 ExprKind::PMapExpr {
993 block,
994 list,
995 progress,
996 flat_outputs,
997 on_cluster,
998 stream: _,
999 } if progress.is_none() && on_cluster.is_none() => {
1000 let kw = if *flat_outputs { "pflat_map" } else { "pmap" };
1001 segments.push(format!("{} {{\n{}\n}}", kw, convert_block(block, 0)));
1002 extract_pipe_source(list, segments)
1003 }
1004 ExprKind::PGrepExpr {
1005 block,
1006 list,
1007 progress,
1008 stream: _,
1009 } if progress.is_none() => {
1010 segments.push(format!("pgrep {{\n{}\n}}", convert_block(block, 0)));
1011 extract_pipe_source(list, segments)
1012 }
1013 ExprKind::PSortExpr {
1014 cmp,
1015 list,
1016 progress,
1017 } if progress.is_none() => {
1018 let seg = match cmp {
1019 Some(b) => format!("psort {{\n{}\n}}", convert_block(b, 0)),
1020 None => "psort".to_string(),
1021 };
1022 segments.push(seg);
1023 extract_pipe_source(list, segments)
1024 }
1025
1026 ExprKind::Say { handle: None, args } if args.len() == 1 => {
1029 segments.push("p".into());
1030 extract_pipe_source(&args[0], segments)
1031 }
1032 ExprKind::Print { handle: None, args } if args.len() == 1 => {
1033 segments.push("print".into());
1034 extract_pipe_source(&args[0], segments)
1035 }
1036
1037 ExprKind::FuncCall { name, args } if !args.is_empty() => {
1039 let seg = if args.len() == 1 {
1040 name.clone()
1041 } else {
1042 let rest = args[1..]
1043 .iter()
1044 .map(convert_expr)
1045 .collect::<Vec<_>>()
1046 .join(", ");
1047 format!("{} {}", name, rest)
1048 };
1049 segments.push(seg);
1050 extract_pipe_source(&args[0], segments)
1051 }
1052
1053 ExprKind::Substitution {
1055 expr,
1056 pattern,
1057 replacement,
1058 flags,
1059 delim,
1060 } if flags.contains('r') => {
1061 let d = choose_delim(*delim);
1065 segments.push(format!(
1066 "s{}{}{}{}{}{}",
1067 d,
1068 fmt::escape_regex_part(pattern),
1069 d,
1070 fmt::escape_regex_part(replacement),
1071 d,
1072 flags
1073 ));
1074 extract_pipe_source(expr, segments)
1075 }
1076
1077 ExprKind::Transliterate {
1079 expr,
1080 from,
1081 to,
1082 flags,
1083 delim,
1084 } if flags.contains('r') => {
1085 let d = choose_delim(*delim);
1086 segments.push(format!(
1087 "tr{}{}{}{}{}{}",
1088 d,
1089 fmt::escape_regex_part(from),
1090 d,
1091 fmt::escape_regex_part(to),
1092 d,
1093 flags
1094 ));
1095 extract_pipe_source(expr, segments)
1096 }
1097
1098 ExprKind::List(elems) if elems.len() == 1 => extract_pipe_source(&elems[0], segments),
1100
1101 _ => convert_expr_direct(e, false),
1103 }
1104}
1105
1106fn convert_expr_direct(e: &Expr, top: bool) -> String {
1112 match &e.kind {
1113 ExprKind::Integer(_)
1115 | ExprKind::Float(_)
1116 | ExprKind::String(_)
1117 | ExprKind::Bareword(_)
1118 | ExprKind::Regex(..)
1119 | ExprKind::QW(_)
1120 | ExprKind::Undef
1121 | ExprKind::MagicConst(_)
1122 | ExprKind::ScalarVar(_)
1123 | ExprKind::ArrayVar(_)
1124 | ExprKind::HashVar(_)
1125 | ExprKind::Typeglob(_)
1126 | ExprKind::Wantarray
1127 | ExprKind::SubroutineRef(_)
1128 | ExprKind::SubroutineCodeRef(_) => fmt::format_expr(e),
1129
1130 ExprKind::InterpolatedString(parts) => {
1132 format!(
1133 "\"{}\"",
1134 parts.iter().map(convert_string_part).collect::<String>()
1135 )
1136 }
1137
1138 ExprKind::BinOp { left, op, right } => {
1140 format!(
1141 "{} {} {}",
1142 convert_expr(left),
1143 fmt::format_binop(*op),
1144 convert_expr(right)
1145 )
1146 }
1147
1148 ExprKind::UnaryOp { op, expr } => {
1150 format!("{}{}", fmt::format_unary(*op), convert_expr(expr))
1151 }
1152 ExprKind::PostfixOp { expr, op } => {
1153 format!("{}{}", convert_expr(expr), fmt::format_postfix(*op))
1154 }
1155
1156 ExprKind::Assign { target, value } => {
1158 format!("{} = {}", convert_expr(target), convert_expr_top(value))
1159 }
1160 ExprKind::CompoundAssign { target, op, value } => format!(
1161 "{} {}= {}",
1162 convert_expr(target),
1163 fmt::format_binop(*op),
1164 convert_expr_top(value)
1165 ),
1166
1167 ExprKind::Ternary {
1169 condition,
1170 then_expr,
1171 else_expr,
1172 } => format!(
1173 "{} ? {} : {}",
1174 convert_expr(condition),
1175 convert_expr(then_expr),
1176 convert_expr(else_expr)
1177 ),
1178
1179 ExprKind::SliceRange { from, to, step } => {
1181 let f = from.as_ref().map(|e| convert_expr(e)).unwrap_or_default();
1182 let t = to.as_ref().map(|e| convert_expr(e)).unwrap_or_default();
1183 match step {
1184 Some(s) => format!("{}:{}:{}", f, t, convert_expr(s)),
1185 None => format!("{}:{}", f, t),
1186 }
1187 }
1188 ExprKind::Range {
1189 from,
1190 to,
1191 exclusive,
1192 step,
1193 } => {
1194 let op = if *exclusive { "..." } else { ".." };
1195 if let Some(s) = step {
1196 format!(
1197 "{} {} {}:{}",
1198 convert_expr(from),
1199 op,
1200 convert_expr(to),
1201 convert_expr(s)
1202 )
1203 } else {
1204 format!("{} {} {}", convert_expr(from), op, convert_expr(to))
1205 }
1206 }
1207 ExprKind::Repeat {
1208 expr,
1209 count,
1210 list_repeat,
1211 } => {
1212 if *list_repeat && !matches!(expr.kind, ExprKind::List(_) | ExprKind::QW(_)) {
1215 format!("({}) x {}", convert_expr(expr), convert_expr(count))
1216 } else {
1217 format!("{} x {}", convert_expr(expr), convert_expr(count))
1218 }
1219 }
1220
1221 ExprKind::FuncCall { name, args } => format!("{}({})", name, convert_expr_list(args)),
1223 ExprKind::MethodCall {
1224 object,
1225 method,
1226 args,
1227 super_call,
1228 } => {
1229 let m = if *super_call {
1230 format!("SUPER::{}", method)
1231 } else {
1232 method.clone()
1233 };
1234 format!(
1235 "{}->{}({})",
1236 convert_expr(object),
1237 m,
1238 convert_expr_list(args)
1239 )
1240 }
1241 ExprKind::IndirectCall {
1242 target,
1243 args,
1244 ampersand,
1245 pass_caller_arglist,
1246 } => {
1247 if *pass_caller_arglist && args.is_empty() {
1248 format!("&{}", convert_expr(target))
1249 } else {
1250 let inner = format!("{}({})", convert_expr(target), convert_expr_list(args));
1251 if *ampersand {
1252 format!("&{}", inner)
1253 } else {
1254 inner
1255 }
1256 }
1257 }
1258
1259 ExprKind::List(exprs) => format!("({})", convert_expr_list(exprs)),
1261 ExprKind::ArrayRef(elems) => format!("[{}]", convert_expr_list(elems)),
1262 ExprKind::HashRef(pairs) => {
1263 let inner = pairs
1264 .iter()
1265 .map(|(k, v)| format!("{} => {}", convert_expr(k), convert_expr(v)))
1266 .collect::<Vec<_>>()
1267 .join(", ");
1268 format!("{{{}}}", inner)
1269 }
1270 ExprKind::CodeRef { params, body } => {
1271 if params.is_empty() {
1272 format!("fn {{\n{}\n}}", convert_block(body, 0))
1273 } else {
1274 let sig = params
1275 .iter()
1276 .map(fmt::format_sub_sig_param)
1277 .collect::<Vec<_>>()
1278 .join(", ");
1279 format!("fn ({}) {{\n{}\n}}", sig, convert_block(body, 0))
1280 }
1281 }
1282
1283 ExprKind::ArrayElement { array, index } => {
1285 format!("${}[{}]", array, convert_expr(index))
1286 }
1287 ExprKind::HashElement { hash, key } => {
1288 format!("${}{{{}}}", hash, convert_expr(key))
1289 }
1290 ExprKind::ScalarRef(inner) => format!("\\{}", convert_expr(inner)),
1291 ExprKind::ArrowDeref { expr, index, kind } => match kind {
1292 DerefKind::Array => {
1293 format!("({})->[{}]", convert_expr(expr), convert_expr(index))
1294 }
1295 DerefKind::Hash => {
1296 format!("({})->{{{}}}", convert_expr(expr), convert_expr(index))
1297 }
1298 DerefKind::Call => {
1299 format!("({})->({})", convert_expr(expr), convert_expr(index))
1300 }
1301 },
1302 ExprKind::Deref { expr, kind } => match kind {
1303 Sigil::Scalar => format!("${{{}}}", convert_expr(expr)),
1304 Sigil::Array => format!("@{{${}}}", convert_expr(expr)),
1305 Sigil::Hash => format!("%{{${}}}", convert_expr(expr)),
1306 Sigil::Typeglob => format!("*{{${}}}", convert_expr(expr)),
1307 },
1308
1309 ExprKind::Print { handle, args } => {
1312 let h = handle
1313 .as_ref()
1314 .map(|h| format!("{} ", h))
1315 .unwrap_or_default();
1316 format!("print {}{}", h, convert_expr_list(args))
1317 }
1318 ExprKind::Say { handle, args } => {
1319 if let Some(h) = handle {
1320 format!("say {} {}", h, convert_expr_list(args))
1321 } else {
1322 format!("p {}", convert_expr_list(args))
1323 }
1324 }
1325 ExprKind::Printf { handle, args } => {
1326 let h = handle
1327 .as_ref()
1328 .map(|h| format!("{} ", h))
1329 .unwrap_or_default();
1330 format!("printf {}{}", h, convert_expr_list(args))
1331 }
1332 ExprKind::Die(args) => {
1333 if args.is_empty() {
1334 "die".to_string()
1335 } else {
1336 format!("die {}", convert_expr_list(args))
1337 }
1338 }
1339 ExprKind::Warn(args) => {
1340 if args.is_empty() {
1341 "warn".to_string()
1342 } else {
1343 format!("warn {}", convert_expr_list(args))
1344 }
1345 }
1346
1347 ExprKind::Match {
1349 expr,
1350 pattern,
1351 flags,
1352 delim,
1353 ..
1354 } => {
1355 let d = choose_delim(*delim);
1356 format!(
1357 "{} =~ {}{}{}{}",
1358 convert_expr(expr),
1359 d,
1360 fmt::escape_regex_part(pattern),
1361 d,
1362 flags
1363 )
1364 }
1365 ExprKind::Substitution {
1366 expr,
1367 pattern,
1368 replacement,
1369 flags,
1370 delim,
1371 } => {
1372 let d = choose_delim(*delim);
1373 format!(
1374 "{} =~ s{}{}{}{}{}{}",
1375 convert_expr(expr),
1376 d,
1377 fmt::escape_regex_part(pattern),
1378 d,
1379 fmt::escape_regex_part(replacement),
1380 d,
1381 flags
1382 )
1383 }
1384 ExprKind::Transliterate {
1385 expr,
1386 from,
1387 to,
1388 flags,
1389 delim,
1390 } => {
1391 let d = choose_delim(*delim);
1392 format!(
1393 "{} =~ tr{}{}{}{}{}{}",
1394 convert_expr(expr),
1395 d,
1396 fmt::escape_regex_part(from),
1397 d,
1398 fmt::escape_regex_part(to),
1399 d,
1400 flags
1401 )
1402 }
1403
1404 ExprKind::PostfixIf { expr, condition } => {
1406 format!("{} if {}", convert_expr_top(expr), convert_expr(condition))
1407 }
1408 ExprKind::PostfixUnless { expr, condition } => {
1409 format!(
1410 "{} unless {}",
1411 convert_expr_top(expr),
1412 convert_expr(condition)
1413 )
1414 }
1415 ExprKind::PostfixWhile { expr, condition } => {
1416 format!(
1417 "{} while {}",
1418 convert_expr_top(expr),
1419 convert_expr(condition)
1420 )
1421 }
1422 ExprKind::PostfixUntil { expr, condition } => {
1423 format!(
1424 "{} until {}",
1425 convert_expr_top(expr),
1426 convert_expr(condition)
1427 )
1428 }
1429 ExprKind::PostfixForeach { expr, list } => {
1430 format!("{} for {}", convert_expr_top(expr), convert_expr(list))
1431 }
1432
1433 ExprKind::MapExpr {
1435 block,
1436 list,
1437 flatten_array_refs,
1438 stream,
1439 } => {
1440 let kw = match (*flatten_array_refs, *stream) {
1441 (true, true) => "flat_maps",
1442 (true, false) => "flat_map",
1443 (false, true) => "maps",
1444 (false, false) => "map",
1445 };
1446 format!(
1447 "{} {{\n{}\n}} {}",
1448 kw,
1449 convert_block(block, 0),
1450 convert_expr(list)
1451 )
1452 }
1453 ExprKind::GrepExpr {
1454 block,
1455 list,
1456 keyword,
1457 } => {
1458 format!(
1459 "{} {{\n{}\n}} {}",
1460 keyword.as_str(),
1461 convert_block(block, 0),
1462 convert_expr(list)
1463 )
1464 }
1465 ExprKind::SortExpr { cmp, list } => match cmp {
1466 Some(SortComparator::Block(b)) => {
1467 format!(
1468 "sort {{\n{}\n}} {}",
1469 convert_block(b, 0),
1470 convert_expr(list)
1471 )
1472 }
1473 Some(SortComparator::Code(e)) => {
1474 format!("sort {} {}", convert_expr(e), convert_expr(list))
1475 }
1476 None => format!("sort {}", convert_expr(list)),
1477 },
1478 ExprKind::JoinExpr { separator, list } => {
1479 format!("join({}, {})", convert_expr(separator), convert_expr(list))
1480 }
1481 ExprKind::SplitExpr {
1482 pattern,
1483 string,
1484 limit,
1485 } => match limit {
1486 Some(l) => format!(
1487 "split({}, {}, {})",
1488 convert_expr(pattern),
1489 convert_expr(string),
1490 convert_expr(l)
1491 ),
1492 None => format!("split({}, {})", convert_expr(pattern), convert_expr(string)),
1493 },
1494
1495 ExprKind::Bless { ref_expr, class } => match class {
1497 Some(c) => format!("bless({}, {})", convert_expr(ref_expr), convert_expr(c)),
1498 None => format!("bless({})", convert_expr(ref_expr)),
1499 },
1500
1501 ExprKind::Push { array, values } => {
1503 format!(
1504 "push({}, {})",
1505 convert_expr(array),
1506 convert_expr_list(values)
1507 )
1508 }
1509 ExprKind::Unshift { array, values } => {
1510 format!(
1511 "unshift({}, {})",
1512 convert_expr(array),
1513 convert_expr_list(values)
1514 )
1515 }
1516
1517 ExprKind::AlgebraicMatch { subject, arms } => {
1519 let arms_s = arms
1520 .iter()
1521 .map(|a| {
1522 let guard_s = a
1523 .guard
1524 .as_ref()
1525 .map(|g| format!(" if {}", convert_expr(g)))
1526 .unwrap_or_default();
1527 format!(
1528 "{}{} => {}",
1529 fmt::format_match_pattern(&a.pattern),
1530 guard_s,
1531 convert_expr(&a.body)
1532 )
1533 })
1534 .collect::<Vec<_>>()
1535 .join(", ");
1536 format!("match ({}) {{ {} }}", convert_expr(subject), arms_s)
1537 }
1538
1539 _ => fmt::format_expr(e),
1541 }
1542}
1543
1544#[cfg(test)]
1547mod tests {
1548 use super::*;
1549 use crate::parse;
1550
1551 fn convert(code: &str) -> String {
1553 let p = parse(code).expect("parse failed");
1554 let out = convert_program(&p);
1555 out.strip_prefix("#!/usr/bin/env stryke\n")
1557 .unwrap_or(&out)
1558 .to_string()
1559 }
1560
1561 #[test]
1562 fn unary_builtin_direct() {
1563 assert_eq!(convert("uc($x)"), "uc $x");
1565 assert_eq!(convert("length($str)"), "length $str");
1566 }
1567
1568 #[test]
1569 fn nested_unary_direct() {
1570 let out = convert("uc(lc($x))");
1572 assert_eq!(out, "lc uc $x");
1573 }
1574
1575 #[test]
1576 fn nested_builtin_chain_thread() {
1577 let out = convert("chomp(lc(uc($x)))");
1578 assert_eq!(out, "t $x uc lc chomp");
1579 }
1580
1581 #[test]
1582 fn deeply_nested_thread() {
1583 let out = convert("length(chomp(lc(uc($x))))");
1584 assert_eq!(out, "t $x uc lc chomp length");
1585 }
1586
1587 #[test]
1588 fn map_grep_sort_thread() {
1589 let out = convert("sort { $a <=> $b } map { $_ * 2 } grep { $_ > 0 } @numbers");
1590 assert!(out.contains("t @numbers grep"));
1591 assert!(out.contains(" map"));
1592 assert!(out.contains(" sort"));
1593 }
1594
1595 #[test]
1596 fn join_direct() {
1597 let out = convert(r#"join(",", sort(@arr))"#);
1598 assert!(out.contains("sort join \",\" @arr"));
1600 }
1601
1602 #[test]
1603 fn no_semicolons() {
1604 let out = convert("my $x = 1;\nmy $y = 2");
1605 assert!(!out.contains(';'));
1606 assert!(out.contains("my $x = 1"));
1607 assert!(out.contains("my $y = 2"));
1608 }
1609
1610 #[test]
1611 fn assignment_rhs_direct() {
1612 let out = convert("my $x = uc(lc($str))");
1613 assert_eq!(out, "my $x = lc uc $str");
1615 }
1616
1617 #[test]
1618 fn chain_in_subexpression_parenthesized() {
1619 let out = convert("$x + uc(lc($str))");
1620 assert!(out.contains("(lc uc $str)"));
1622 }
1623
1624 #[test]
1625 fn fn_body_indented() {
1626 let out = convert("fn foo { return uc(lc($x)); }");
1627 assert!(out.contains("fn foo"));
1628 assert!(out.contains("lc uc $x"));
1630 assert!(out.contains(" return"));
1632 }
1633
1634 #[test]
1635 fn if_condition_converted() {
1636 let out = convert("if (defined(length($x))) { 1; }");
1637 assert!(out.contains("length defined $x"));
1639 }
1640
1641 #[test]
1642 fn method_call_preserved() {
1643 let out = convert("$obj->method($x)");
1644 assert!(out.contains("->method"));
1645 }
1646
1647 #[test]
1648 fn substitution_r_flag_direct() {
1649 let out = convert(r#"($str =~ s/old/new/r)"#);
1651 assert!(out.contains("s/old/new/r $str"));
1652 }
1653
1654 #[test]
1655 fn user_func_call_direct() {
1656 let out = convert("fn Str::trim { } Str::trim(uc($x))");
1657 assert!(out.contains("fn Str::trim"));
1658 assert!(out.contains("uc Str::trim $x"));
1660 }
1661
1662 #[test]
1663 fn user_func_extra_args_direct() {
1664 let out = convert("fn process { } process(uc($x), 42)");
1665 assert!(out.contains("fn process"));
1666 assert!(out.contains("uc process 42 $x"));
1668 }
1669
1670 #[test]
1671 fn map_grep_sort_chain_thread() {
1672 let out = convert("join(',', sort { $a <=> $b } map { $_ * 2 } grep { $_ > 0 } @nums)");
1673 assert!(out.contains("t @nums grep"));
1674 assert!(out.contains(" map"));
1675 assert!(out.contains(" sort"));
1676 assert!(out.contains(" join"));
1677 }
1678
1679 #[test]
1680 fn reduce_direct() {
1681 let out = convert("reduce { $a + $b } @nums");
1683 assert!(out.contains("reduce {\n$a + $b\n} @nums"));
1684 }
1685
1686 #[test]
1687 fn shebang_prepended() {
1688 let p = parse("print 1").expect("parse failed");
1689 let out = convert_program(&p);
1690 assert!(out.starts_with("#!/usr/bin/env stryke\n"));
1691 }
1692
1693 #[test]
1694 fn indentation_in_blocks() {
1695 let out = convert("if ($x) { print 1; print 2; }");
1696 assert!(out.contains("\n print 1\n print 2\n"));
1698 }
1699
1700 #[test]
1701 fn binop_no_parens_at_top() {
1702 let out = convert("my $x = $a + $b");
1703 assert!(out.contains("= $a + $b"));
1705 assert!(!out.contains("= ($a + $b)"));
1706 }
1707
1708 fn convert_with_delim(code: &str, delim: char) -> String {
1709 let p = parse(code).expect("parse failed");
1710 let opts = ConvertOptions {
1711 output_delim: Some(delim),
1712 ..Default::default()
1713 };
1714 let out = convert_program_with_options(&p, &opts);
1715 out.strip_prefix("#!/usr/bin/env stryke\n")
1716 .unwrap_or(&out)
1717 .to_string()
1718 }
1719
1720 #[test]
1721 fn output_delim_substitution() {
1722 let out = convert_with_delim("$x =~ s/foo/bar/g;", '|');
1723 assert_eq!(out, "$x =~ s|foo|bar|g");
1724 }
1725
1726 #[test]
1727 fn output_delim_transliterate() {
1728 let out = convert_with_delim("$y =~ tr/a-z/A-Z/;", '#');
1729 assert_eq!(out, "$y =~ tr#a-z#A-Z#");
1730 }
1731
1732 #[test]
1733 fn output_delim_match() {
1734 let out = convert_with_delim("$z =~ m/pattern/i;", '!');
1735 assert_eq!(out, "$z =~ !pattern!i");
1736 }
1737
1738 #[test]
1739 fn output_delim_preserves_original_when_none() {
1740 let out = convert("$x =~ s#old#new#g");
1741 assert_eq!(out, "$x =~ s#old#new#g");
1742 }
1743
1744 #[test]
1755 fn convert_begin_block() {
1756 let out = convert("BEGIN { my $x = 1; }");
1757 assert!(out.starts_with("BEGIN {"), "got {out:?}");
1758 assert!(out.contains("my $x = 1"));
1759 }
1760
1761 #[test]
1762 fn convert_end_block() {
1763 let out = convert("END { print 'bye'; }");
1764 assert!(out.starts_with("END {"), "got {out:?}");
1765 assert!(out.contains("bye"), "got {out:?}");
1768 }
1769
1770 #[test]
1771 fn convert_init_block() {
1772 let out = convert("INIT { my $x = 1; }");
1773 assert!(out.starts_with("INIT {"), "got {out:?}");
1774 }
1775
1776 #[test]
1777 fn convert_check_block() {
1778 let out = convert("CHECK { my $x = 1; }");
1779 assert!(out.starts_with("CHECK {"), "got {out:?}");
1780 }
1781
1782 #[test]
1783 fn convert_package_decl() {
1784 let out = convert("package Foo::Bar;");
1785 assert_eq!(out, "package Foo::Bar");
1786 }
1787
1788 #[test]
1789 fn convert_local_decl() {
1790 let out = convert("local $x;");
1791 assert_eq!(out, "local $x");
1792 }
1793
1794 #[test]
1795 fn convert_state_decl() {
1796 let out = convert("state $counter;");
1797 assert_eq!(out, "state $counter");
1798 }
1799
1800 #[test]
1801 fn convert_our_decl() {
1802 let out = convert("our $shared;");
1803 assert_eq!(out, "our $shared");
1804 }
1805
1806 #[test]
1807 fn convert_goto_label() {
1808 let out = convert("goto LABEL;");
1809 assert!(out.contains("goto"));
1810 assert!(out.contains("LABEL"));
1811 }
1812
1813 #[test]
1814 fn convert_last_with_label() {
1815 let out = convert("LOOP: while ($x) { last LOOP; }");
1816 assert!(out.contains("LOOP:"));
1817 assert!(out.contains("last LOOP"));
1818 }
1819
1820 #[test]
1821 fn convert_next_with_label() {
1822 let out = convert("LOOP: while ($x) { next LOOP; }");
1823 assert!(out.contains("next LOOP"));
1824 }
1825
1826 #[test]
1827 fn convert_redo_with_label() {
1828 let out = convert("LOOP: while ($x) { redo LOOP; }");
1829 assert!(out.contains("redo LOOP"));
1830 }
1831
1832 #[test]
1833 fn convert_bare_block_emits_braces() {
1834 let out = convert("{ my $x = 1; }");
1835 assert!(out.starts_with("{") && out.contains("my $x = 1"));
1837 }
1838
1839 #[test]
1840 fn convert_continue_block() {
1841 let out = convert("while ($x) { print 1; } continue { $x--; }");
1842 assert!(out.contains("continue {"), "got {out:?}");
1843 assert!(out.contains("$x--"));
1844 }
1845
1846 #[test]
1847 fn convert_do_while() {
1848 let out = convert("do { $x++; } while ($x < 5);");
1849 assert!(out.contains("do {"));
1850 assert!(out.contains("} while ($x < 5)"));
1851 }
1852
1853 #[test]
1854 fn convert_foreach_with_my() {
1855 let out = convert("foreach my $e (@arr) { print $e; }");
1856 assert!(out.contains("for $e"), "got {out:?}");
1860 assert!(out.contains("@arr"));
1861 assert!(out.contains("print $e"));
1862 }
1863
1864 #[test]
1865 fn convert_for_three_part() {
1866 let out = convert("for (my $i = 0; $i < 10; $i++) { print $i; }");
1867 assert!(out.contains("for ("));
1869 assert!(out.contains("$i = 0"));
1870 assert!(out.contains("$i < 10"));
1871 assert!(out.contains("$i++"));
1872 }
1873
1874 #[test]
1875 fn convert_use_module_imports() {
1876 let out = convert("use Foo::Bar qw(baz quux);");
1877 assert!(out.contains("use Foo::Bar"));
1878 }
1879
1880 #[test]
1881 fn convert_no_module() {
1882 let out = convert("no strict;");
1883 assert!(out.contains("no strict"));
1884 }
1885
1886 #[test]
1887 fn convert_return_expr() {
1888 let out = convert("fn my_doubler { return $_[0] * 2; }");
1891 assert!(out.contains("return"));
1892 assert!(out.contains("* 2"));
1893 }
1894
1895 #[test]
1896 fn convert_return_void() {
1897 let out = convert("fn my_done { return; }");
1898 assert!(out.contains("return"));
1900 }
1901
1902 #[test]
1903 fn convert_empty_statement_collapses() {
1904 let out = convert("print 1;;print 2;");
1907 assert!(out.contains("print 1"));
1909 assert!(out.contains("print 2"));
1910 assert!(
1911 !out.contains(";;"),
1912 "empty stmt leaked literal `;;`: {out:?}"
1913 );
1914 }
1915
1916 #[test]
1925 fn pipe_string_fns_chain() {
1926 let out = convert("uc(lc(ucfirst(lcfirst($s))))");
1928 assert!(out.contains("t $s lcfirst ucfirst lc uc"), "got {out:?}");
1929 }
1930
1931 #[test]
1932 fn pipe_chomp_chop_length() {
1933 let out = convert("length(chop(chomp($s)))");
1934 assert!(out.contains("t $s chomp chop length"), "got {out:?}");
1935 }
1936
1937 #[test]
1938 fn pipe_numeric_chain() {
1939 let out = convert("sqrt(abs(int($x)))");
1941 assert!(out.contains("t $x int abs sqrt"), "got {out:?}");
1942 }
1943
1944 #[test]
1945 fn pipe_mixed_str_num_chain() {
1946 let out = convert("length(uc($s))");
1950 assert_eq!(out, "uc length $s", "got {out:?}");
1951 }
1952
1953 #[test]
1954 fn pipe_terminates_at_array_source() {
1955 let out = convert("sort(@xs)");
1957 assert!(out.contains("@xs"));
1958 assert!(out.contains("sort"));
1959 }
1960
1961 #[test]
1962 fn pipe_single_unary_direct() {
1963 let out = convert("uc($x)");
1965 assert!(out.contains("uc"));
1967 assert!(out.contains("$x"));
1968 }
1969}