dist_agent_lang 1.0.12

Hybrid programming with library and CLI support for Off/On-chain network integration
Documentation
//! CT4: Native backend — transpile DAL services to Rust, build with cargo (host).
//! See docs/development/implementation/COMPILE_TARGET_IMPLEMENTATION_PLAN.md.

use crate::compile::{CompileArtifacts, CompileBackend, CompileError, CompileOptions};
use crate::parser::ast::{Program, ServiceStatement};
use std::process::Command;

/// Map DAL type to Rust type for native (full Rust types).
fn dal_type_to_rust(dal_type: &str) -> String {
    let t = dal_type.trim();
    if t.is_empty() || t == "int" {
        return "i64".to_string();
    }
    if t == "string" {
        return "String".to_string();
    }
    if t == "bool" {
        return "bool".to_string();
    }
    if t.starts_with("list<") || t.starts_with("vector<") {
        let inner = t
            .strip_prefix("list<")
            .or_else(|| t.strip_prefix("vector<"))
            .and_then(|s| s.strip_suffix('>'))
            .unwrap_or("i64");
        return format!("Vec<{}>", dal_type_to_rust(inner));
    }
    if t.starts_with("map<") {
        let inner = t
            .strip_prefix("map<")
            .and_then(|s| s.strip_suffix('>'))
            .unwrap_or("String, i64");
        let mut parts = inner.splitn(2, ',');
        let k = parts.next().unwrap_or("String").trim();
        let v = parts.next().unwrap_or("i64").trim();
        return format!(
            "std::collections::HashMap<{}, {}>",
            dal_type_to_rust(k),
            dal_type_to_rust(v)
        );
    }
    if t == "any" || t == "float" {
        return "f64".to_string();
    }
    t.to_string()
}

/// Return a default expression for a Rust type (for stub method bodies so generated code compiles and runs).
fn default_return_expr(ret: &str, method_name: &str) -> String {
    let r = ret.trim();
    if r == "()" {
        return "()".to_string();
    }
    if r == "bool" {
        return "false".to_string();
    }
    if r == "i64" || r == "i32" || r == "u64" || r == "u32" || r == "usize" {
        return "0".to_string();
    }
    if r == "f64" || r == "f32" {
        return "0.0".to_string();
    }
    if r == "String" {
        return "String::new()".to_string();
    }
    if r.starts_with("Vec<") {
        return "Default::default()".to_string();
    }
    if r.contains("HashMap") {
        return "Default::default()".to_string();
    }
    if r.starts_with("Option<") {
        return "None".to_string();
    }
    if r.starts_with("Result<") {
        return format!(
            "unimplemented!(\"native stub: {} not implemented; implement in Rust or use DAL interpreter\")",
            method_name
        );
    }
    "Default::default()".to_string()
}

/// Emit Rust lib.rs for one or more DAL services (rlib for host).
fn services_to_rust(services: &[&ServiceStatement]) -> String {
    let mut out = String::new();
    out.push_str("// Generated by DAL native backend. Compiles for host.\n");
    out.push_str("#![allow(dead_code)]\n\n");

    for service in services {
        let mod_name = service.name.to_lowercase().replace('-', "_");
        out.push_str(&format!("pub mod {} {{\n", mod_name));
        if service.fields.is_empty() {
            out.push_str("    pub struct State;\n");
        } else {
            out.push_str("    pub struct State {\n");
            for field in &service.fields {
                let rust_type = dal_type_to_rust(&field.field_type);
                out.push_str(&format!("        pub {}: {},\n", field.name, rust_type));
            }
            out.push_str("    }\n");
        }
        for method in &service.methods {
            let params_str: Vec<String> = method
                .parameters
                .iter()
                .map(|p| {
                    let ty = p.param_type.as_deref().unwrap_or("i64");
                    format!("{}: {}", p.name, dal_type_to_rust(ty))
                })
                .collect();
            let ret = method
                .return_type
                .as_deref()
                .map(|r| dal_type_to_rust(r))
                .unwrap_or_else(|| "()".to_string());
            let default_ret = default_return_expr(&ret, &method.name);
            out.push_str(&format!(
                "    /// Stub: replace with DAL-implemented logic or keep default return.\n    pub fn {}({}) -> {} {{\n        {}\n    }}\n",
                method.name,
                params_str.join(", "),
                ret,
                default_ret
            ));
        }
        out.push_str("}\n\n");
    }
    out
}

/// Check that cargo/rustc is available.
fn check_rust_available() -> bool {
    if let Some(available) = super::get_compiler_available_override() {
        return available;
    }
    Command::new("cargo")
        .arg("--version")
        .output()
        .map(|o| o.status.success())
        .unwrap_or(false)
}

/// Native backend: DAL → Rust → cargo build --release → .rlib (or binary).
pub struct NativeBackend;

impl CompileBackend for NativeBackend {
    fn compile(
        &self,
        _program: &Program,
        services: &[&ServiceStatement],
        opts: &CompileOptions,
    ) -> Result<CompileArtifacts, CompileError> {
        if !check_rust_available() {
            return Err(CompileError::CompilerNotFound {
                target: "native".to_string(),
                hint: "Install Rust: https://rustup.rs".to_string(),
            });
        }

        std::fs::create_dir_all(&opts.output_dir).map_err(CompileError::Io)?;
        let build_dir = opts.output_dir.join("native_build");
        let src_dir = build_dir.join("src");
        std::fs::create_dir_all(&src_dir).map_err(CompileError::Io)?;

        let rust_code = services_to_rust(services);
        let lib_path = src_dir.join("lib.rs");
        std::fs::write(&lib_path, rust_code).map_err(CompileError::Io)?;

        let cargo_toml = r#"
[package]
name = "dal_native"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["rlib"]
"#;
        std::fs::write(build_dir.join("Cargo.toml"), cargo_toml).map_err(CompileError::Io)?;

        let output = Command::new("cargo")
            .current_dir(&build_dir)
            .args(["build", "--release"])
            .output()
            .map_err(CompileError::Io)?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(CompileError::Backend(format!(
                "cargo native build failed: {}",
                stderr.trim()
            )));
        }

        let mut artifact_paths = Vec::new();
        let rlib_name = "libdal_native.rlib";
        let built_rlib = build_dir.join("target").join("release").join(rlib_name);
        if built_rlib.exists() {
            let out_rlib = opts.output_dir.join(rlib_name);
            std::fs::copy(&built_rlib, &out_rlib).map_err(CompileError::Io)?;
            artifact_paths.push(out_rlib);
        }

        let service_names: Vec<String> = services.iter().map(|s| s.name.clone()).collect();

        Ok(CompileArtifacts {
            target: "native".to_string(),
            service_names,
            artifact_paths,
            stub: false,
        })
    }
}