use alef_core::config::{AdapterConfig, AlefConfig, Language};
pub fn generate_body(adapter: &AdapterConfig, language: Language, config: &AlefConfig) -> anyhow::Result<String> {
let body = match language {
Language::Python => gen_python_body(adapter, config),
Language::Node => gen_node_body(adapter, config),
Language::Ruby => gen_ruby_body(adapter, config),
Language::Php => gen_php_body(adapter, config),
Language::Elixir => gen_elixir_body(adapter, config),
Language::Wasm => gen_wasm_body(adapter, config),
Language::Ffi => gen_ffi_body(adapter, config),
Language::Go => gen_go_body(adapter, config),
Language::Java => gen_java_body(adapter, config),
Language::Csharp => gen_csharp_body(adapter, config),
Language::R => gen_r_body(adapter, config),
};
Ok(body)
}
fn call_args(adapter: &AdapterConfig) -> Vec<String> {
adapter
.params
.iter()
.map(|p| {
if p.optional {
format!("{}.map(Into::into)", p.name)
} else {
format!("{}.into()", p.name)
}
})
.collect()
}
fn gen_python_body(adapter: &AdapterConfig, _config: &AlefConfig) -> String {
let core_path = &adapter.core_path;
let returns = adapter.returns.as_deref().unwrap_or("()");
let gil_release = adapter.gil_release;
let args = call_args(adapter);
let call_str = args.join(", ");
if gil_release {
format!(
"py.allow_threads(|| {{\n \
{core_path}({call_str})\n \
.map({returns}::from)\n \
.map_err(|e| PyErr::new::<PyRuntimeError, _>(e.to_string()))\n \
}})"
)
} else {
format!(
"{core_path}({call_str})\n \
.map({returns}::from)\n \
.map_err(|e| PyErr::new::<PyRuntimeError, _>(e.to_string()))"
)
}
}
fn gen_node_body(adapter: &AdapterConfig, _config: &AlefConfig) -> String {
let core_path = &adapter.core_path;
let returns = adapter.returns.as_deref().unwrap_or("()");
let args = call_args(adapter);
let call_str = args.join(", ");
format!(
"{core_path}({call_str})\n \
.map({returns}::from)\n \
.map_err(|e| napi::Error::from_reason(e.to_string()))"
)
}
fn gen_ruby_body(adapter: &AdapterConfig, _config: &AlefConfig) -> String {
let core_path = &adapter.core_path;
let returns = adapter.returns.as_deref().unwrap_or("()");
let args = call_args(adapter);
let call_str = args.join(", ");
format!(
"{core_path}({call_str})\n \
.map({returns}::from)\n \
.map_err(|e| magnus::Error::new(magnus::exception::runtime_error(), e.to_string()))"
)
}
fn gen_php_body(adapter: &AdapterConfig, _config: &AlefConfig) -> String {
let core_path = &adapter.core_path;
let returns = adapter.returns.as_deref().unwrap_or("()");
let args = call_args(adapter);
let call_str = args.join(", ");
format!(
"{core_path}({call_str})\n \
.map({returns}::from)\n \
.map_err(|e| PhpException::default(e.to_string()))"
)
}
fn gen_elixir_body(adapter: &AdapterConfig, _config: &AlefConfig) -> String {
let core_path = &adapter.core_path;
let returns = adapter.returns.as_deref().unwrap_or("()");
let args = call_args(adapter);
let call_str = args.join(", ");
format!(
"{core_path}({call_str})\n \
.map({returns}::from)\n \
.map_err(|e| e.to_string())"
)
}
fn gen_wasm_body(adapter: &AdapterConfig, _config: &AlefConfig) -> String {
let core_path = &adapter.core_path;
let returns = adapter.returns.as_deref().unwrap_or("JsValue");
let args = call_args(adapter);
let call_str = args.join(", ");
format!(
"{core_path}({call_str})\n \
.map({returns}::from)\n \
.map_err(|e| JsValue::from_str(&e.to_string()))"
)
}
fn gen_ffi_body(adapter: &AdapterConfig, config: &AlefConfig) -> String {
let core_path = &adapter.core_path;
let prefix = config.ffi_prefix();
let name = &adapter.name;
let _ = prefix;
let _ = name;
let conversions: Vec<String> = adapter
.params
.iter()
.filter_map(|p| {
if p.ty == "String" || p.ty == "&str" {
Some(format!(
"let {name} = unsafe {{ std::ffi::CStr::from_ptr({name}) }}\n \
.to_str()\n \
.unwrap_or_default()\n \
.to_owned();",
name = p.name
))
} else {
None
}
})
.collect();
let call_args_list: Vec<String> = adapter.params.iter().map(|p| p.name.clone()).collect();
let call_str = call_args_list.join(", ");
let conversion_block = if conversions.is_empty() {
String::new()
} else {
format!("{}\n", conversions.join("\n"))
};
format!(
"{conversion_block}\
match {core_path}({call_str}) {{\n \
Ok(result) => {{\n \
let json = serde_json::to_string(&result).unwrap_or_default();\n \
std::ffi::CString::new(json).unwrap_or_default().into_raw()\n \
}}\n \
Err(e) => {{\n \
update_last_error(e);\n \
std::ptr::null_mut()\n \
}}\n \
}}"
)
}
fn gen_go_body(adapter: &AdapterConfig, config: &AlefConfig) -> String {
let name = &adapter.name;
let prefix = config.ffi_prefix();
let returns = adapter.returns.as_deref().unwrap_or("string");
let go_name = to_pascal_case(name);
let c_call_args: Vec<String> = adapter
.params
.iter()
.map(|p| {
if p.ty == "String" || p.ty == "&str" {
format!("C.CString({})", p.name)
} else {
format!("C.{}({})", rust_type_to_c_go(&p.ty), p.name)
}
})
.collect();
let call_str = c_call_args.join(", ");
let _ = go_name;
format!(
"result := C.{prefix}_{name}({call_str})\n \
if result == nil {{\n \
return nil, fmt.Errorf(\"%s\", lastError())\n \
}}\n \
defer C.free(unsafe.Pointer(result))\n \
var out {returns}\n \
if err := json.Unmarshal([]byte(C.GoString(result)), &out); err != nil {{\n \
return nil, err\n \
}}\n \
return &out, nil"
)
}
fn gen_java_body(adapter: &AdapterConfig, config: &AlefConfig) -> String {
let name = &adapter.name;
let prefix = config.ffi_prefix();
let arg_pass = if adapter.params.is_empty() {
String::new()
} else {
format!(
", {}",
adapter
.params
.iter()
.map(|p| {
if p.ty == "String" || p.ty == "&str" {
format!("arena.allocateFrom({})", p.name)
} else {
p.name.clone()
}
})
.collect::<Vec<_>>()
.join(", ")
)
};
format!(
"try (var arena = Arena.ofConfined()) {{\n\
\x20 var result = (MemorySegment) {prefix}_{name}.invokeExact(arena{arg_pass});\n\
\x20 if (result.equals(MemorySegment.NULL)) {{\n\
\x20 throw new RuntimeException(lastError());\n\
\x20 }}\n\
\x20 return result.getString(0);\n\
\x20 }}"
)
}
fn gen_csharp_body(adapter: &AdapterConfig, config: &AlefConfig) -> String {
let name = &adapter.name;
let prefix = config.ffi_prefix();
let call_args_list: Vec<String> = adapter.params.iter().map(|p| p.name.clone()).collect();
let call_str = call_args_list.join(", ");
format!(
"var ptr = {prefix}_{name}_native({call_str});\n\
\x20 if (ptr == IntPtr.Zero)\n\
\x20 throw new InvalidOperationException(GetLastError());\n\
\x20 try {{ return Marshal.PtrToStringUTF8(ptr)!; }}\n\
\x20 finally {{ FreeString(ptr); }}"
)
}
fn gen_r_body(adapter: &AdapterConfig, _config: &AlefConfig) -> String {
let core_path = &adapter.core_path;
let returns = adapter.returns.as_deref().unwrap_or("Robj");
let args = call_args(adapter);
let call_str = args.join(", ");
format!(
"{core_path}({call_str})\n \
.map({returns}::from)\n \
.map_err(|e| extendr_api::Error::Other(e.to_string()))"
)
}
fn to_pascal_case(s: &str) -> String {
s.split('_')
.map(|part| {
let mut chars = part.chars();
match chars.next() {
None => String::new(),
Some(first) => first.to_uppercase().to_string() + chars.as_str(),
}
})
.collect()
}
fn rust_type_to_c_go(ty: &str) -> &str {
match ty {
"bool" => "int",
"i32" => "int",
"i64" => "longlong",
"u32" => "uint",
"u64" => "ulonglong",
"f32" => "float",
"f64" => "double",
_ => "int",
}
}