Skip to main content

rgx/
codegen.rs

1use std::fmt;
2
3use crate::engine::EngineFlags;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum Language {
7    Rust,
8    Python,
9    JavaScript,
10    Go,
11    Java,
12    CSharp,
13    Php,
14    Ruby,
15}
16
17pub const ALL_LANGUAGES: &[Language] = &[
18    Language::Rust,
19    Language::Python,
20    Language::JavaScript,
21    Language::Go,
22    Language::Java,
23    Language::CSharp,
24    Language::Php,
25    Language::Ruby,
26];
27
28impl Language {
29    pub fn all() -> &'static [Language] {
30        ALL_LANGUAGES
31    }
32}
33
34impl fmt::Display for Language {
35    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36        match self {
37            Language::Rust => write!(f, "Rust"),
38            Language::Python => write!(f, "Python"),
39            Language::JavaScript => write!(f, "JavaScript"),
40            Language::Go => write!(f, "Go"),
41            Language::Java => write!(f, "Java"),
42            Language::CSharp => write!(f, "C#"),
43            Language::Php => write!(f, "PHP"),
44            Language::Ruby => write!(f, "Ruby"),
45        }
46    }
47}
48
49pub fn generate_code(lang: &Language, pattern: &str, flags: &EngineFlags) -> String {
50    match lang {
51        Language::Rust => generate_rust(pattern, flags),
52        Language::Python => generate_python(pattern, flags),
53        Language::JavaScript => generate_javascript(pattern, flags),
54        Language::Go => generate_go(pattern, flags),
55        Language::Java => generate_java(pattern, flags),
56        Language::CSharp => generate_csharp(pattern, flags),
57        Language::Php => generate_php(pattern, flags),
58        Language::Ruby => generate_ruby(pattern, flags),
59    }
60}
61
62/// Escape a pattern for use inside a double-quoted string literal.
63fn escape_double_quoted(pattern: &str) -> String {
64    pattern.replace('\\', "\\\\").replace('"', "\\\"")
65}
66
67/// Collect active flag names from a language-specific mapping.
68fn collect_flags<'a>(mapping: &[(&'a str, bool)]) -> Vec<&'a str> {
69    mapping
70        .iter()
71        .filter(|(_, active)| *active)
72        .map(|(name, _)| *name)
73        .collect()
74}
75
76fn generate_rust(pattern: &str, flags: &EngineFlags) -> String {
77    let escaped = escape_double_quoted(pattern);
78    let has_flags = flags.case_insensitive
79        || flags.multi_line
80        || flags.dot_matches_newline
81        || flags.unicode
82        || flags.extended;
83
84    if has_flags {
85        let mut lines = String::from("use regex::RegexBuilder;\n\n");
86        lines.push_str(&format!("let re = RegexBuilder::new(r\"{}\")\n", escaped));
87        if flags.case_insensitive {
88            lines.push_str("    .case_insensitive(true)\n");
89        }
90        if flags.multi_line {
91            lines.push_str("    .multi_line(true)\n");
92        }
93        if flags.dot_matches_newline {
94            lines.push_str("    .dot_matches_new_line(true)\n");
95        }
96        if flags.unicode {
97            lines.push_str("    .unicode(true)\n");
98        }
99        if flags.extended {
100            lines.push_str("    .ignore_whitespace(true)\n");
101        }
102        lines.push_str("    .build()\n    .unwrap();\n");
103        lines.push_str(
104            "let matches: Vec<&str> = re.find_iter(text).map(|m| m.as_str()).collect();\n",
105        );
106        lines
107    } else {
108        format!(
109            "use regex::Regex;\n\n\
110             let re = Regex::new(r\"{}\").unwrap();\n\
111             let matches: Vec<&str> = re.find_iter(text).map(|m| m.as_str()).collect();\n",
112            escaped
113        )
114    }
115}
116
117fn generate_python(pattern: &str, flags: &EngineFlags) -> String {
118    let escaped = escape_double_quoted(pattern);
119    let flag_parts = collect_flags(&[
120        ("re.IGNORECASE", flags.case_insensitive),
121        ("re.MULTILINE", flags.multi_line),
122        ("re.DOTALL", flags.dot_matches_newline),
123        ("re.UNICODE", flags.unicode),
124        ("re.VERBOSE", flags.extended),
125    ]);
126
127    if flag_parts.is_empty() {
128        format!(
129            "import re\n\n\
130             pattern = re.compile(r\"{}\")\n\
131             matches = pattern.findall(text)\n",
132            escaped
133        )
134    } else {
135        format!(
136            "import re\n\n\
137             pattern = re.compile(r\"{}\", {})\n\
138             matches = pattern.findall(text)\n",
139            escaped,
140            flag_parts.join(" | ")
141        )
142    }
143}
144
145fn generate_javascript(pattern: &str, flags: &EngineFlags) -> String {
146    let escaped = pattern.replace('/', "\\/");
147    let mut js_flags = String::from("g");
148    if flags.case_insensitive {
149        js_flags.push('i');
150    }
151    if flags.multi_line {
152        js_flags.push('m');
153    }
154    if flags.dot_matches_newline {
155        js_flags.push('s');
156    }
157    if flags.unicode {
158        js_flags.push('u');
159    }
160
161    format!(
162        "const regex = /{}/{js_flags};\n\
163         const matches = [...text.matchAll(regex)];\n",
164        escaped
165    )
166}
167
168fn generate_go(pattern: &str, flags: &EngineFlags) -> String {
169    let escaped = pattern.replace('`', "`+\"`\"+`");
170    let mut inline_flags = String::new();
171    if flags.case_insensitive {
172        inline_flags.push('i');
173    }
174    if flags.multi_line {
175        inline_flags.push('m');
176    }
177    if flags.dot_matches_newline {
178        inline_flags.push('s');
179    }
180    if flags.unicode {
181        inline_flags.push('U');
182    }
183
184    let pattern_str = if inline_flags.is_empty() {
185        format!("`{}`", escaped)
186    } else {
187        format!("`(?{}){}`", inline_flags, escaped)
188    };
189
190    format!(
191        "import \"regexp\"\n\n\
192         re := regexp.MustCompile({})\n\
193         matches := re.FindAllString(text, -1)\n",
194        pattern_str
195    )
196}
197
198fn generate_java(pattern: &str, flags: &EngineFlags) -> String {
199    let escaped = escape_double_quoted(pattern);
200    let flag_parts = collect_flags(&[
201        ("Pattern.CASE_INSENSITIVE", flags.case_insensitive),
202        ("Pattern.MULTILINE", flags.multi_line),
203        ("Pattern.DOTALL", flags.dot_matches_newline),
204        ("Pattern.UNICODE_CHARACTER_CLASS", flags.unicode),
205        ("Pattern.COMMENTS", flags.extended),
206    ]);
207
208    if flag_parts.is_empty() {
209        format!(
210            "import java.util.regex.*;\n\n\
211             Pattern pattern = Pattern.compile(\"{}\");\n\
212             Matcher matcher = pattern.matcher(text);\n\
213             while (matcher.find()) {{\n\
214             \x20   System.out.println(matcher.group());\n\
215             }}\n",
216            escaped
217        )
218    } else {
219        format!(
220            "import java.util.regex.*;\n\n\
221             Pattern pattern = Pattern.compile(\"{}\", {});\n\
222             Matcher matcher = pattern.matcher(text);\n\
223             while (matcher.find()) {{\n\
224             \x20   System.out.println(matcher.group());\n\
225             }}\n",
226            escaped,
227            flag_parts.join(" | ")
228        )
229    }
230}
231
232fn generate_csharp(pattern: &str, flags: &EngineFlags) -> String {
233    let escaped = pattern.replace('"', "\"\"");
234    let flag_parts = collect_flags(&[
235        ("RegexOptions.IgnoreCase", flags.case_insensitive),
236        ("RegexOptions.Multiline", flags.multi_line),
237        ("RegexOptions.Singleline", flags.dot_matches_newline),
238        ("RegexOptions.IgnorePatternWhitespace", flags.extended),
239    ]);
240
241    if flag_parts.is_empty() {
242        format!(
243            "using System.Text.RegularExpressions;\n\n\
244             var regex = new Regex(@\"{}\");\n\
245             var matches = regex.Matches(text);\n",
246            escaped
247        )
248    } else {
249        format!(
250            "using System.Text.RegularExpressions;\n\n\
251             var regex = new Regex(@\"{}\", {});\n\
252             var matches = regex.Matches(text);\n",
253            escaped,
254            flag_parts.join(" | ")
255        )
256    }
257}
258
259fn generate_php(pattern: &str, flags: &EngineFlags) -> String {
260    let escaped = pattern.replace('\'', "\\'").replace('/', "\\/");
261    let php_flags = flags.to_inline_prefix();
262
263    format!(
264        "$pattern = '/{}/{}';\n\
265         preg_match_all($pattern, $text, $matches);\n",
266        escaped, php_flags
267    )
268}
269
270fn generate_ruby(pattern: &str, flags: &EngineFlags) -> String {
271    let escaped = pattern.replace('/', "\\/");
272    let mut ruby_flags = String::new();
273    if flags.case_insensitive {
274        ruby_flags.push('i');
275    }
276    if flags.multi_line {
277        ruby_flags.push('m');
278    }
279    if flags.extended {
280        ruby_flags.push('x');
281    }
282
283    format!(
284        "pattern = /{}/{}\n\
285         matches = text.scan(pattern)\n",
286        escaped, ruby_flags
287    )
288}