dist_agent_lang 1.0.16

Hybrid programming with library and CLI support for Off/On-chain network integration
Documentation
//! Edge/IoT backend — transpile DAL services to Rust, build with cargo for edge/on-device.
//! Same pattern as native: DAL → Rust → cargo build. Use CARGO_BUILD_TARGET for embedded
//! (e.g. thumbv7em-none-eabihf). See docs/design/MOBILE_EDGE_IOT_TARGETS.md.

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

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()
}

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!(\"edge stub: {}; implement in Rust or use DAL interpreter\")",
            method_name
        );
    }
    "Default::default()".to_string()
}

fn services_to_rust(services: &[&ServiceStatement]) -> String {
    let mut out = String::new();
    out.push_str("// Generated by DAL edge/IoT backend. For embedded, set CARGO_BUILD_TARGET (e.g. thumbv7em-none-eabihf).\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(dal_type_to_rust)
                .unwrap_or_else(|| "()".to_string());
            let default_ret = default_return_expr(&ret, &method.name);
            out.push_str(&format!(
                "    /// Edge stub: replace with device logic or keep default.\n    pub fn {}({}) -> {} {{\n        {}\n    }}\n",
                method.name,
                params_str.join(", "),
                ret,
                default_ret
            ));
        }
        out.push_str("}\n\n");
    }
    out
}

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)
}

/// Edge/IoT backend: DAL → Rust → cargo build --release [--target $CARGO_BUILD_TARGET] → .rlib.
pub struct EdgeBackend;

impl CompileBackend for EdgeBackend {
    fn compile(
        &self,
        _program: &Program,
        services: &[&ServiceStatement],
        opts: &CompileOptions,
    ) -> Result<CompileArtifacts, CompileError> {
        if services.is_empty() {
            return Err(CompileError::Backend(
                "no services with @compile_target(\"edge\") or @compile_target(\"iot\") found; add at least one service marked for edge".to_string(),
            ));
        }
        if !check_rust_available() {
            return Err(CompileError::CompilerNotFound {
                target: "edge".to_string(),
                hint: "Install Rust: https://rustup.rs. For embedded: rustup target add thumbv7em-none-eabihf".to_string(),
            });
        }

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

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

        // Pin target dir to edge_build/target so artifacts are here even if cargo discovers a parent workspace.
        let build_target_dir_abs = std::fs::canonicalize(&build_dir)
            .map_err(CompileError::Io)?
            .join("target");
        let mut cmd = Command::new("cargo");
        cmd.current_dir(&build_dir)
            .arg("build")
            .arg("--release")
            .env("CARGO_TARGET_DIR", &build_target_dir_abs);
        if let Ok(t) = env::var("CARGO_BUILD_TARGET") {
            if !t.is_empty() {
                cmd.arg("--target").arg(&t);
            }
        }

        let output = cmd.output().map_err(CompileError::Io)?;

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

        let target_subdir = env::var("CARGO_BUILD_TARGET")
            .ok()
            .filter(|t| !t.is_empty());
        let target_dir = match &target_subdir {
            Some(t) => build_target_dir_abs.join(t).join("release"),
            None => build_target_dir_abs.join("release"),
        };

        let rlib_name = "libdal_edge.rlib";
        let built_rlib = target_dir.join(rlib_name);
        if !built_rlib.exists() {
            return Err(CompileError::Backend(format!(
                "cargo build succeeded but {} not found at {}; check cargo output for the actual artifact path",
                rlib_name,
                built_rlib.display()
            )));
        }
        let out_rlib = opts.output_dir.join(rlib_name);
        std::fs::copy(&built_rlib, &out_rlib).map_err(CompileError::Io)?;
        let artifact_paths = vec![out_rlib];

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

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