#![cfg(not(target_arch = "wasm32"))]
use handlebars::Handlebars;
use serde_json::Value;
use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use tempfile::TempDir;
use thiserror::Error;
use super::cache::{CacheManager, CompilationCache};
use crate::modules::schema::Schema;
#[derive(Debug, Error)]
pub enum RuntimeCompilerError {
#[error("Template error: {0}")]
TemplateError(#[from] handlebars::RenderError),
#[error("Template file error: {0}")]
TemplateFileError(#[from] handlebars::TemplateError),
#[error("IO error: {0}")]
IoError(#[from] std::io::Error),
#[error("Cache error: {0}")]
CacheError(#[from] super::cache::CacheError),
#[error("Compilation error: {0}")]
CompilationError(String),
#[error("Library loading error: {0}")]
LibraryLoadingError(String),
}
pub struct RuntimeCompiler {
template_engine: Handlebars<'static>,
cache_manager: CacheManager,
temp_dir: Option<TempDir>,
}
impl RuntimeCompiler {
pub fn new() -> Result<Self, RuntimeCompilerError> {
let mut template_engine = Handlebars::new();
let template_path = "templates/token_based_converter.hbs";
if Path::new(template_path).exists() {
template_engine.register_template_file("token_based_converter", template_path)?;
}
let cache_manager = CacheManager::new()?;
Ok(Self {
template_engine,
cache_manager,
temp_dir: None,
})
}
pub fn compile_schema(
&mut self,
schema: &Schema,
) -> Result<CompiledProcessor, RuntimeCompilerError> {
let cache_key = self.cache_manager.generate_cache_key(schema);
if let Some(cached) = self.cache_manager.get_cached(&cache_key)? {
return Ok(CompiledProcessor::from_cache(cached));
}
let template_data = self.prepare_template_data(schema)?;
let generated_code = self
.template_engine
.render("token_based_converter", &template_data)?;
let temp_crate_dir = self.create_temp_crate(schema, &generated_code)?;
let dylib_path = self.compile_to_dylib(&temp_crate_dir)?;
let compilation_cache = CompilationCache {
schema_hash: cache_key.clone(),
dylib_path: dylib_path.clone(),
generated_code: generated_code.clone(),
metadata: schema.metadata.clone(),
};
self.cache_manager
.store_cache(&cache_key, &compilation_cache)?;
Ok(CompiledProcessor::new(dylib_path, schema.clone()))
}
fn prepare_template_data(&self, schema: &Schema) -> Result<Value, RuntimeCompilerError> {
let mut template_data = serde_json::Map::new();
template_data.insert(
"script_name".to_string(),
Value::String(schema.metadata.name.clone()),
);
template_data.insert(
"struct_name".to_string(),
Value::String(format!("{}Converter", schema.metadata.name)),
);
let is_alphabet = schema.target == "alphabet_tokens";
template_data.insert("is_alphabet".to_string(), Value::Bool(is_alphabet));
let mut mappings = Vec::new();
for (category, entries) in &schema.mappings {
let mut category_mappings = serde_json::Map::new();
category_mappings.insert("category".to_string(), Value::String(category.clone()));
let mut entries_list = Vec::new();
for (token, mapping) in entries {
let mut entry = serde_json::Map::new();
entry.insert("token".to_string(), Value::String(token.clone()));
match mapping {
Value::String(single_input) => {
entry.insert("preferred".to_string(), Value::String(single_input.clone()));
entry.insert(
"all_inputs".to_string(),
Value::Array(vec![Value::String(single_input.clone())]),
);
}
Value::Array(multiple_inputs) => {
if let Some(first) = multiple_inputs.first() {
entry.insert("preferred".to_string(), first.clone());
}
entry.insert("all_inputs".to_string(), mapping.clone());
}
_ => {
return Err(RuntimeCompilerError::CompilationError(
"Invalid mapping format".to_string(),
))
}
}
entries_list.push(Value::Object(entry));
}
category_mappings.insert("entries".to_string(), Value::Array(entries_list));
mappings.push(Value::Object(category_mappings));
}
template_data.insert("mappings".to_string(), Value::Array(mappings));
let has_multi_char = schema
.mappings
.values()
.flat_map(|entries| entries.values())
.any(|mapping| match mapping {
Value::String(s) => s.len() > 1,
Value::Array(arr) => arr.iter().any(|v| {
if let Value::String(s) = v {
s.len() > 1
} else {
false
}
}),
_ => false,
});
template_data.insert(
"has_multi_char_mappings".to_string(),
Value::Bool(has_multi_char),
);
Ok(Value::Object(template_data))
}
fn create_temp_crate(
&mut self,
schema: &Schema,
generated_code: &str,
) -> Result<PathBuf, RuntimeCompilerError> {
let temp_dir = TempDir::new()?;
let crate_dir = temp_dir.path().to_path_buf();
let cargo_toml = format!(
r#"
[package]
name = "{}_runtime"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
serde = {{ version = "1.0", features = ["derive"] }}
"#,
schema.metadata.name
);
fs::write(crate_dir.join("Cargo.toml"), cargo_toml)?;
let src_dir = crate_dir.join("src");
fs::create_dir_all(&src_dir)?;
let lib_rs = format!(
r#"
// Runtime generated token processor for {}
use serde::{{Deserialize, Serialize}};
{}
// Export functions for FFI
#[no_mangle]
pub extern "C" fn convert_string_to_tokens(input: *const std::os::raw::c_char) -> *mut std::os::raw::c_char {{
// Implementation for FFI interface
std::ptr::null_mut()
}}
#[no_mangle]
pub extern "C" fn convert_tokens_to_string(tokens: *const std::os::raw::c_char) -> *mut std::os::raw::c_char {{
// Implementation for FFI interface
std::ptr::null_mut()
}}
"#,
schema.metadata.name, generated_code
);
fs::write(src_dir.join("lib.rs"), lib_rs)?;
self.temp_dir = Some(temp_dir);
Ok(crate_dir)
}
fn compile_to_dylib(&self, crate_dir: &Path) -> Result<PathBuf, RuntimeCompilerError> {
let output = Command::new("cargo")
.args(["build", "--release"])
.current_dir(crate_dir)
.output()?;
if !output.status.success() {
let error_msg = String::from_utf8_lossy(&output.stderr);
return Err(RuntimeCompilerError::CompilationError(format!(
"Cargo build failed: {}",
error_msg
)));
}
let target_dir = crate_dir.join("target").join("release");
let dylib_extension = if cfg!(target_os = "macos") {
"dylib"
} else if cfg!(target_os = "windows") {
"dll"
} else {
"so"
};
let dylib_files: Vec<_> = fs::read_dir(&target_dir)?
.filter_map(|entry| entry.ok())
.filter(|entry| {
entry
.path()
.extension()
.and_then(|ext| ext.to_str())
.map(|ext| ext == dylib_extension)
.unwrap_or(false)
})
.collect();
if dylib_files.is_empty() {
return Err(RuntimeCompilerError::CompilationError(
"No dylib found after compilation".to_string(),
));
}
Ok(dylib_files[0].path())
}
}
pub struct CompiledProcessor {
dylib_path: PathBuf,
schema: Schema,
}
impl CompiledProcessor {
fn new(dylib_path: PathBuf, schema: Schema) -> Self {
Self { dylib_path, schema }
}
fn from_cache(cache: CompilationCache) -> Self {
Self {
dylib_path: cache.dylib_path,
schema: Schema {
metadata: cache.metadata,
target: "unknown".to_string(), mappings: HashMap::new(), },
}
}
pub fn get_dylib_path(&self) -> &Path {
&self.dylib_path
}
pub fn get_schema(&self) -> &Schema {
&self.schema
}
}
impl Default for RuntimeCompiler {
fn default() -> Self {
Self::new().expect("Failed to create RuntimeCompiler")
}
}