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)
}
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)?;
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,
})
}
}