use crate::parser::Program;
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Target {
Rust,
Go,
JavaScript,
WebAssembly,
Wgsl,
}
impl std::str::FromStr for Target {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"rust" | "rs" => Ok(Target::Rust),
"go" | "golang" => Ok(Target::Go),
"javascript" | "js" => Ok(Target::JavaScript),
"wasm" | "webassembly" => Ok(Target::WebAssembly),
"wgsl" => Ok(Target::Wgsl),
_ => Err(format!("Unknown target: {}", s)),
}
}
}
impl Target {
pub fn as_str(&self) -> &'static str {
match self {
Target::Rust => "rust",
Target::Go => "go",
Target::JavaScript => "javascript",
Target::WebAssembly => "webassembly",
Target::Wgsl => "wgsl",
}
}
pub fn file_extension(&self) -> &'static str {
match self {
Target::Rust => "rs",
Target::Go => "go",
Target::JavaScript => "js",
Target::WebAssembly => "wasm",
Target::Wgsl => "wgsl",
}
}
}
impl std::fmt::Display for Target {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CodegenConfig {
pub target: Target,
pub output_dir: PathBuf,
pub source_maps: bool,
pub type_definitions: bool,
pub optimization_level: u8,
pub idiomatic_output: bool,
pub pretty_print: bool,
pub include_comments: bool,
pub minify: bool,
pub tree_shake: bool,
pub polyfills: bool,
pub v8_optimize: bool,
}
impl Default for CodegenConfig {
fn default() -> Self {
Self {
target: Target::Rust,
output_dir: PathBuf::from("output"),
source_maps: true,
type_definitions: true,
optimization_level: 2,
idiomatic_output: true,
pretty_print: true,
include_comments: false,
minify: false,
tree_shake: false,
polyfills: false,
v8_optimize: false,
}
}
}
#[derive(Debug, Clone)]
pub struct CodegenOutput {
pub source: String,
pub source_map: Option<String>,
pub type_definitions: Option<String>,
pub additional_files: Vec<(String, String)>,
pub extension: String,
}
impl CodegenOutput {
pub fn new(source: String, extension: String) -> Self {
Self {
source,
source_map: None,
type_definitions: None,
additional_files: Vec::new(),
extension,
}
}
pub fn with_source_map(mut self, source_map: String) -> Self {
self.source_map = Some(source_map);
self
}
pub fn with_type_definitions(mut self, type_defs: String) -> Self {
self.type_definitions = Some(type_defs);
self
}
pub fn add_file(&mut self, filename: String, content: String) {
self.additional_files.push((filename, content));
}
}
pub trait CodegenBackend: Send + Sync {
fn name(&self) -> &str;
fn target(&self) -> Target;
fn generate(&self, program: &Program, config: &CodegenConfig) -> Result<CodegenOutput>;
fn make_idiomatic(&self, code: String, _config: &CodegenConfig) -> Result<String> {
Ok(code)
}
fn generate_type_definitions(&self, _program: &Program) -> Option<String> {
None
}
fn generate_additional_files(
&self,
_program: &Program,
_config: &CodegenConfig,
) -> Vec<(String, String)> {
Vec::new()
}
fn target_specific_optimizations(&self) -> Vec<String> {
Vec::new()
}
}
pub fn create_backend(target: Target) -> Box<dyn CodegenBackend> {
match target {
Target::Rust => Box::new(crate::codegen::rust::RustBackend::new()),
Target::Go => Box::new(crate::codegen::go::GoBackend::new()),
Target::JavaScript => Box::new(crate::codegen::javascript::JavaScriptBackend::new()),
Target::WebAssembly => Box::new(crate::codegen::wasm::WasmBackend::new()),
Target::Wgsl => Box::new(crate::codegen::wgsl::WgslBackend::new()),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_target_from_str() {
use std::str::FromStr;
assert_eq!(Target::from_str("rust"), Ok(Target::Rust));
assert_eq!(Target::from_str("Rust"), Ok(Target::Rust));
assert_eq!(Target::from_str("RS"), Ok(Target::Rust));
assert_eq!(Target::from_str("go"), Ok(Target::Go));
assert_eq!(Target::from_str("golang"), Ok(Target::Go));
assert_eq!(Target::from_str("javascript"), Ok(Target::JavaScript));
assert_eq!(Target::from_str("JS"), Ok(Target::JavaScript));
assert_eq!(Target::from_str("wasm"), Ok(Target::WebAssembly));
assert_eq!(Target::from_str("webassembly"), Ok(Target::WebAssembly));
assert!(Target::from_str("unknown").is_err());
}
#[test]
fn test_target_extensions() {
assert_eq!(Target::Rust.file_extension(), "rs");
assert_eq!(Target::Go.file_extension(), "go");
assert_eq!(Target::JavaScript.file_extension(), "js");
assert_eq!(Target::WebAssembly.file_extension(), "wasm");
}
#[test]
fn test_codegen_config_defaults() {
let config = CodegenConfig::default();
assert_eq!(config.target, Target::Rust);
assert!(config.idiomatic_output);
assert!(config.pretty_print);
assert_eq!(config.optimization_level, 2);
}
}