Skip to main content

stryke/
deconvert.rs

1//! Deconvert stryke .stk syntax back to standard Perl .pl syntax.
2//!
3//! This is the inverse of [`crate::convert`]: takes a parsed stryke program
4//! and emits valid Perl 5 source code that can run under stock `perl`.
5//!
6//! Transformations applied:
7//! - `fn` → `sub`
8//! - `p` (say alias) → `say`
9//! - Pipe chains and thread macros → nested function calls (preserved from parse)
10//! - Adds trailing semicolons
11//! - `#!/usr/bin/env perl` shebang prepended
12
13use crate::ast::*;
14use crate::deparse;
15
16/// Options for the deconvert module.
17#[derive(Debug, Clone, Default)]
18pub struct DeconvertOptions {
19    /// Custom delimiter for s///, tr///, m// patterns (e.g., '|', '#', '!').
20    pub output_delim: Option<char>,
21}
22
23/// Convert a parsed stryke program to standard Perl syntax.
24pub fn deconvert_program(p: &Program) -> String {
25    deconvert_program_with_options(p, &DeconvertOptions::default())
26}
27
28/// Convert a parsed stryke program to standard Perl syntax with custom options.
29pub fn deconvert_program_with_options(p: &Program, opts: &DeconvertOptions) -> String {
30    let body = if let Some(delim) = opts.output_delim {
31        deparse::deparse_block_with_delim(&p.statements, delim)
32    } else {
33        deparse::deparse_block(&p.statements)
34    };
35    format!(
36        "#!/usr/bin/env perl\nuse v5.10;\nuse strict;\nuse warnings;\n\n{}\n",
37        body
38    )
39}
40
41#[cfg(test)]
42mod tests {
43    use super::*;
44    use crate::parse;
45
46    fn deconvert(code: &str) -> String {
47        let p = parse(code).expect("parse failed");
48        deconvert_program(&p)
49    }
50
51    #[test]
52    fn deconvert_simple() {
53        let out = deconvert("my $x = 1;");
54        assert!(out.contains("#!/usr/bin/env perl"));
55        assert!(out.contains("my $x = 1;"));
56    }
57
58    #[test]
59    fn deconvert_fn_to_sub() {
60        let out = deconvert("fn foo { 42 }");
61        assert!(out.contains("sub foo"));
62        assert!(out.contains("42"));
63    }
64
65    #[test]
66    fn deconvert_say() {
67        let out = deconvert("p 'hello';");
68        assert!(out.contains("say"));
69        assert!(out.contains("hello"));
70    }
71
72    #[test]
73    fn deconvert_has_strict_warnings() {
74        let out = deconvert("1;");
75        assert!(out.contains("use strict;"));
76        assert!(out.contains("use warnings;"));
77    }
78
79    #[test]
80    fn deconvert_pipe_to_nested() {
81        let out = deconvert("@a |> map { $_ * 2 } |> join \",\";");
82        assert!(out.contains("join"));
83        assert!(out.contains("map"));
84        assert!(out.contains("@a"));
85        assert!(!out.contains("|>"));
86    }
87
88    #[test]
89    fn deconvert_thread_macro() {
90        let out = deconvert("t $x uc lc;");
91        assert!(out.contains("lc"));
92        assert!(out.contains("uc"));
93        assert!(out.contains("$x"));
94        assert!(!out.contains(" t "));
95    }
96}