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::OurSync(decls) => format!("oursync {}", convert_var_decls(decls)),
403 StmtKind::StmtGroup(b) => convert_block(b, depth),
404 StmtKind::Block(b) => format!("{{\n{}\n{}}}", convert_block(b, depth + 1), pfx),
405 StmtKind::Begin(b) => format!("BEGIN {{\n{}\n{}}}", convert_block(b, depth + 1), pfx),
406 StmtKind::UnitCheck(b) => {
407 format!("UNITCHECK {{\n{}\n{}}}", convert_block(b, depth + 1), pfx)
408 }
409 StmtKind::Check(b) => format!("CHECK {{\n{}\n{}}}", convert_block(b, depth + 1), pfx),
410 StmtKind::Init(b) => format!("INIT {{\n{}\n{}}}", convert_block(b, depth + 1), pfx),
411 StmtKind::End(b) => format!("END {{\n{}\n{}}}", convert_block(b, depth + 1), pfx),
412 StmtKind::Empty => String::new(),
413 StmtKind::Goto { target } => format!("goto {}", convert_expr(target)),
414 StmtKind::Continue(b) => format!("continue {{\n{}\n{}}}", convert_block(b, depth + 1), pfx),
415 StmtKind::StructDecl { def } => {
416 let fields = def
417 .fields
418 .iter()
419 .map(|f| format!("{} => {}", f.name, f.ty.display_name()))
420 .collect::<Vec<_>>()
421 .join(", ");
422 format!("struct {} {{ {} }}", def.name, fields)
423 }
424 StmtKind::EnumDecl { def } => {
425 let variants = def
426 .variants
427 .iter()
428 .map(|v| {
429 if let Some(ty) = &v.ty {
430 format!("{} => {}", v.name, ty.display_name())
431 } else {
432 v.name.clone()
433 }
434 })
435 .collect::<Vec<_>>()
436 .join(", ");
437 format!("enum {} {{ {} }}", def.name, variants)
438 }
439 StmtKind::ClassDecl { def } => {
440 let prefix = if def.is_abstract {
441 "abstract "
442 } else if def.is_final {
443 "final "
444 } else {
445 ""
446 };
447 let mut parts = vec![format!("{}class {}", prefix, def.name)];
448 if !def.extends.is_empty() {
449 parts.push(format!("extends {}", def.extends.join(", ")));
450 }
451 if !def.implements.is_empty() {
452 parts.push(format!("impl {}", def.implements.join(", ")));
453 }
454 let fields = def
455 .fields
456 .iter()
457 .map(|f| {
458 let vis = match f.visibility {
459 crate::ast::Visibility::Private => "priv ",
460 crate::ast::Visibility::Protected => "prot ",
461 crate::ast::Visibility::Public => "",
462 };
463 format!("{}{}: {}", vis, f.name, f.ty.display_name())
464 })
465 .collect::<Vec<_>>()
466 .join("; ");
467 format!("{} {{ {} }}", parts.join(" "), fields)
468 }
469 StmtKind::TraitDecl { def } => {
470 let methods = def
471 .methods
472 .iter()
473 .map(|m| format!("fn {}", m.name))
474 .collect::<Vec<_>>()
475 .join("; ");
476 format!("trait {} {{ {} }}", def.name, methods)
477 }
478 StmtKind::EvalTimeout { timeout, body } => {
479 format!(
480 "eval_timeout {} {{\n{}\n{}}}",
481 convert_expr(timeout),
482 convert_block(body, depth + 1),
483 pfx
484 )
485 }
486 StmtKind::TryCatch {
487 try_block,
488 catch_var,
489 catch_block,
490 finally_block,
491 } => {
492 let fin = finally_block
493 .as_ref()
494 .map(|b| {
495 format!(
496 "\n{}finally {{\n{}\n{}}}",
497 pfx,
498 convert_block(b, depth + 1),
499 pfx
500 )
501 })
502 .unwrap_or_default();
503 format!(
504 "try {{\n{}\n{}}} catch (${}) {{\n{}\n{}}}{}",
505 convert_block(try_block, depth + 1),
506 pfx,
507 catch_var,
508 convert_block(catch_block, depth + 1),
509 pfx,
510 fin
511 )
512 }
513 StmtKind::Given { topic, body } => {
514 format!(
515 "given ({}) {{\n{}\n{}}}",
516 convert_expr(topic),
517 convert_block(body, depth + 1),
518 pfx
519 )
520 }
521 StmtKind::When { cond, body } => {
522 format!(
523 "when ({}) {{\n{}\n{}}}",
524 convert_expr(cond),
525 convert_block(body, depth + 1),
526 pfx
527 )
528 }
529 StmtKind::DefaultCase { body } => {
530 format!("default {{\n{}\n{}}}", convert_block(body, depth + 1), pfx)
531 }
532 StmtKind::FormatDecl { name, lines } => {
533 let mut s = format!("format {} =\n", name);
534 for ln in lines {
535 s.push_str(ln);
536 s.push('\n');
537 }
538 s.push('.');
539 s
540 }
541 StmtKind::AdviceDecl {
542 kind,
543 pattern,
544 body,
545 } => {
546 let kw = match kind {
547 crate::ast::AdviceKind::Before => "before",
548 crate::ast::AdviceKind::After => "after",
549 crate::ast::AdviceKind::Around => "around",
550 };
551 format!(
552 "{} \"{}\" {{\n{}\n{}}}",
553 kw,
554 pattern,
555 convert_block(body, depth + 1),
556 pfx
557 )
558 }
559 StmtKind::Tie {
560 target,
561 class,
562 args,
563 } => {
564 let target_s = match target {
565 crate::ast::TieTarget::Hash(h) => format!("%{}", h),
566 crate::ast::TieTarget::Array(a) => format!("@{}", a),
567 crate::ast::TieTarget::Scalar(s) => format!("${}", s),
568 };
569 let mut s = format!("tie {} {}", target_s, convert_expr(class));
570 for a in args {
571 s.push_str(&format!(", {}", convert_expr(a)));
572 }
573 s
574 }
575 };
576 format!("{}{}{}", pfx, lab, body)
577}
578
579fn convert_statement_body(s: &Statement) -> String {
581 let lab = s
582 .label
583 .as_ref()
584 .map(|l| format!("{}: ", l))
585 .unwrap_or_default();
586 let body = match &s.kind {
587 StmtKind::Expression(e) => convert_expr_top(e),
588 StmtKind::My(decls) => format!("my {}", convert_var_decls(decls)),
589 _ => convert_statement(s, 0).trim().to_string(),
590 };
591 format!("{}{}", lab, body)
592}
593
594fn convert_var_decls(decls: &[VarDecl]) -> String {
597 decls
598 .iter()
599 .map(|d| {
600 let sig = match d.sigil {
601 Sigil::Scalar => "$",
602 Sigil::Array => "@",
603 Sigil::Hash => "%",
604 Sigil::Typeglob => "*",
605 };
606 let mut s = format!("{}{}", sig, d.name);
607 if let Some(ref t) = d.type_annotation {
608 s.push_str(&format!(" : {}", t.display_name()));
609 }
610 if let Some(ref init) = d.initializer {
611 s.push_str(&format!(" = {}", convert_expr_top(init)));
612 }
613 s
614 })
615 .collect::<Vec<_>>()
616 .join(", ")
617}
618
619fn convert_expr_list(es: &[Expr]) -> String {
622 es.iter().map(convert_expr).collect::<Vec<_>>().join(", ")
623}
624
625fn convert_string_part(p: &StringPart) -> String {
628 match p {
629 StringPart::Literal(s) => fmt::escape_interpolated_literal(s),
630 StringPart::ScalarVar(n) => {
631 if needs_braces(n) {
633 format!("${{{}}}", n)
634 } else {
635 format!("${}", n)
636 }
637 }
638 StringPart::ArrayVar(n) => {
639 if needs_braces(n) {
640 format!("@{{{}}}", n)
641 } else {
642 format!("@{}", n)
643 }
644 }
645 StringPart::Expr(e) => fmt::format_expr(e),
646 }
647}
648
649fn needs_braces(name: &str) -> bool {
651 if name.is_empty() || name.chars().next().is_some_and(|c| c.is_ascii_digit()) {
653 return true;
654 }
655 !name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
657}
658
659fn convert_expr_top(e: &Expr) -> String {
662 convert_expr_impl(e, true)
663}
664
665fn convert_expr(e: &Expr) -> String {
668 convert_expr_impl(e, false)
669}
670
671fn convert_expr_impl(e: &Expr, top: bool) -> String {
672 let mut segments: Vec<String> = Vec::new();
673 let source = extract_pipe_source(e, &mut segments);
674 if !segments.is_empty() {
675 segments.reverse();
676 if segments.len() <= 2 {
679 let result = format!("{} {}", segments.join(" "), source);
680 if !top {
681 return format!("({})", result);
682 }
683 return result;
684 }
685 let stages = segments.join(" ");
687 let source = if source.starts_with("(t ") || source.starts_with("((") {
689 source[1..source.len() - 1].to_string()
690 } else {
691 source
692 };
693 let result = format!("t {} {}", source, stages);
694 if !top {
695 return format!("({})", result);
696 }
697 return result;
698 }
699 convert_expr_direct(e, top)
701}
702
703fn extract_pipe_source(e: &Expr, segments: &mut Vec<String>) -> String {
710 match &e.kind {
711 ExprKind::Uc(inner) => {
713 segments.push("uc".into());
714 extract_pipe_source(inner, segments)
715 }
716 ExprKind::Lc(inner) => {
717 segments.push("lc".into());
718 extract_pipe_source(inner, segments)
719 }
720 ExprKind::Ucfirst(inner) => {
721 segments.push("ucfirst".into());
722 extract_pipe_source(inner, segments)
723 }
724 ExprKind::Lcfirst(inner) => {
725 segments.push("lcfirst".into());
726 extract_pipe_source(inner, segments)
727 }
728 ExprKind::Fc(inner) => {
729 segments.push("fc".into());
730 extract_pipe_source(inner, segments)
731 }
732 ExprKind::Chomp(inner) => {
733 segments.push("chomp".into());
734 extract_pipe_source(inner, segments)
735 }
736 ExprKind::Chop(inner) => {
737 segments.push("chop".into());
738 extract_pipe_source(inner, segments)
739 }
740 ExprKind::Length(inner) => {
741 segments.push("length".into());
742 extract_pipe_source(inner, segments)
743 }
744 ExprKind::Abs(inner) => {
745 segments.push("abs".into());
746 extract_pipe_source(inner, segments)
747 }
748 ExprKind::Int(inner) => {
749 segments.push("int".into());
750 extract_pipe_source(inner, segments)
751 }
752 ExprKind::Sqrt(inner) => {
753 segments.push("sqrt".into());
754 extract_pipe_source(inner, segments)
755 }
756 ExprKind::Sin(inner) => {
757 segments.push("sin".into());
758 extract_pipe_source(inner, segments)
759 }
760 ExprKind::Cos(inner) => {
761 segments.push("cos".into());
762 extract_pipe_source(inner, segments)
763 }
764 ExprKind::Exp(inner) => {
765 segments.push("exp".into());
766 extract_pipe_source(inner, segments)
767 }
768 ExprKind::Log(inner) => {
769 segments.push("log".into());
770 extract_pipe_source(inner, segments)
771 }
772 ExprKind::Hex(inner) => {
773 segments.push("hex".into());
774 extract_pipe_source(inner, segments)
775 }
776 ExprKind::Oct(inner) => {
777 segments.push("oct".into());
778 extract_pipe_source(inner, segments)
779 }
780 ExprKind::Chr(inner) => {
781 segments.push("chr".into());
782 extract_pipe_source(inner, segments)
783 }
784 ExprKind::Ord(inner) => {
785 segments.push("ord".into());
786 extract_pipe_source(inner, segments)
787 }
788 ExprKind::Defined(inner) => {
789 segments.push("defined".into());
790 extract_pipe_source(inner, segments)
791 }
792 ExprKind::Ref(inner) => {
793 segments.push("ref".into());
794 extract_pipe_source(inner, segments)
795 }
796 ExprKind::ScalarContext(inner) => {
797 segments.push("scalar".into());
798 extract_pipe_source(inner, segments)
799 }
800 ExprKind::Keys(inner) => {
801 segments.push("keys".into());
802 extract_pipe_source(inner, segments)
803 }
804 ExprKind::Values(inner) => {
805 segments.push("values".into());
806 extract_pipe_source(inner, segments)
807 }
808 ExprKind::Each(inner) => {
809 segments.push("each".into());
810 extract_pipe_source(inner, segments)
811 }
812 ExprKind::Pop(inner) => {
813 segments.push("pop".into());
814 extract_pipe_source(inner, segments)
815 }
816 ExprKind::Shift(inner) => {
817 segments.push("shift".into());
818 extract_pipe_source(inner, segments)
819 }
820 ExprKind::ReverseExpr(inner) => {
821 segments.push("reverse".into());
822 extract_pipe_source(inner, segments)
823 }
824 ExprKind::Slurp(inner) => {
825 segments.push("slurp".into());
826 extract_pipe_source(inner, segments)
827 }
828 ExprKind::Chdir(inner) => {
829 segments.push("chdir".into());
830 extract_pipe_source(inner, segments)
831 }
832 ExprKind::Stat(inner) => {
833 segments.push("stat".into());
834 extract_pipe_source(inner, segments)
835 }
836 ExprKind::Lstat(inner) => {
837 segments.push("lstat".into());
838 extract_pipe_source(inner, segments)
839 }
840 ExprKind::Readlink(inner) => {
841 segments.push("readlink".into());
842 extract_pipe_source(inner, segments)
843 }
844 ExprKind::Study(inner) => {
845 segments.push("study".into());
846 extract_pipe_source(inner, segments)
847 }
848 ExprKind::Close(inner) => {
849 segments.push("close".into());
850 extract_pipe_source(inner, segments)
851 }
852 ExprKind::Readdir(inner) => {
853 segments.push("readdir".into());
854 extract_pipe_source(inner, segments)
855 }
856 ExprKind::Eval(inner) => {
857 segments.push("eval".into());
858 extract_pipe_source(inner, segments)
859 }
860 ExprKind::Require(inner) => {
861 segments.push("require".into());
862 extract_pipe_source(inner, segments)
863 }
864
865 ExprKind::MapExpr {
867 block,
868 list,
869 flatten_array_refs,
870 stream,
871 } => {
872 let kw = match (*flatten_array_refs, *stream) {
873 (true, true) => "flat_maps",
874 (true, false) => "flat_map",
875 (false, true) => "maps",
876 (false, false) => "map",
877 };
878 segments.push(format!("{} {{\n{}\n}}", kw, convert_block(block, 0)));
879 extract_pipe_source(list, segments)
880 }
881 ExprKind::MapExprComma {
882 expr,
883 list,
884 flatten_array_refs,
885 stream,
886 } => {
887 let kw = match (*flatten_array_refs, *stream) {
888 (true, true) => "flat_maps",
889 (true, false) => "flat_map",
890 (false, true) => "maps",
891 (false, false) => "map",
892 };
893 segments.push(format!("{} {{ {} }}", kw, convert_expr_top(expr)));
895 extract_pipe_source(list, segments)
896 }
897 ExprKind::GrepExpr {
898 block,
899 list,
900 keyword,
901 } => {
902 segments.push(format!(
903 "{} {{\n{}\n}}",
904 keyword.as_str(),
905 convert_block(block, 0)
906 ));
907 extract_pipe_source(list, segments)
908 }
909 ExprKind::GrepExprComma {
910 expr,
911 list,
912 keyword,
913 } => {
914 segments.push(format!(
915 "{} {{ {} }}",
916 keyword.as_str(),
917 convert_expr_top(expr)
918 ));
919 extract_pipe_source(list, segments)
920 }
921 ExprKind::SortExpr { cmp, list } => {
922 let seg = match cmp {
923 Some(SortComparator::Block(b)) => {
924 format!("sort {{\n{}\n}}", convert_block(b, 0))
925 }
926 Some(SortComparator::Code(e)) => {
927 format!("sort {}", convert_expr(e))
928 }
929 None => "sort".to_string(),
930 };
931 segments.push(seg);
932 extract_pipe_source(list, segments)
933 }
934 ExprKind::JoinExpr { separator, list } => {
935 segments.push(format!("join {}", convert_expr(separator)));
936 extract_pipe_source(list, segments)
937 }
938 ExprKind::ReduceExpr { block, list } => {
939 segments.push(format!("reduce {{\n{}\n}}", convert_block(block, 0)));
940 extract_pipe_source(list, segments)
941 }
942 ExprKind::ForEachExpr { block, list } => {
943 segments.push(format!("fore {{\n{}\n}}", convert_block(block, 0)));
944 extract_pipe_source(list, segments)
945 }
946
947 ExprKind::PMapExpr {
949 block,
950 list,
951 progress,
952 flat_outputs,
953 on_cluster,
954 stream: _,
955 } if progress.is_none() && on_cluster.is_none() => {
956 let kw = if *flat_outputs { "pflat_map" } else { "pmap" };
957 segments.push(format!("{} {{\n{}\n}}", kw, convert_block(block, 0)));
958 extract_pipe_source(list, segments)
959 }
960 ExprKind::PGrepExpr {
961 block,
962 list,
963 progress,
964 stream: _,
965 } if progress.is_none() => {
966 segments.push(format!("pgrep {{\n{}\n}}", convert_block(block, 0)));
967 extract_pipe_source(list, segments)
968 }
969 ExprKind::PSortExpr {
970 cmp,
971 list,
972 progress,
973 } if progress.is_none() => {
974 let seg = match cmp {
975 Some(b) => format!("psort {{\n{}\n}}", convert_block(b, 0)),
976 None => "psort".to_string(),
977 };
978 segments.push(seg);
979 extract_pipe_source(list, segments)
980 }
981
982 ExprKind::Say { handle: None, args } if args.len() == 1 => {
985 segments.push("p".into());
986 extract_pipe_source(&args[0], segments)
987 }
988 ExprKind::Print { handle: None, args } if args.len() == 1 => {
989 segments.push("print".into());
990 extract_pipe_source(&args[0], segments)
991 }
992
993 ExprKind::FuncCall { name, args } if !args.is_empty() => {
995 let seg = if args.len() == 1 {
996 name.clone()
997 } else {
998 let rest = args[1..]
999 .iter()
1000 .map(convert_expr)
1001 .collect::<Vec<_>>()
1002 .join(", ");
1003 format!("{} {}", name, rest)
1004 };
1005 segments.push(seg);
1006 extract_pipe_source(&args[0], segments)
1007 }
1008
1009 ExprKind::Substitution {
1011 expr,
1012 pattern,
1013 replacement,
1014 flags,
1015 delim,
1016 } if flags.contains('r') => {
1017 let d = choose_delim(*delim);
1021 segments.push(format!(
1022 "s{}{}{}{}{}{}",
1023 d,
1024 fmt::escape_regex_part(pattern),
1025 d,
1026 fmt::escape_regex_part(replacement),
1027 d,
1028 flags
1029 ));
1030 extract_pipe_source(expr, segments)
1031 }
1032
1033 ExprKind::Transliterate {
1035 expr,
1036 from,
1037 to,
1038 flags,
1039 delim,
1040 } if flags.contains('r') => {
1041 let d = choose_delim(*delim);
1042 segments.push(format!(
1043 "tr{}{}{}{}{}{}",
1044 d,
1045 fmt::escape_regex_part(from),
1046 d,
1047 fmt::escape_regex_part(to),
1048 d,
1049 flags
1050 ));
1051 extract_pipe_source(expr, segments)
1052 }
1053
1054 ExprKind::List(elems) if elems.len() == 1 => extract_pipe_source(&elems[0], segments),
1056
1057 _ => convert_expr_direct(e, false),
1059 }
1060}
1061
1062fn convert_expr_direct(e: &Expr, top: bool) -> String {
1068 match &e.kind {
1069 ExprKind::Integer(_)
1071 | ExprKind::Float(_)
1072 | ExprKind::String(_)
1073 | ExprKind::Bareword(_)
1074 | ExprKind::Regex(..)
1075 | ExprKind::QW(_)
1076 | ExprKind::Undef
1077 | ExprKind::MagicConst(_)
1078 | ExprKind::ScalarVar(_)
1079 | ExprKind::ArrayVar(_)
1080 | ExprKind::HashVar(_)
1081 | ExprKind::Typeglob(_)
1082 | ExprKind::Wantarray
1083 | ExprKind::SubroutineRef(_)
1084 | ExprKind::SubroutineCodeRef(_) => fmt::format_expr(e),
1085
1086 ExprKind::InterpolatedString(parts) => {
1088 format!(
1089 "\"{}\"",
1090 parts.iter().map(convert_string_part).collect::<String>()
1091 )
1092 }
1093
1094 ExprKind::BinOp { left, op, right } => {
1096 format!(
1097 "{} {} {}",
1098 convert_expr(left),
1099 fmt::format_binop(*op),
1100 convert_expr(right)
1101 )
1102 }
1103
1104 ExprKind::UnaryOp { op, expr } => {
1106 format!("{}{}", fmt::format_unary(*op), convert_expr(expr))
1107 }
1108 ExprKind::PostfixOp { expr, op } => {
1109 format!("{}{}", convert_expr(expr), fmt::format_postfix(*op))
1110 }
1111
1112 ExprKind::Assign { target, value } => {
1114 format!("{} = {}", convert_expr(target), convert_expr_top(value))
1115 }
1116 ExprKind::CompoundAssign { target, op, value } => format!(
1117 "{} {}= {}",
1118 convert_expr(target),
1119 fmt::format_binop(*op),
1120 convert_expr_top(value)
1121 ),
1122
1123 ExprKind::Ternary {
1125 condition,
1126 then_expr,
1127 else_expr,
1128 } => format!(
1129 "{} ? {} : {}",
1130 convert_expr(condition),
1131 convert_expr(then_expr),
1132 convert_expr(else_expr)
1133 ),
1134
1135 ExprKind::SliceRange { from, to, step } => {
1137 let f = from.as_ref().map(|e| convert_expr(e)).unwrap_or_default();
1138 let t = to.as_ref().map(|e| convert_expr(e)).unwrap_or_default();
1139 match step {
1140 Some(s) => format!("{}:{}:{}", f, t, convert_expr(s)),
1141 None => format!("{}:{}", f, t),
1142 }
1143 }
1144 ExprKind::Range {
1145 from,
1146 to,
1147 exclusive,
1148 step,
1149 } => {
1150 let op = if *exclusive { "..." } else { ".." };
1151 if let Some(s) = step {
1152 format!(
1153 "{} {} {}:{}",
1154 convert_expr(from),
1155 op,
1156 convert_expr(to),
1157 convert_expr(s)
1158 )
1159 } else {
1160 format!("{} {} {}", convert_expr(from), op, convert_expr(to))
1161 }
1162 }
1163 ExprKind::Repeat {
1164 expr,
1165 count,
1166 list_repeat,
1167 } => {
1168 if *list_repeat && !matches!(expr.kind, ExprKind::List(_) | ExprKind::QW(_)) {
1171 format!("({}) x {}", convert_expr(expr), convert_expr(count))
1172 } else {
1173 format!("{} x {}", convert_expr(expr), convert_expr(count))
1174 }
1175 }
1176
1177 ExprKind::FuncCall { name, args } => format!("{}({})", name, convert_expr_list(args)),
1179 ExprKind::MethodCall {
1180 object,
1181 method,
1182 args,
1183 super_call,
1184 } => {
1185 let m = if *super_call {
1186 format!("SUPER::{}", method)
1187 } else {
1188 method.clone()
1189 };
1190 format!(
1191 "{}->{}({})",
1192 convert_expr(object),
1193 m,
1194 convert_expr_list(args)
1195 )
1196 }
1197 ExprKind::IndirectCall {
1198 target,
1199 args,
1200 ampersand,
1201 pass_caller_arglist,
1202 } => {
1203 if *pass_caller_arglist && args.is_empty() {
1204 format!("&{}", convert_expr(target))
1205 } else {
1206 let inner = format!("{}({})", convert_expr(target), convert_expr_list(args));
1207 if *ampersand {
1208 format!("&{}", inner)
1209 } else {
1210 inner
1211 }
1212 }
1213 }
1214
1215 ExprKind::List(exprs) => format!("({})", convert_expr_list(exprs)),
1217 ExprKind::ArrayRef(elems) => format!("[{}]", convert_expr_list(elems)),
1218 ExprKind::HashRef(pairs) => {
1219 let inner = pairs
1220 .iter()
1221 .map(|(k, v)| format!("{} => {}", convert_expr(k), convert_expr(v)))
1222 .collect::<Vec<_>>()
1223 .join(", ");
1224 format!("{{{}}}", inner)
1225 }
1226 ExprKind::CodeRef { params, body } => {
1227 if params.is_empty() {
1228 format!("fn {{\n{}\n}}", convert_block(body, 0))
1229 } else {
1230 let sig = params
1231 .iter()
1232 .map(fmt::format_sub_sig_param)
1233 .collect::<Vec<_>>()
1234 .join(", ");
1235 format!("fn ({}) {{\n{}\n}}", sig, convert_block(body, 0))
1236 }
1237 }
1238
1239 ExprKind::ArrayElement { array, index } => {
1241 format!("${}[{}]", array, convert_expr(index))
1242 }
1243 ExprKind::HashElement { hash, key } => {
1244 format!("${}{{{}}}", hash, convert_expr(key))
1245 }
1246 ExprKind::ScalarRef(inner) => format!("\\{}", convert_expr(inner)),
1247 ExprKind::ArrowDeref { expr, index, kind } => match kind {
1248 DerefKind::Array => {
1249 format!("({})->[{}]", convert_expr(expr), convert_expr(index))
1250 }
1251 DerefKind::Hash => {
1252 format!("({})->{{{}}}", convert_expr(expr), convert_expr(index))
1253 }
1254 DerefKind::Call => {
1255 format!("({})->({})", convert_expr(expr), convert_expr(index))
1256 }
1257 },
1258 ExprKind::Deref { expr, kind } => match kind {
1259 Sigil::Scalar => format!("${{{}}}", convert_expr(expr)),
1260 Sigil::Array => format!("@{{${}}}", convert_expr(expr)),
1261 Sigil::Hash => format!("%{{${}}}", convert_expr(expr)),
1262 Sigil::Typeglob => format!("*{{${}}}", convert_expr(expr)),
1263 },
1264
1265 ExprKind::Print { handle, args } => {
1268 let h = handle
1269 .as_ref()
1270 .map(|h| format!("{} ", h))
1271 .unwrap_or_default();
1272 format!("print {}{}", h, convert_expr_list(args))
1273 }
1274 ExprKind::Say { handle, args } => {
1275 if let Some(h) = handle {
1276 format!("say {} {}", h, convert_expr_list(args))
1277 } else {
1278 format!("p {}", convert_expr_list(args))
1279 }
1280 }
1281 ExprKind::Printf { handle, args } => {
1282 let h = handle
1283 .as_ref()
1284 .map(|h| format!("{} ", h))
1285 .unwrap_or_default();
1286 format!("printf {}{}", h, convert_expr_list(args))
1287 }
1288 ExprKind::Die(args) => {
1289 if args.is_empty() {
1290 "die".to_string()
1291 } else {
1292 format!("die {}", convert_expr_list(args))
1293 }
1294 }
1295 ExprKind::Warn(args) => {
1296 if args.is_empty() {
1297 "warn".to_string()
1298 } else {
1299 format!("warn {}", convert_expr_list(args))
1300 }
1301 }
1302
1303 ExprKind::Match {
1305 expr,
1306 pattern,
1307 flags,
1308 delim,
1309 ..
1310 } => {
1311 let d = choose_delim(*delim);
1312 format!(
1313 "{} =~ {}{}{}{}",
1314 convert_expr(expr),
1315 d,
1316 fmt::escape_regex_part(pattern),
1317 d,
1318 flags
1319 )
1320 }
1321 ExprKind::Substitution {
1322 expr,
1323 pattern,
1324 replacement,
1325 flags,
1326 delim,
1327 } => {
1328 let d = choose_delim(*delim);
1329 format!(
1330 "{} =~ s{}{}{}{}{}{}",
1331 convert_expr(expr),
1332 d,
1333 fmt::escape_regex_part(pattern),
1334 d,
1335 fmt::escape_regex_part(replacement),
1336 d,
1337 flags
1338 )
1339 }
1340 ExprKind::Transliterate {
1341 expr,
1342 from,
1343 to,
1344 flags,
1345 delim,
1346 } => {
1347 let d = choose_delim(*delim);
1348 format!(
1349 "{} =~ tr{}{}{}{}{}{}",
1350 convert_expr(expr),
1351 d,
1352 fmt::escape_regex_part(from),
1353 d,
1354 fmt::escape_regex_part(to),
1355 d,
1356 flags
1357 )
1358 }
1359
1360 ExprKind::PostfixIf { expr, condition } => {
1362 format!("{} if {}", convert_expr_top(expr), convert_expr(condition))
1363 }
1364 ExprKind::PostfixUnless { expr, condition } => {
1365 format!(
1366 "{} unless {}",
1367 convert_expr_top(expr),
1368 convert_expr(condition)
1369 )
1370 }
1371 ExprKind::PostfixWhile { expr, condition } => {
1372 format!(
1373 "{} while {}",
1374 convert_expr_top(expr),
1375 convert_expr(condition)
1376 )
1377 }
1378 ExprKind::PostfixUntil { expr, condition } => {
1379 format!(
1380 "{} until {}",
1381 convert_expr_top(expr),
1382 convert_expr(condition)
1383 )
1384 }
1385 ExprKind::PostfixForeach { expr, list } => {
1386 format!("{} for {}", convert_expr_top(expr), convert_expr(list))
1387 }
1388
1389 ExprKind::MapExpr {
1391 block,
1392 list,
1393 flatten_array_refs,
1394 stream,
1395 } => {
1396 let kw = match (*flatten_array_refs, *stream) {
1397 (true, true) => "flat_maps",
1398 (true, false) => "flat_map",
1399 (false, true) => "maps",
1400 (false, false) => "map",
1401 };
1402 format!(
1403 "{} {{\n{}\n}} {}",
1404 kw,
1405 convert_block(block, 0),
1406 convert_expr(list)
1407 )
1408 }
1409 ExprKind::GrepExpr {
1410 block,
1411 list,
1412 keyword,
1413 } => {
1414 format!(
1415 "{} {{\n{}\n}} {}",
1416 keyword.as_str(),
1417 convert_block(block, 0),
1418 convert_expr(list)
1419 )
1420 }
1421 ExprKind::SortExpr { cmp, list } => match cmp {
1422 Some(SortComparator::Block(b)) => {
1423 format!(
1424 "sort {{\n{}\n}} {}",
1425 convert_block(b, 0),
1426 convert_expr(list)
1427 )
1428 }
1429 Some(SortComparator::Code(e)) => {
1430 format!("sort {} {}", convert_expr(e), convert_expr(list))
1431 }
1432 None => format!("sort {}", convert_expr(list)),
1433 },
1434 ExprKind::JoinExpr { separator, list } => {
1435 format!("join({}, {})", convert_expr(separator), convert_expr(list))
1436 }
1437 ExprKind::SplitExpr {
1438 pattern,
1439 string,
1440 limit,
1441 } => match limit {
1442 Some(l) => format!(
1443 "split({}, {}, {})",
1444 convert_expr(pattern),
1445 convert_expr(string),
1446 convert_expr(l)
1447 ),
1448 None => format!("split({}, {})", convert_expr(pattern), convert_expr(string)),
1449 },
1450
1451 ExprKind::Bless { ref_expr, class } => match class {
1453 Some(c) => format!("bless({}, {})", convert_expr(ref_expr), convert_expr(c)),
1454 None => format!("bless({})", convert_expr(ref_expr)),
1455 },
1456
1457 ExprKind::Push { array, values } => {
1459 format!(
1460 "push({}, {})",
1461 convert_expr(array),
1462 convert_expr_list(values)
1463 )
1464 }
1465 ExprKind::Unshift { array, values } => {
1466 format!(
1467 "unshift({}, {})",
1468 convert_expr(array),
1469 convert_expr_list(values)
1470 )
1471 }
1472
1473 ExprKind::AlgebraicMatch { subject, arms } => {
1475 let arms_s = arms
1476 .iter()
1477 .map(|a| {
1478 let guard_s = a
1479 .guard
1480 .as_ref()
1481 .map(|g| format!(" if {}", convert_expr(g)))
1482 .unwrap_or_default();
1483 format!(
1484 "{}{} => {}",
1485 fmt::format_match_pattern(&a.pattern),
1486 guard_s,
1487 convert_expr(&a.body)
1488 )
1489 })
1490 .collect::<Vec<_>>()
1491 .join(", ");
1492 format!("match ({}) {{ {} }}", convert_expr(subject), arms_s)
1493 }
1494
1495 _ => fmt::format_expr(e),
1497 }
1498}
1499
1500#[cfg(test)]
1503mod tests {
1504 use super::*;
1505 use crate::parse;
1506
1507 fn convert(code: &str) -> String {
1509 let p = parse(code).expect("parse failed");
1510 let out = convert_program(&p);
1511 out.strip_prefix("#!/usr/bin/env stryke\n")
1513 .unwrap_or(&out)
1514 .to_string()
1515 }
1516
1517 #[test]
1518 fn unary_builtin_direct() {
1519 assert_eq!(convert("uc($x)"), "uc $x");
1521 assert_eq!(convert("length($str)"), "length $str");
1522 }
1523
1524 #[test]
1525 fn nested_unary_direct() {
1526 let out = convert("uc(lc($x))");
1528 assert_eq!(out, "lc uc $x");
1529 }
1530
1531 #[test]
1532 fn nested_builtin_chain_thread() {
1533 let out = convert("chomp(lc(uc($x)))");
1534 assert_eq!(out, "t $x uc lc chomp");
1535 }
1536
1537 #[test]
1538 fn deeply_nested_thread() {
1539 let out = convert("length(chomp(lc(uc($x))))");
1540 assert_eq!(out, "t $x uc lc chomp length");
1541 }
1542
1543 #[test]
1544 fn map_grep_sort_thread() {
1545 let out = convert("sort { $a <=> $b } map { $_ * 2 } grep { $_ > 0 } @numbers");
1546 assert!(out.contains("t @numbers grep"));
1547 assert!(out.contains(" map"));
1548 assert!(out.contains(" sort"));
1549 }
1550
1551 #[test]
1552 fn join_direct() {
1553 let out = convert(r#"join(",", sort(@arr))"#);
1554 assert!(out.contains("sort join \",\" @arr"));
1556 }
1557
1558 #[test]
1559 fn no_semicolons() {
1560 let out = convert("my $x = 1;\nmy $y = 2");
1561 assert!(!out.contains(';'));
1562 assert!(out.contains("my $x = 1"));
1563 assert!(out.contains("my $y = 2"));
1564 }
1565
1566 #[test]
1567 fn assignment_rhs_direct() {
1568 let out = convert("my $x = uc(lc($str))");
1569 assert_eq!(out, "my $x = lc uc $str");
1571 }
1572
1573 #[test]
1574 fn chain_in_subexpression_parenthesized() {
1575 let out = convert("$x + uc(lc($str))");
1576 assert!(out.contains("(lc uc $str)"));
1578 }
1579
1580 #[test]
1581 fn fn_body_indented() {
1582 let out = convert("fn foo { return uc(lc($x)); }");
1583 assert!(out.contains("fn foo"));
1584 assert!(out.contains("lc uc $x"));
1586 assert!(out.contains(" return"));
1588 }
1589
1590 #[test]
1591 fn if_condition_converted() {
1592 let out = convert("if (defined(length($x))) { 1; }");
1593 assert!(out.contains("length defined $x"));
1595 }
1596
1597 #[test]
1598 fn method_call_preserved() {
1599 let out = convert("$obj->method($x)");
1600 assert!(out.contains("->method"));
1601 }
1602
1603 #[test]
1604 fn substitution_r_flag_direct() {
1605 let out = convert(r#"($str =~ s/old/new/r)"#);
1607 assert!(out.contains("s/old/new/r $str"));
1608 }
1609
1610 #[test]
1611 fn user_func_call_direct() {
1612 let out = convert("fn Str::trim { } Str::trim(uc($x))");
1613 assert!(out.contains("fn Str::trim"));
1614 assert!(out.contains("uc Str::trim $x"));
1616 }
1617
1618 #[test]
1619 fn user_func_extra_args_direct() {
1620 let out = convert("fn process { } process(uc($x), 42)");
1621 assert!(out.contains("fn process"));
1622 assert!(out.contains("uc process 42 $x"));
1624 }
1625
1626 #[test]
1627 fn map_grep_sort_chain_thread() {
1628 let out = convert("join(',', sort { $a <=> $b } map { $_ * 2 } grep { $_ > 0 } @nums)");
1629 assert!(out.contains("t @nums grep"));
1630 assert!(out.contains(" map"));
1631 assert!(out.contains(" sort"));
1632 assert!(out.contains(" join"));
1633 }
1634
1635 #[test]
1636 fn reduce_direct() {
1637 let out = convert("reduce { $a + $b } @nums");
1639 assert!(out.contains("reduce {\n$a + $b\n} @nums"));
1640 }
1641
1642 #[test]
1643 fn shebang_prepended() {
1644 let p = parse("print 1").expect("parse failed");
1645 let out = convert_program(&p);
1646 assert!(out.starts_with("#!/usr/bin/env stryke\n"));
1647 }
1648
1649 #[test]
1650 fn indentation_in_blocks() {
1651 let out = convert("if ($x) { print 1; print 2; }");
1652 assert!(out.contains("\n print 1\n print 2\n"));
1654 }
1655
1656 #[test]
1657 fn binop_no_parens_at_top() {
1658 let out = convert("my $x = $a + $b");
1659 assert!(out.contains("= $a + $b"));
1661 assert!(!out.contains("= ($a + $b)"));
1662 }
1663
1664 fn convert_with_delim(code: &str, delim: char) -> String {
1665 let p = parse(code).expect("parse failed");
1666 let opts = ConvertOptions {
1667 output_delim: Some(delim),
1668 };
1669 let out = convert_program_with_options(&p, &opts);
1670 out.strip_prefix("#!/usr/bin/env stryke\n")
1671 .unwrap_or(&out)
1672 .to_string()
1673 }
1674
1675 #[test]
1676 fn output_delim_substitution() {
1677 let out = convert_with_delim("$x =~ s/foo/bar/g;", '|');
1678 assert_eq!(out, "$x =~ s|foo|bar|g");
1679 }
1680
1681 #[test]
1682 fn output_delim_transliterate() {
1683 let out = convert_with_delim("$y =~ tr/a-z/A-Z/;", '#');
1684 assert_eq!(out, "$y =~ tr#a-z#A-Z#");
1685 }
1686
1687 #[test]
1688 fn output_delim_match() {
1689 let out = convert_with_delim("$z =~ m/pattern/i;", '!');
1690 assert_eq!(out, "$z =~ !pattern!i");
1691 }
1692
1693 #[test]
1694 fn output_delim_preserves_original_when_none() {
1695 let out = convert("$x =~ s#old#new#g");
1696 assert_eq!(out, "$x =~ s#old#new#g");
1697 }
1698}