mod cinterop_def;
mod native_build_gradle;
mod native_types;
use alef_codegen::c_consumer;
use alef_core::backend::GeneratedFile;
use alef_core::config::ResolvedCrateConfig;
use alef_core::ir::{ApiSurface, FunctionDef, ParamDef, TypeRef};
use std::collections::BTreeSet;
use std::path::PathBuf;
use crate::gen_bindings::{to_lower_camel, to_pascal_case};
pub fn emit(api: &ApiSurface, config: &ResolvedCrateConfig) -> anyhow::Result<Vec<GeneratedFile>> {
let kt = emit_kotlin_source(api, config);
let def = cinterop_def::emit_def_file(config);
let gradle = native_build_gradle::emit_gradle_build(config);
let package = config.kotlin_package();
let package_path = package.replace('.', "/");
let module_name = to_pascal_case(&config.name);
let crate_name = &config.name;
let native_root = "packages/kotlin-native".to_string();
let kt_path = PathBuf::from(&native_root)
.join("src/nativeMain/kotlin")
.join(&package_path)
.join(format!("{module_name}.kt"));
let def_path = PathBuf::from(&native_root).join(format!("{crate_name}.def"));
let gradle_path = PathBuf::from(&native_root).join("build.gradle.kts");
Ok(vec![
GeneratedFile {
path: kt_path,
content: kt,
generated_header: false,
},
GeneratedFile {
path: def_path,
content: def,
generated_header: false,
},
GeneratedFile {
path: gradle_path,
content: gradle,
generated_header: false,
},
])
}
fn emit_kotlin_source(api: &ApiSurface, config: &ResolvedCrateConfig) -> String {
let package = config.kotlin_package();
let module_name = to_pascal_case(&config.name);
let prefix = config.ffi_prefix();
let crate_name = &config.name;
let exclude_functions: std::collections::HashSet<&str> = config
.kotlin
.as_ref()
.map(|c| c.exclude_functions.iter().map(String::as_str).collect())
.unwrap_or_default();
let exclude_types: std::collections::HashSet<&str> = config
.kotlin
.as_ref()
.map(|c| c.exclude_types.iter().map(String::as_str).collect())
.unwrap_or_default();
let mut imports: BTreeSet<String> = BTreeSet::new();
imports.insert("import kotlinx.cinterop.*".to_string());
imports.insert(format!("import {crate_name}.*"));
let mut body = String::new();
for ty in api.types.iter().filter(|t| !exclude_types.contains(t.name.as_str())) {
native_types::emit_native_type(ty, &mut body);
body.push('\n');
}
for en in api.enums.iter().filter(|e| !exclude_types.contains(e.name.as_str())) {
native_types::emit_native_enum(en, &mut body);
body.push('\n');
}
for error in &api.errors {
native_types::emit_native_error(error, &mut body);
body.push('\n');
}
let visible_functions: Vec<&alef_core::ir::FunctionDef> = api
.functions
.iter()
.filter(|f| !exclude_functions.contains(f.name.as_str()))
.collect();
if !visible_functions.is_empty() {
body.push_str(&crate::template_env::render(
"object_declaration.jinja",
minijinja::context! {
name => module_name,
},
));
for f in &visible_functions {
emit_native_function(f, &prefix, &mut body);
body.push('\n');
}
body.push_str("}\n");
}
let mut content = String::new();
content.push_str("// Generated by alef. Do not edit by hand.\n\n");
content.push_str(&crate::template_env::render(
"package_declaration.jinja",
minijinja::context! {
package => package,
},
));
content.push_str("\n\n");
for import in &imports {
content.push_str(import);
content.push('\n');
}
content.push('\n');
content.push_str(&body);
content
}
pub(crate) fn emit_native_function_pub(f: &FunctionDef, prefix: &str, out: &mut String) {
emit_native_function(f, prefix, out)
}
fn emit_native_function(f: &FunctionDef, prefix: &str, out: &mut String) {
if !f.doc.is_empty() {
let doc_lines: Vec<String> = f.doc.lines().map(ToString::to_string).collect();
out.push_str(&crate::template_env::render(
"doc_comment.jinja",
minijinja::context! {
indent => " ",
lines => doc_lines,
},
));
}
let params: Vec<String> = f.params.iter().map(format_native_param).collect();
let return_ty = native_return_type_str(&f.return_type);
let func_name_camel = to_lower_camel(&f.name);
let error_code_sym = c_consumer::last_error_code_symbol(prefix);
let error_context_sym = c_consumer::last_error_context_symbol(prefix);
let free_sym = c_consumer::free_string_symbol(prefix);
let c_fn = format!("{prefix}_{}", f.name);
out.push_str(&crate::template_env::render(
"native_function_header.jinja",
minijinja::context! {
name => func_name_camel,
params => params.join(", "),
return_type => return_ty,
},
));
out.push_str(" return memScoped {\n");
for p in &f.params {
emit_native_param_conversion(p, out);
}
let c_args: Vec<String> = f.params.iter().map(native_c_arg).collect();
let call = format!("{c_fn}({})", c_args.join(", "));
if f.error_type.is_some() {
out.push_str(&crate::template_env::render(
"native_result_assign.jinja",
minijinja::context! {
call => call,
},
));
out.push_str(&crate::template_env::render(
"native_error_code_check.jinja",
minijinja::context! {
error_code_sym => error_code_sym,
},
));
out.push_str(" if (_code != 0) {\n");
out.push_str(&crate::template_env::render(
"native_error_message.jinja",
minijinja::context! {
error_context_sym => error_context_sym,
},
));
out.push_str(" throw RuntimeException(_msg)\n");
out.push_str(" }\n");
if matches!(f.return_type, TypeRef::Unit) {
out.push_str(" Unit\n");
} else {
let expr = native_unwrap_return("_result", &f.return_type, &free_sym);
out.push_str(&crate::template_env::render(
"native_return_expr.jinja",
minijinja::context! {
expr => expr,
},
));
}
} else if matches!(f.return_type, TypeRef::Unit) {
out.push_str(&crate::template_env::render(
"native_call_only.jinja",
minijinja::context! {
call => call,
},
));
out.push_str(" Unit\n");
} else {
out.push_str(&crate::template_env::render(
"native_result_assign.jinja",
minijinja::context! {
call => call,
},
));
let expr = native_unwrap_return("_result", &f.return_type, &free_sym);
out.push_str(&crate::template_env::render(
"native_return_expr.jinja",
minijinja::context! {
expr => expr,
},
));
}
out.push_str(" }\n");
out.push_str(" }\n");
}
fn format_native_param(p: &ParamDef) -> String {
let ty_str = native_param_type_str(&p.ty, p.optional);
format!("{}: {}", to_lower_camel(&p.name), ty_str)
}
fn native_param_type_str(ty: &TypeRef, optional: bool) -> String {
let inner = match ty {
TypeRef::String | TypeRef::Path | TypeRef::Char | TypeRef::Json | TypeRef::Vec(_) | TypeRef::Map(_, _) => {
"String".to_string()
}
TypeRef::Bytes => "ByteArray".to_string(),
TypeRef::Optional(inner) => return format!("{}?", native_param_type_str(inner, false)),
other => native_type_str(other, false),
};
if optional { format!("{inner}?") } else { inner }
}
fn emit_native_param_conversion(p: &ParamDef, out: &mut String) {
let name = to_lower_camel(&p.name);
match &p.ty {
TypeRef::String | TypeRef::Path | TypeRef::Char | TypeRef::Json | TypeRef::Vec(_) | TypeRef::Map(_, _) => {
out.push_str(&crate::template_env::render(
"native_param_cstr_conversion.jinja",
minijinja::context! {
name => &name,
},
));
}
TypeRef::Bytes => {
out.push_str(&crate::template_env::render(
"native_param_bytes_conversion.jinja",
minijinja::context! {
name => &name,
},
));
}
_ => {}
}
}
fn native_c_arg(p: &ParamDef) -> String {
let name = to_lower_camel(&p.name);
match &p.ty {
TypeRef::String | TypeRef::Path | TypeRef::Char | TypeRef::Json | TypeRef::Vec(_) | TypeRef::Map(_, _) => {
format!("{name}C")
}
TypeRef::Bytes => format!("{name}Pin.addressOf(0), {name}.size"),
_ => name,
}
}
fn native_unwrap_return(raw: &str, ty: &TypeRef, free_sym: &str) -> String {
match ty {
TypeRef::String | TypeRef::Path | TypeRef::Char | TypeRef::Json | TypeRef::Vec(_) | TypeRef::Map(_, _) => {
format!("run {{ val _s = {raw}!!.toKString(); {free_sym}({raw}); _s }}")
}
TypeRef::Bytes => {
format!("run {{ val _s = {raw}!!.toKString().encodeToByteArray(); {free_sym}({raw}); _s }}")
}
_ => raw.to_string(),
}
}
fn native_return_type_str(ty: &TypeRef) -> String {
match ty {
TypeRef::Unit => "Unit".to_string(),
TypeRef::String | TypeRef::Path | TypeRef::Char | TypeRef::Json | TypeRef::Vec(_) | TypeRef::Map(_, _) => {
"String".to_string()
}
TypeRef::Bytes => "ByteArray".to_string(),
other => native_type_str(other, false),
}
}
pub(crate) fn native_type_str(ty: &TypeRef, optional: bool) -> String {
use alef_core::ir::PrimitiveType;
let inner = match ty {
TypeRef::Primitive(p) => match p {
PrimitiveType::Bool => "Boolean".to_string(),
PrimitiveType::U8 | PrimitiveType::I8 => "Byte".to_string(),
PrimitiveType::U16 | PrimitiveType::I16 => "Short".to_string(),
PrimitiveType::U32 | PrimitiveType::I32 => "Int".to_string(),
PrimitiveType::U64 | PrimitiveType::I64 | PrimitiveType::Usize | PrimitiveType::Isize => "Long".to_string(),
PrimitiveType::F32 => "Float".to_string(),
PrimitiveType::F64 => "Double".to_string(),
},
TypeRef::String | TypeRef::Json => "String".to_string(),
TypeRef::Path => "String".to_string(),
TypeRef::Char => "Char".to_string(),
TypeRef::Bytes => "ByteArray".to_string(),
TypeRef::Unit => "Unit".to_string(),
TypeRef::Duration => "Long".to_string(),
TypeRef::Named(name) => name.clone(),
TypeRef::Optional(inner) => return format!("{}?", native_type_str(inner, false)),
TypeRef::Vec(inner) => format!("List<{}>", native_type_str(inner, false)),
TypeRef::Map(k, v) => format!("Map<{}, {}>", native_type_str(k, false), native_type_str(v, false)),
};
if optional { format!("{inner}?") } else { inner }
}