normalize_surface_syntax/
registry.rs1use crate::traits::{Reader, Writer};
4use std::sync::{OnceLock, RwLock};
5
6static READERS: RwLock<Vec<&'static dyn Reader>> = RwLock::new(Vec::new());
8static READERS_INITIALIZED: OnceLock<()> = OnceLock::new();
9
10static WRITERS: RwLock<Vec<&'static dyn Writer>> = RwLock::new(Vec::new());
12static WRITERS_INITIALIZED: OnceLock<()> = OnceLock::new();
13
14pub fn register_reader(reader: &'static dyn Reader) {
16 READERS.write().unwrap().push(reader);
18}
19
20pub fn register_writer(writer: &'static dyn Writer) {
22 WRITERS.write().unwrap().push(writer);
24}
25
26fn init_readers() {
27 READERS_INITIALIZED.get_or_init(|| {
28 #[cfg(feature = "read-typescript")]
29 {
30 register_reader(&crate::input::typescript::TYPESCRIPT_READER);
31 }
32 #[cfg(feature = "read-javascript")]
33 {
34 register_reader(&crate::input::javascript::JAVASCRIPT_READER);
35 }
36 #[cfg(feature = "read-lua")]
37 {
38 register_reader(&crate::input::lua::LUA_READER);
39 }
40 #[cfg(feature = "read-python")]
41 {
42 register_reader(&crate::input::python::PYTHON_READER);
43 }
44 });
45}
46
47fn init_writers() {
48 WRITERS_INITIALIZED.get_or_init(|| {
49 #[cfg(feature = "write-lua")]
50 {
51 register_writer(&crate::output::lua::LUA_WRITER);
52 }
53 #[cfg(feature = "write-typescript")]
54 {
55 register_writer(&crate::output::typescript::TYPESCRIPT_WRITER);
56 }
57 #[cfg(feature = "write-javascript")]
58 {
59 register_writer(&crate::output::javascript::JAVASCRIPT_WRITER);
60 }
61 #[cfg(feature = "write-python")]
62 {
63 register_writer(&crate::output::python::PYTHON_WRITER);
64 }
65 });
66}
67
68pub fn reader_for_language(lang: &str) -> Option<&'static dyn Reader> {
70 init_readers();
71 let guard = READERS.read().unwrap();
73 guard.iter().find(|r| r.language() == lang).copied()
74}
75
76pub fn reader_for_extension(ext: &str) -> Option<&'static dyn Reader> {
78 init_readers();
79 let guard = READERS.read().unwrap();
81 guard
82 .iter()
83 .find(|r| r.extensions().contains(&ext))
84 .copied()
85}
86
87pub fn writer_for_language(lang: &str) -> Option<&'static dyn Writer> {
89 init_writers();
90 let guard = WRITERS.read().unwrap();
92 guard.iter().find(|w| w.language() == lang).copied()
93}
94
95pub fn readers() -> Vec<&'static dyn Reader> {
97 init_readers();
98 READERS.read().unwrap().clone()
100}
101
102pub fn writers() -> Vec<&'static dyn Writer> {
104 init_writers();
105 WRITERS.read().unwrap().clone()
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112 use crate::ir::StructureEq;
113
114 #[test]
115 #[cfg(feature = "read-typescript")]
116 fn test_reader_lookup() -> Result<(), String> {
117 let reader = reader_for_language("typescript").ok_or("typescript reader not found")?;
118 assert_eq!(reader.language(), "typescript");
119 assert!(reader.extensions().contains(&"ts"));
120
121 let reader = reader_for_extension("tsx").ok_or("tsx extension not found")?;
122 assert_eq!(reader.language(), "typescript");
123 Ok(())
124 }
125
126 #[test]
127 #[cfg(feature = "write-lua")]
128 fn test_writer_lookup() -> Result<(), String> {
129 let writer = writer_for_language("lua").ok_or("lua writer not found")?;
130 assert_eq!(writer.language(), "lua");
131 assert_eq!(writer.extension(), "lua");
132 Ok(())
133 }
134
135 #[test]
136 #[cfg(all(feature = "read-typescript", feature = "write-lua"))]
137 fn test_roundtrip_via_registry() -> Result<(), String> {
138 let reader = reader_for_language("typescript").ok_or("typescript reader not found")?;
139 let writer = writer_for_language("lua").ok_or("lua writer not found")?;
140
141 let ir = reader.read("const x = 1 + 2;").map_err(|e| e.to_string())?;
142 let lua = writer.write(&ir);
143
144 assert!(lua.contains("local x"));
145 Ok(())
146 }
147
148 #[test]
149 #[cfg(feature = "read-lua")]
150 fn test_lua_reader_lookup() -> Result<(), String> {
151 let reader = reader_for_language("lua").ok_or("lua reader not found")?;
152 assert_eq!(reader.language(), "lua");
153 assert!(reader.extensions().contains(&"lua"));
154 Ok(())
155 }
156
157 #[test]
158 #[cfg(feature = "write-typescript")]
159 fn test_typescript_writer_lookup() -> Result<(), String> {
160 let writer = writer_for_language("typescript").ok_or("typescript writer not found")?;
161 assert_eq!(writer.language(), "typescript");
162 assert_eq!(writer.extension(), "ts");
163 Ok(())
164 }
165
166 #[test]
167 #[cfg(all(feature = "read-lua", feature = "write-typescript"))]
168 fn test_lua_to_typescript_roundtrip() -> Result<(), String> {
169 let reader = reader_for_language("lua").ok_or("lua reader not found")?;
170 let writer = writer_for_language("typescript").ok_or("typescript writer not found")?;
171
172 let ir = reader.read("local x = 1 + 2").map_err(|e| e.to_string())?;
173 let ts = writer.write(&ir);
174
175 assert!(ts.contains("let x") || ts.contains("const x"));
176 assert!(ts.contains("1 + 2") || ts.contains("(1 + 2)"));
177 Ok(())
178 }
179
180 #[test]
181 #[cfg(all(feature = "read-typescript", feature = "write-typescript"))]
182 fn test_typescript_roundtrip() -> Result<(), String> {
183 let reader = reader_for_language("typescript").ok_or("typescript reader not found")?;
184 let writer = writer_for_language("typescript").ok_or("typescript writer not found")?;
185
186 let ir = reader.read("const x = 1 + 2;").map_err(|e| e.to_string())?;
187 let ts = writer.write(&ir);
188
189 assert!(ts.contains("const x"));
190 Ok(())
191 }
192
193 #[test]
202 #[cfg(all(
203 feature = "read-typescript",
204 feature = "write-lua",
205 feature = "read-lua"
206 ))]
207 fn test_structure_eq_ts_lua_variable() -> Result<(), String> {
208 let ts_reader = reader_for_language("typescript").ok_or("typescript reader not found")?;
209 let lua_writer = writer_for_language("lua").ok_or("lua writer not found")?;
210 let lua_reader = reader_for_language("lua").ok_or("lua reader not found")?;
211
212 let ir1 = ts_reader.read("const x = 42;").map_err(|e| e.to_string())?;
214 let lua = lua_writer.write(&ir1);
216 let ir2 = lua_reader.read(&lua).map_err(|e| e.to_string())?;
218
219 assert!(
220 ir1.structure_eq(&ir2),
221 "IR mismatch:\nIR₁: {:?}\nLua: {}\nIR₂: {:?}",
222 ir1,
223 lua,
224 ir2
225 );
226 Ok(())
227 }
228
229 #[test]
230 #[cfg(all(
231 feature = "read-typescript",
232 feature = "write-lua",
233 feature = "read-lua"
234 ))]
235 fn test_structure_eq_ts_lua_binary_expr() -> Result<(), String> {
236 let ts_reader = reader_for_language("typescript").ok_or("typescript reader not found")?;
237 let lua_writer = writer_for_language("lua").ok_or("lua writer not found")?;
238 let lua_reader = reader_for_language("lua").ok_or("lua reader not found")?;
239
240 let ir1 = ts_reader
241 .read("let result = 1 + 2 * 3;")
242 .map_err(|e| e.to_string())?;
243 let lua = lua_writer.write(&ir1);
244 let ir2 = lua_reader.read(&lua).map_err(|e| e.to_string())?;
245
246 assert!(
247 ir1.structure_eq(&ir2),
248 "IR mismatch:\nIR₁: {:?}\nLua: {}\nIR₂: {:?}",
249 ir1,
250 lua,
251 ir2
252 );
253 Ok(())
254 }
255
256 #[test]
257 #[cfg(all(
258 feature = "read-typescript",
259 feature = "write-lua",
260 feature = "read-lua"
261 ))]
262 fn test_structure_eq_ts_lua_function_call() -> Result<(), String> {
263 let ts_reader = reader_for_language("typescript").ok_or("typescript reader not found")?;
264 let lua_writer = writer_for_language("lua").ok_or("lua writer not found")?;
265 let lua_reader = reader_for_language("lua").ok_or("lua reader not found")?;
266
267 let ir1 = ts_reader
268 .read("console.log(\"hello\", 42);")
269 .map_err(|e| e.to_string())?;
270 let lua = lua_writer.write(&ir1);
271 let ir2 = lua_reader.read(&lua).map_err(|e| e.to_string())?;
272
273 assert!(
274 ir1.structure_eq(&ir2),
275 "IR mismatch:\nIR₁: {:?}\nLua: {}\nIR₂: {:?}",
276 ir1,
277 lua,
278 ir2
279 );
280 Ok(())
281 }
282
283 #[test]
284 #[cfg(all(
285 feature = "read-typescript",
286 feature = "write-lua",
287 feature = "read-lua"
288 ))]
289 fn test_structure_eq_ts_lua_if_statement() -> Result<(), String> {
290 let ts_reader = reader_for_language("typescript").ok_or("typescript reader not found")?;
291 let lua_writer = writer_for_language("lua").ok_or("lua writer not found")?;
292 let lua_reader = reader_for_language("lua").ok_or("lua reader not found")?;
293
294 let ir1 = ts_reader
295 .read("if (x > 0) { console.log(x); }")
296 .map_err(|e| e.to_string())?;
297 let lua = lua_writer.write(&ir1);
298 let ir2 = lua_reader.read(&lua).map_err(|e| e.to_string())?;
299
300 assert!(
301 ir1.structure_eq(&ir2),
302 "IR mismatch:\nIR₁: {:?}\nLua: {}\nIR₂: {:?}",
303 ir1,
304 lua,
305 ir2
306 );
307 Ok(())
308 }
309
310 #[test]
311 #[cfg(all(
312 feature = "read-lua",
313 feature = "write-typescript",
314 feature = "read-typescript"
315 ))]
316 fn test_structure_eq_lua_ts_variable() -> Result<(), String> {
317 let lua_reader = reader_for_language("lua").ok_or("lua reader not found")?;
318 let ts_writer = writer_for_language("typescript").ok_or("typescript writer not found")?;
319 let ts_reader = reader_for_language("typescript").ok_or("typescript reader not found")?;
320
321 let ir1 = lua_reader.read("local x = 42").map_err(|e| e.to_string())?;
323 let ts = ts_writer.write(&ir1);
325 let ir2 = ts_reader.read(&ts).map_err(|e| e.to_string())?;
327
328 assert!(
329 ir1.structure_eq(&ir2),
330 "IR mismatch:\nIR₁: {:?}\nTS: {}\nIR₂: {:?}",
331 ir1,
332 ts,
333 ir2
334 );
335 Ok(())
336 }
337
338 #[test]
339 #[cfg(all(
340 feature = "read-lua",
341 feature = "write-typescript",
342 feature = "read-typescript"
343 ))]
344 fn test_structure_eq_lua_ts_function() -> Result<(), String> {
345 let lua_reader = reader_for_language("lua").ok_or("lua reader not found")?;
346 let ts_writer = writer_for_language("typescript").ok_or("typescript writer not found")?;
347 let ts_reader = reader_for_language("typescript").ok_or("typescript reader not found")?;
348
349 let ir1 = lua_reader
350 .read("function add(a, b) return a + b end")
351 .map_err(|e| e.to_string())?;
352 let ts = ts_writer.write(&ir1);
353 let ir2 = ts_reader.read(&ts).map_err(|e| e.to_string())?;
354
355 assert!(
356 ir1.structure_eq(&ir2),
357 "IR mismatch:\nIR₁: {:?}\nTS: {}\nIR₂: {:?}",
358 ir1,
359 ts,
360 ir2
361 );
362 Ok(())
363 }
364}