use crate::error::{CompilerError, Result};
use crate::generator::CodeGenerator;
use crate::lexer_utf8::Utf8Lexer;
use crate::parser::Parser;
use std::fs;
use std::path::{Path, PathBuf};
pub struct Compiler {
optimize: bool,
runtime: bool,
output_dir: Option<PathBuf>,
}
impl Compiler {
pub fn new() -> Self {
Self {
optimize: false,
runtime: false,
output_dir: None,
}
}
pub fn with_optimization(mut self, optimize: bool) -> Self {
self.optimize = optimize;
self
}
pub fn with_runtime(mut self, runtime: bool) -> Self {
self.runtime = runtime;
self
}
pub fn with_output_dir(mut self, output_dir: PathBuf) -> Self {
self.output_dir = Some(output_dir);
self
}
pub fn compile(&mut self, input: &Path, output: &Path) -> Result<()> {
let input_content = fs::read_to_string(input).map_err(CompilerError::Io)?;
let mut lexer = Utf8Lexer::new(input_content);
let tokens = lexer.tokenize()?;
let mut parser = Parser::new(tokens);
let program = parser.parse()?;
let mut generator = CodeGenerator::new(self.runtime);
let rust_code = generator.generate(&program)?;
self.write_output(output, &rust_code)?;
Ok(())
}
fn write_output(&self, output: &Path, rust_code: &str) -> Result<()> {
if output.is_dir() {
self.write_multiple_files(output, rust_code)?;
} else {
if let Some(parent) = output.parent() {
fs::create_dir_all(parent).map_err(CompilerError::Io)?;
}
fs::write(output, rust_code).map_err(CompilerError::Io)?;
}
Ok(())
}
fn write_multiple_files(&self, output_dir: &Path, rust_code: &str) -> Result<()> {
fs::create_dir_all(output_dir).map_err(CompilerError::Io)?;
let main_rs_path = output_dir.join("src").join("main.rs");
fs::create_dir_all(main_rs_path.parent().unwrap()).map_err(CompilerError::Io)?;
fs::write(&main_rs_path, rust_code).map_err(CompilerError::Io)?;
let cargo_toml = self.generate_cargo_toml();
let cargo_toml_path = output_dir.join("Cargo.toml");
fs::write(&cargo_toml_path, cargo_toml).map_err(CompilerError::Io)?;
let lib_rs_path = output_dir.join("src").join("lib.rs");
let lib_rs_content = self.generate_lib_rs();
fs::write(&lib_rs_path, lib_rs_content).map_err(CompilerError::Io)?;
Ok(())
}
fn generate_cargo_toml(&self) -> String {
let mut dependencies = vec![
"serde = { version = \"1.0\", features = [\"derive\"] }".to_string(),
"serde_json = \"1.0\"".to_string(),
];
if self.runtime {
dependencies.push("anyhow = \"1.0\"".to_string());
dependencies.push("thiserror = \"1.0\"".to_string());
}
format!(
r#"[package]
name = "generated_rust_project"
version = "0.4.0"
edition = "2021"
[dependencies]
{}
[profile.release]
opt-level = 3
lto = true
"#,
dependencies.join("\n")
)
}
fn generate_lib_rs(&self) -> String {
if self.runtime {
r#"
pub mod runtime;
pub mod types;
use runtime::*;
use types::*;
// Re-export commonly used types
pub use runtime::{Any, Unknown, TypeScriptObject};
"#
.to_string()
} else {
r#"
// Generated Rust code
"#
.to_string()
}
}
pub fn compile_project(&mut self, input_dir: &Path, output_dir: &Path) -> Result<()> {
let ts_files = self.find_typescript_files(input_dir)?;
if ts_files.is_empty() {
return Err(CompilerError::internal_error(
"No TypeScript files found in input directory",
));
}
fs::create_dir_all(output_dir).map_err(CompilerError::Io)?;
for ts_file in ts_files {
let relative_path = ts_file
.strip_prefix(input_dir)
.map_err(|_| CompilerError::internal_error("Failed to strip prefix"))?;
let rust_file = output_dir.join(relative_path).with_extension("rs");
if let Some(parent) = rust_file.parent() {
fs::create_dir_all(parent).map_err(CompilerError::Io)?;
}
self.compile(&ts_file, &rust_file)?;
}
self.generate_project_files(output_dir)?;
Ok(())
}
fn find_typescript_files(&self, dir: &Path) -> Result<Vec<PathBuf>> {
let mut ts_files = Vec::new();
for entry in fs::read_dir(dir).map_err(CompilerError::Io)? {
let entry = entry.map_err(CompilerError::Io)?;
let path = entry.path();
if path.is_dir() {
let sub_files = self.find_typescript_files(&path)?;
ts_files.extend(sub_files);
} else if path.extension().and_then(|s| s.to_str()) == Some("ts") {
ts_files.push(path);
}
}
Ok(ts_files)
}
fn generate_project_files(&self, output_dir: &Path) -> Result<()> {
let cargo_toml = self.generate_cargo_toml();
let cargo_toml_path = output_dir.join("Cargo.toml");
fs::write(&cargo_toml_path, cargo_toml).map_err(CompilerError::Io)?;
let readme = self.generate_readme();
let readme_path = output_dir.join("README.md");
fs::write(&readme_path, readme).map_err(CompilerError::Io)?;
let gitignore = self.generate_gitignore();
let gitignore_path = output_dir.join(".gitignore");
fs::write(&gitignore_path, gitignore).map_err(CompilerError::Io)?;
Ok(())
}
fn generate_readme(&self) -> String {
r#"# Generated Rust Project
This project was generated from TypeScript code using the TypeScript-Rust-Compiler.
## Building
```bash
cargo build
```
## Running
```bash
cargo run
```
## Testing
```bash
cargo test
```
## Features
- Generated from TypeScript source code
- Full Rust type safety
- Serde serialization support
"#
.to_string()
}
fn generate_gitignore(&self) -> String {
r#"# Rust
/target/
Cargo.lock
# IDE
.vscode/
.idea/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
"#
.to_string()
}
pub fn version() -> &'static str {
env!("CARGO_PKG_VERSION")
}
pub fn info(&self) -> String {
format!(
"TypeScript-Rust-Compiler v{}\nOptimization: {}\nRuntime: {}",
Self::version(),
self.optimize,
self.runtime
)
}
}
impl Default for Compiler {
fn default() -> Self {
Self::new()
}
}