ts-native 0.1.2

A TypeScript to native executable compiler using Cranelift
use anyhow::{Result, bail};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::Path;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Extension {
    pub package: ExtensionPackage,
    pub functions: HashMap<String, ExternalFunction>,
    #[serde(default)]
    pub link: LinkConfig,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExtensionPackage {
    pub name: String,
    pub version: String,
    #[serde(default)]
    pub description: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExternalFunction {
    pub args: Vec<ArgType>,
    pub ret: RetType,
    pub impl_name: String,
    #[serde(default)]
    pub is_property: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum ArgType {
    Number,
    String,
    Boolean,
    Any,
    Array,
    Object,
    Function,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum RetType {
    Number,
    String,
    Boolean,
    Any,
    Array,
    Object,
    Void,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct LinkConfig {
    #[serde(default)]
    pub runtime: Option<String>,
    #[serde(default)]
    pub lib: Option<String>,
    #[serde(default)]
    pub libs: Vec<String>,
    #[serde(default)]
    pub flags: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CargoToml {
    #[serde(default)]
    pub dependencies: HashMap<String, toml::Value>,
}

#[derive(Debug, Clone)]
pub struct ExtensionRegistry {
    pub extensions: Vec<Extension>,
    pub function_map: HashMap<String, String>,
}

impl ExtensionRegistry {
    pub fn new() -> Self {
        Self {
            extensions: Vec::new(),
            function_map: HashMap::new(),
        }
    }
    
    pub fn register(&mut self, extension: Extension) {
        for (ts_name, func) in &extension.functions {
            self.function_map.insert(ts_name.clone(), func.impl_name.clone());
        }
        self.extensions.push(extension);
    }
    
    pub fn get_c_name(&self, ts_name: &str) -> Option<&String> {
        self.function_map.get(ts_name)
    }
    
    pub fn is_external_function(&self, ts_name: &str) -> bool {
        self.function_map.contains_key(ts_name)
    }
}

pub fn parse_manifest(path: &Path) -> Result<Extension> {
    let content = std::fs::read_to_string(path)?;
    let manifest: Extension = toml::from_str(&content)?;
    Ok(manifest)
}

pub fn parse_cargo_toml(path: &Path) -> Result<CargoToml> {
    let content = std::fs::read_to_string(path)?;
    let cargo: CargoToml = toml::from_str(&content)?;
    Ok(cargo)
}

pub fn find_ts_native_crates(cargo_toml: &CargoToml) -> Vec<String> {
    cargo_toml
        .dependencies
        .keys()
        .filter(|name| name.starts_with("ts-native-"))
        .cloned()
        .collect()
}

pub fn load_extension_from_path(path: &Path) -> Result<Extension> {
    let manifest_path = path.join("manifest.toml");
    if !manifest_path.exists() {
        bail!("manifest.toml not found in {:?}", path);
    }
    parse_manifest(&manifest_path)
}

pub fn load_extension_from_crate(crate_name: &str) -> Result<Extension> {
    let crate_path = find_crate_path(crate_name)?;
    load_extension_from_path(&crate_path)
}

fn find_crate_path(crate_name: &str) -> Result<std::path::PathBuf> {
    if let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") {
        let local_path = std::path::PathBuf::from(&manifest_dir)
            .join(crate_name);
        if local_path.join("manifest.toml").exists() {
            return Ok(local_path);
        }
    }
    
    let current_dir = std::env::current_dir()?;
    let local_path = current_dir.join(crate_name);
    if local_path.join("manifest.toml").exists() {
        return Ok(local_path);
    }
    
    let parent_dir = current_dir.parent().unwrap_or(&current_dir);
    let sibling_path = parent_dir.join(crate_name);
    if sibling_path.join("manifest.toml").exists() {
        return Ok(sibling_path);
    }
    
    bail!("Crate {} not found. Tried:\n  - ./{}/\n  - ../{}/", crate_name, crate_name, crate_name)
}

pub fn discover_extensions_from_cargo(cargo_toml_path: &Path) -> Result<ExtensionRegistry> {
    let mut registry = ExtensionRegistry::new();
    
    if !cargo_toml_path.exists() {
        return Ok(registry);
    }
    
    let cargo_toml = parse_cargo_toml(cargo_toml_path)?;
    let ts_native_crates = find_ts_native_crates(&cargo_toml);
    
    for crate_name in ts_native_crates {
        match load_extension_from_crate(&crate_name) {
            Ok(ext) => {
                println!("  加载扩展包: {} ({})", ext.package.name, crate_name);
                registry.register(ext);
            }
            Err(e) => {
                eprintln!("  ⚠️  加载扩展包 {} 失败: {}", crate_name, e);
            }
        }
    }
    
    Ok(registry)
}

#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_parse_manifest() {
        let manifest_content = r#"
[package]
name = "stdlib"
version = "0.1.0"

[functions]
"Math.sin" = { args = ["number"], ret = "number", impl_name = "js_math_sin" }

[link]
runtime = "runtime.c"
"#;
        let ext: Extension = toml::from_str(manifest_content).unwrap();
        assert_eq!(ext.package.name, "stdlib");
        assert!(ext.functions.contains_key("Math.sin"));
    }
}