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);
17}
18
19pub fn register_writer(writer: &'static dyn Writer) {
21 WRITERS.write().unwrap().push(writer);
22}
23
24fn init_readers() {
25 READERS_INITIALIZED.get_or_init(|| {
26 #[cfg(feature = "read-typescript")]
27 {
28 register_reader(&crate::input::typescript::TYPESCRIPT_READER);
29 }
30 #[cfg(feature = "read-lua")]
31 {
32 register_reader(&crate::input::lua::LUA_READER);
33 }
34 #[cfg(feature = "read-python")]
35 {
36 register_reader(&crate::input::python::PYTHON_READER);
37 }
38 });
39}
40
41fn init_writers() {
42 WRITERS_INITIALIZED.get_or_init(|| {
43 #[cfg(feature = "write-lua")]
44 {
45 register_writer(&crate::output::lua::LUA_WRITER);
46 }
47 #[cfg(feature = "write-typescript")]
48 {
49 register_writer(&crate::output::typescript::TYPESCRIPT_WRITER);
50 }
51 #[cfg(feature = "write-python")]
52 {
53 register_writer(&crate::output::python::PYTHON_WRITER);
54 }
55 });
56}
57
58pub fn reader_for_language(lang: &str) -> Option<&'static dyn Reader> {
60 init_readers();
61 READERS
62 .read()
63 .unwrap()
64 .iter()
65 .find(|r| r.language() == lang)
66 .copied()
67}
68
69pub fn reader_for_extension(ext: &str) -> Option<&'static dyn Reader> {
71 init_readers();
72 READERS
73 .read()
74 .unwrap()
75 .iter()
76 .find(|r| r.extensions().contains(&ext))
77 .copied()
78}
79
80pub fn writer_for_language(lang: &str) -> Option<&'static dyn Writer> {
82 init_writers();
83 WRITERS
84 .read()
85 .unwrap()
86 .iter()
87 .find(|w| w.language() == lang)
88 .copied()
89}
90
91pub fn readers() -> Vec<&'static dyn Reader> {
93 init_readers();
94 READERS.read().unwrap().clone()
95}
96
97pub fn writers() -> Vec<&'static dyn Writer> {
99 init_writers();
100 WRITERS.read().unwrap().clone()
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106 use crate::ir::StructureEq;
107
108 #[test]
109 #[cfg(feature = "read-typescript")]
110 fn test_reader_lookup() {
111 let reader = reader_for_language("typescript").expect("typescript reader");
112 assert_eq!(reader.language(), "typescript");
113 assert!(reader.extensions().contains(&"ts"));
114
115 let reader = reader_for_extension("tsx").expect("tsx extension");
116 assert_eq!(reader.language(), "typescript");
117 }
118
119 #[test]
120 #[cfg(feature = "write-lua")]
121 fn test_writer_lookup() {
122 let writer = writer_for_language("lua").expect("lua writer");
123 assert_eq!(writer.language(), "lua");
124 assert_eq!(writer.extension(), "lua");
125 }
126
127 #[test]
128 #[cfg(all(feature = "read-typescript", feature = "write-lua"))]
129 fn test_roundtrip_via_registry() {
130 let reader = reader_for_language("typescript").unwrap();
131 let writer = writer_for_language("lua").unwrap();
132
133 let ir = reader.read("const x = 1 + 2;").unwrap();
134 let lua = writer.write(&ir);
135
136 assert!(lua.contains("local x"));
137 }
138
139 #[test]
140 #[cfg(feature = "read-lua")]
141 fn test_lua_reader_lookup() {
142 let reader = reader_for_language("lua").expect("lua reader");
143 assert_eq!(reader.language(), "lua");
144 assert!(reader.extensions().contains(&"lua"));
145 }
146
147 #[test]
148 #[cfg(feature = "write-typescript")]
149 fn test_typescript_writer_lookup() {
150 let writer = writer_for_language("typescript").expect("typescript writer");
151 assert_eq!(writer.language(), "typescript");
152 assert_eq!(writer.extension(), "ts");
153 }
154
155 #[test]
156 #[cfg(all(feature = "read-lua", feature = "write-typescript"))]
157 fn test_lua_to_typescript_roundtrip() {
158 let reader = reader_for_language("lua").unwrap();
159 let writer = writer_for_language("typescript").unwrap();
160
161 let ir = reader.read("local x = 1 + 2").unwrap();
162 let ts = writer.write(&ir);
163
164 assert!(ts.contains("let x") || ts.contains("const x"));
165 assert!(ts.contains("1 + 2") || ts.contains("(1 + 2)"));
166 }
167
168 #[test]
169 #[cfg(all(feature = "read-typescript", feature = "write-typescript"))]
170 fn test_typescript_roundtrip() {
171 let reader = reader_for_language("typescript").unwrap();
172 let writer = writer_for_language("typescript").unwrap();
173
174 let ir = reader.read("const x = 1 + 2;").unwrap();
175 let ts = writer.write(&ir);
176
177 assert!(ts.contains("const x"));
178 }
179
180 #[test]
189 #[cfg(all(
190 feature = "read-typescript",
191 feature = "write-lua",
192 feature = "read-lua"
193 ))]
194 fn test_structure_eq_ts_lua_variable() {
195 let ts_reader = reader_for_language("typescript").unwrap();
196 let lua_writer = writer_for_language("lua").unwrap();
197 let lua_reader = reader_for_language("lua").unwrap();
198
199 let ir1 = ts_reader.read("const x = 42;").unwrap();
201 let lua = lua_writer.write(&ir1);
203 let ir2 = lua_reader.read(&lua).unwrap();
205
206 assert!(
207 ir1.structure_eq(&ir2),
208 "IR mismatch:\nIR₁: {:?}\nLua: {}\nIR₂: {:?}",
209 ir1,
210 lua,
211 ir2
212 );
213 }
214
215 #[test]
216 #[cfg(all(
217 feature = "read-typescript",
218 feature = "write-lua",
219 feature = "read-lua"
220 ))]
221 fn test_structure_eq_ts_lua_binary_expr() {
222 let ts_reader = reader_for_language("typescript").unwrap();
223 let lua_writer = writer_for_language("lua").unwrap();
224 let lua_reader = reader_for_language("lua").unwrap();
225
226 let ir1 = ts_reader.read("let result = 1 + 2 * 3;").unwrap();
227 let lua = lua_writer.write(&ir1);
228 let ir2 = lua_reader.read(&lua).unwrap();
229
230 assert!(
231 ir1.structure_eq(&ir2),
232 "IR mismatch:\nIR₁: {:?}\nLua: {}\nIR₂: {:?}",
233 ir1,
234 lua,
235 ir2
236 );
237 }
238
239 #[test]
240 #[cfg(all(
241 feature = "read-typescript",
242 feature = "write-lua",
243 feature = "read-lua"
244 ))]
245 fn test_structure_eq_ts_lua_function_call() {
246 let ts_reader = reader_for_language("typescript").unwrap();
247 let lua_writer = writer_for_language("lua").unwrap();
248 let lua_reader = reader_for_language("lua").unwrap();
249
250 let ir1 = ts_reader.read("console.log(\"hello\", 42);").unwrap();
251 let lua = lua_writer.write(&ir1);
252 let ir2 = lua_reader.read(&lua).unwrap();
253
254 assert!(
255 ir1.structure_eq(&ir2),
256 "IR mismatch:\nIR₁: {:?}\nLua: {}\nIR₂: {:?}",
257 ir1,
258 lua,
259 ir2
260 );
261 }
262
263 #[test]
264 #[cfg(all(
265 feature = "read-typescript",
266 feature = "write-lua",
267 feature = "read-lua"
268 ))]
269 fn test_structure_eq_ts_lua_if_statement() {
270 let ts_reader = reader_for_language("typescript").unwrap();
271 let lua_writer = writer_for_language("lua").unwrap();
272 let lua_reader = reader_for_language("lua").unwrap();
273
274 let ir1 = ts_reader.read("if (x > 0) { console.log(x); }").unwrap();
275 let lua = lua_writer.write(&ir1);
276 let ir2 = lua_reader.read(&lua).unwrap();
277
278 assert!(
279 ir1.structure_eq(&ir2),
280 "IR mismatch:\nIR₁: {:?}\nLua: {}\nIR₂: {:?}",
281 ir1,
282 lua,
283 ir2
284 );
285 }
286
287 #[test]
288 #[cfg(all(
289 feature = "read-lua",
290 feature = "write-typescript",
291 feature = "read-typescript"
292 ))]
293 fn test_structure_eq_lua_ts_variable() {
294 let lua_reader = reader_for_language("lua").unwrap();
295 let ts_writer = writer_for_language("typescript").unwrap();
296 let ts_reader = reader_for_language("typescript").unwrap();
297
298 let ir1 = lua_reader.read("local x = 42").unwrap();
300 let ts = ts_writer.write(&ir1);
302 let ir2 = ts_reader.read(&ts).unwrap();
304
305 assert!(
306 ir1.structure_eq(&ir2),
307 "IR mismatch:\nIR₁: {:?}\nTS: {}\nIR₂: {:?}",
308 ir1,
309 ts,
310 ir2
311 );
312 }
313
314 #[test]
315 #[cfg(all(
316 feature = "read-lua",
317 feature = "write-typescript",
318 feature = "read-typescript"
319 ))]
320 fn test_structure_eq_lua_ts_function() {
321 let lua_reader = reader_for_language("lua").unwrap();
322 let ts_writer = writer_for_language("typescript").unwrap();
323 let ts_reader = reader_for_language("typescript").unwrap();
324
325 let ir1 = lua_reader
326 .read("function add(a, b) return a + b end")
327 .unwrap();
328 let ts = ts_writer.write(&ir1);
329 let ir2 = ts_reader.read(&ts).unwrap();
330
331 assert!(
332 ir1.structure_eq(&ir2),
333 "IR mismatch:\nIR₁: {:?}\nTS: {}\nIR₂: {:?}",
334 ir1,
335 ts,
336 ir2
337 );
338 }
339}