dist_agent_lang 1.0.22

Agentic programming with library and CLI support for Off/On-chain network integration
Documentation
//! CT3: WebAssembly backend — transpile DAL services to Rust, build with cargo wasm32.
//! 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 WASM (simple types for cdylib ABI).
fn dal_type_to_rust(dal_type: &str) -> String {
    let t = dal_type.trim();
    if t.is_empty() || t == "int" {
        return "i32".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("i32");
        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, i32");
        let mut parts = inner.splitn(2, ',');
        let k = parts.next().unwrap_or("String").trim();
        let v = parts.next().unwrap_or("i32").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()
}

/// Emit Rust lib.rs content for one or more DAL services (cdylib for wasm32).
fn services_to_rust(services: &[&ServiceStatement]) -> String {
    let mut out = String::new();
    out.push_str("// Generated by DAL wasm backend. Compiles to wasm32-unknown-unknown.\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 {
            // extern "C" ABI for wasm32: i32 params/return so cdylib compiles without wasm-bindgen
            let params_str: Vec<String> = method
                .parameters
                .iter()
                .map(|p| format!("{}: i32", p.name))
                .collect();
            let fn_name = format!("{}_{}", &mod_name, method.name);
            out.push_str(&format!(
                "    #[no_mangle]\n    pub extern \"C\" fn {}({}) -> i32 {{\n        0i32\n    }}\n",
                fn_name,
                params_str.join(", ")
            ));
        }
        out.push_str("}\n\n");
    }
    out
}

/// Check that rustc can build for wasm32 (target installed).
fn check_wasm_target_available() -> bool {
    if let Some(available) = super::get_compiler_available_override() {
        return available;
    }
    let out = Command::new("rustc")
        .args(["--print", "target-list"])
        .output();
    match out {
        Ok(o) if o.status.success() => {
            let list = String::from_utf8_lossy(&o.stdout);
            list.contains("wasm32-unknown-unknown")
        }
        _ => false,
    }
}

/// WebAssembly backend: DAL → Rust → cargo build --target wasm32-unknown-unknown → .wasm
pub struct WasmBackend;

impl CompileBackend for WasmBackend {
    fn compile(
        &self,
        _program: &Program,
        services: &[&ServiceStatement],
        opts: &CompileOptions,
    ) -> Result<CompileArtifacts, CompileError> {
        if !check_wasm_target_available() {
            return Err(CompileError::CompilerNotFound {
                target: "wasm".to_string(),
                hint: "Install wasm32 target: rustup target add wasm32-unknown-unknown".to_string(),
            });
        }

        std::fs::create_dir_all(&opts.output_dir).map_err(CompileError::Io)?;
        let build_dir = opts.output_dir.join("wasm_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_wasm"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]
"#;
        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", "--target", "wasm32-unknown-unknown", "--release"])
            .output()
            .map_err(CompileError::Io)?;

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

        let wasm_name = "dal_wasm.wasm";
        let built_wasm = build_dir
            .join("target")
            .join("wasm32-unknown-unknown")
            .join("release")
            .join(wasm_name);
        let mut artifact_paths = Vec::new();
        if built_wasm.exists() {
            let out_wasm = opts.output_dir.join(wasm_name);
            std::fs::copy(&built_wasm, &out_wasm).map_err(CompileError::Io)?;
            artifact_paths.push(out_wasm);
        }

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

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