use crate::traits::{Reader, Writer};
use std::sync::{OnceLock, RwLock};
static READERS: RwLock<Vec<&'static dyn Reader>> = RwLock::new(Vec::new());
static READERS_INITIALIZED: OnceLock<()> = OnceLock::new();
static WRITERS: RwLock<Vec<&'static dyn Writer>> = RwLock::new(Vec::new());
static WRITERS_INITIALIZED: OnceLock<()> = OnceLock::new();
pub fn register_reader(reader: &'static dyn Reader) {
READERS.write().unwrap().push(reader);
}
pub fn register_writer(writer: &'static dyn Writer) {
WRITERS.write().unwrap().push(writer);
}
fn init_readers() {
READERS_INITIALIZED.get_or_init(|| {
#[cfg(feature = "read-typescript")]
{
register_reader(&crate::input::typescript::TYPESCRIPT_READER);
}
#[cfg(feature = "read-javascript")]
{
register_reader(&crate::input::javascript::JAVASCRIPT_READER);
}
#[cfg(feature = "read-lua")]
{
register_reader(&crate::input::lua::LUA_READER);
}
#[cfg(feature = "read-python")]
{
register_reader(&crate::input::python::PYTHON_READER);
}
});
}
fn init_writers() {
WRITERS_INITIALIZED.get_or_init(|| {
#[cfg(feature = "write-lua")]
{
register_writer(&crate::output::lua::LUA_WRITER);
}
#[cfg(feature = "write-typescript")]
{
register_writer(&crate::output::typescript::TYPESCRIPT_WRITER);
}
#[cfg(feature = "write-javascript")]
{
register_writer(&crate::output::javascript::JAVASCRIPT_WRITER);
}
#[cfg(feature = "write-python")]
{
register_writer(&crate::output::python::PYTHON_WRITER);
}
});
}
pub fn reader_for_language(lang: &str) -> Option<&'static dyn Reader> {
init_readers();
let guard = READERS.read().unwrap();
guard.iter().find(|r| r.language() == lang).copied()
}
pub fn reader_for_extension(ext: &str) -> Option<&'static dyn Reader> {
init_readers();
let guard = READERS.read().unwrap();
guard
.iter()
.find(|r| r.extensions().contains(&ext))
.copied()
}
pub fn writer_for_language(lang: &str) -> Option<&'static dyn Writer> {
init_writers();
let guard = WRITERS.read().unwrap();
guard.iter().find(|w| w.language() == lang).copied()
}
pub fn readers() -> Vec<&'static dyn Reader> {
init_readers();
READERS.read().unwrap().clone()
}
pub fn writers() -> Vec<&'static dyn Writer> {
init_writers();
WRITERS.read().unwrap().clone()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ir::StructureEq;
#[test]
#[cfg(feature = "read-typescript")]
fn test_reader_lookup() -> Result<(), String> {
let reader = reader_for_language("typescript").ok_or("typescript reader not found")?;
assert_eq!(reader.language(), "typescript");
assert!(reader.extensions().contains(&"ts"));
let reader = reader_for_extension("tsx").ok_or("tsx extension not found")?;
assert_eq!(reader.language(), "typescript");
Ok(())
}
#[test]
#[cfg(feature = "write-lua")]
fn test_writer_lookup() -> Result<(), String> {
let writer = writer_for_language("lua").ok_or("lua writer not found")?;
assert_eq!(writer.language(), "lua");
assert_eq!(writer.extension(), "lua");
Ok(())
}
#[test]
#[cfg(all(feature = "read-typescript", feature = "write-lua"))]
fn test_roundtrip_via_registry() -> Result<(), String> {
let reader = reader_for_language("typescript").ok_or("typescript reader not found")?;
let writer = writer_for_language("lua").ok_or("lua writer not found")?;
let ir = reader.read("const x = 1 + 2;").map_err(|e| e.to_string())?;
let lua = writer.write(&ir);
assert!(lua.contains("local x"));
Ok(())
}
#[test]
#[cfg(feature = "read-lua")]
fn test_lua_reader_lookup() -> Result<(), String> {
let reader = reader_for_language("lua").ok_or("lua reader not found")?;
assert_eq!(reader.language(), "lua");
assert!(reader.extensions().contains(&"lua"));
Ok(())
}
#[test]
#[cfg(feature = "write-typescript")]
fn test_typescript_writer_lookup() -> Result<(), String> {
let writer = writer_for_language("typescript").ok_or("typescript writer not found")?;
assert_eq!(writer.language(), "typescript");
assert_eq!(writer.extension(), "ts");
Ok(())
}
#[test]
#[cfg(all(feature = "read-lua", feature = "write-typescript"))]
fn test_lua_to_typescript_roundtrip() -> Result<(), String> {
let reader = reader_for_language("lua").ok_or("lua reader not found")?;
let writer = writer_for_language("typescript").ok_or("typescript writer not found")?;
let ir = reader.read("local x = 1 + 2").map_err(|e| e.to_string())?;
let ts = writer.write(&ir);
assert!(ts.contains("let x") || ts.contains("const x"));
assert!(ts.contains("1 + 2") || ts.contains("(1 + 2)"));
Ok(())
}
#[test]
#[cfg(all(feature = "read-typescript", feature = "write-typescript"))]
fn test_typescript_roundtrip() -> Result<(), String> {
let reader = reader_for_language("typescript").ok_or("typescript reader not found")?;
let writer = writer_for_language("typescript").ok_or("typescript writer not found")?;
let ir = reader.read("const x = 1 + 2;").map_err(|e| e.to_string())?;
let ts = writer.write(&ir);
assert!(ts.contains("const x"));
Ok(())
}
#[test]
#[cfg(all(
feature = "read-typescript",
feature = "write-lua",
feature = "read-lua"
))]
fn test_structure_eq_ts_lua_variable() -> Result<(), String> {
let ts_reader = reader_for_language("typescript").ok_or("typescript reader not found")?;
let lua_writer = writer_for_language("lua").ok_or("lua writer not found")?;
let lua_reader = reader_for_language("lua").ok_or("lua reader not found")?;
let ir1 = ts_reader.read("const x = 42;").map_err(|e| e.to_string())?;
let lua = lua_writer.write(&ir1);
let ir2 = lua_reader.read(&lua).map_err(|e| e.to_string())?;
assert!(
ir1.structure_eq(&ir2),
"IR mismatch:\nIR₁: {:?}\nLua: {}\nIR₂: {:?}",
ir1,
lua,
ir2
);
Ok(())
}
#[test]
#[cfg(all(
feature = "read-typescript",
feature = "write-lua",
feature = "read-lua"
))]
fn test_structure_eq_ts_lua_binary_expr() -> Result<(), String> {
let ts_reader = reader_for_language("typescript").ok_or("typescript reader not found")?;
let lua_writer = writer_for_language("lua").ok_or("lua writer not found")?;
let lua_reader = reader_for_language("lua").ok_or("lua reader not found")?;
let ir1 = ts_reader
.read("let result = 1 + 2 * 3;")
.map_err(|e| e.to_string())?;
let lua = lua_writer.write(&ir1);
let ir2 = lua_reader.read(&lua).map_err(|e| e.to_string())?;
assert!(
ir1.structure_eq(&ir2),
"IR mismatch:\nIR₁: {:?}\nLua: {}\nIR₂: {:?}",
ir1,
lua,
ir2
);
Ok(())
}
#[test]
#[cfg(all(
feature = "read-typescript",
feature = "write-lua",
feature = "read-lua"
))]
fn test_structure_eq_ts_lua_function_call() -> Result<(), String> {
let ts_reader = reader_for_language("typescript").ok_or("typescript reader not found")?;
let lua_writer = writer_for_language("lua").ok_or("lua writer not found")?;
let lua_reader = reader_for_language("lua").ok_or("lua reader not found")?;
let ir1 = ts_reader
.read("console.log(\"hello\", 42);")
.map_err(|e| e.to_string())?;
let lua = lua_writer.write(&ir1);
let ir2 = lua_reader.read(&lua).map_err(|e| e.to_string())?;
assert!(
ir1.structure_eq(&ir2),
"IR mismatch:\nIR₁: {:?}\nLua: {}\nIR₂: {:?}",
ir1,
lua,
ir2
);
Ok(())
}
#[test]
#[cfg(all(
feature = "read-typescript",
feature = "write-lua",
feature = "read-lua"
))]
fn test_structure_eq_ts_lua_if_statement() -> Result<(), String> {
let ts_reader = reader_for_language("typescript").ok_or("typescript reader not found")?;
let lua_writer = writer_for_language("lua").ok_or("lua writer not found")?;
let lua_reader = reader_for_language("lua").ok_or("lua reader not found")?;
let ir1 = ts_reader
.read("if (x > 0) { console.log(x); }")
.map_err(|e| e.to_string())?;
let lua = lua_writer.write(&ir1);
let ir2 = lua_reader.read(&lua).map_err(|e| e.to_string())?;
assert!(
ir1.structure_eq(&ir2),
"IR mismatch:\nIR₁: {:?}\nLua: {}\nIR₂: {:?}",
ir1,
lua,
ir2
);
Ok(())
}
#[test]
#[cfg(all(
feature = "read-lua",
feature = "write-typescript",
feature = "read-typescript"
))]
fn test_structure_eq_lua_ts_variable() -> Result<(), String> {
let lua_reader = reader_for_language("lua").ok_or("lua reader not found")?;
let ts_writer = writer_for_language("typescript").ok_or("typescript writer not found")?;
let ts_reader = reader_for_language("typescript").ok_or("typescript reader not found")?;
let ir1 = lua_reader.read("local x = 42").map_err(|e| e.to_string())?;
let ts = ts_writer.write(&ir1);
let ir2 = ts_reader.read(&ts).map_err(|e| e.to_string())?;
assert!(
ir1.structure_eq(&ir2),
"IR mismatch:\nIR₁: {:?}\nTS: {}\nIR₂: {:?}",
ir1,
ts,
ir2
);
Ok(())
}
#[test]
#[cfg(all(
feature = "read-lua",
feature = "write-typescript",
feature = "read-typescript"
))]
fn test_structure_eq_lua_ts_function() -> Result<(), String> {
let lua_reader = reader_for_language("lua").ok_or("lua reader not found")?;
let ts_writer = writer_for_language("typescript").ok_or("typescript writer not found")?;
let ts_reader = reader_for_language("typescript").ok_or("typescript reader not found")?;
let ir1 = lua_reader
.read("function add(a, b) return a + b end")
.map_err(|e| e.to_string())?;
let ts = ts_writer.write(&ir1);
let ir2 = ts_reader.read(&ts).map_err(|e| e.to_string())?;
assert!(
ir1.structure_eq(&ir2),
"IR mismatch:\nIR₁: {:?}\nTS: {}\nIR₂: {:?}",
ir1,
ts,
ir2
);
Ok(())
}
}