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
62fn escape_double_quoted(pattern: &str) -> String {
64 pattern.replace('\\', "\\\\").replace('"', "\\\"")
65}
66
67fn 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}