use crate::type_map::java_ffi_type;
use ahash::AHashSet;
use alef_core::ir::{PrimitiveType, TypeRef};
use heck::ToSnakeCase;
use std::fmt::Write;
pub(crate) fn is_ffi_string_return(ty: &TypeRef) -> bool {
match ty {
TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => true,
TypeRef::Optional(inner) => matches!(
inner.as_ref(),
TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json
),
_ => false,
}
}
pub(crate) fn java_ffi_return_cast(ty: &TypeRef) -> &'static str {
match ty {
TypeRef::Primitive(prim) => match prim {
PrimitiveType::Bool => "boolean",
PrimitiveType::U8 | PrimitiveType::I8 => "byte",
PrimitiveType::U16 | PrimitiveType::I16 => "short",
PrimitiveType::U32 | PrimitiveType::I32 => "int",
PrimitiveType::U64 | PrimitiveType::I64 | PrimitiveType::Usize | PrimitiveType::Isize => "long",
PrimitiveType::F32 => "float",
PrimitiveType::F64 => "double",
},
TypeRef::Duration => "long",
_ => "MemorySegment",
}
}
pub(crate) fn gen_ffi_layout(ty: &TypeRef) -> String {
match ty {
TypeRef::Primitive(prim) => java_ffi_type(prim).to_string(),
TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => "ValueLayout.ADDRESS".to_string(),
TypeRef::Bytes => "ValueLayout.ADDRESS".to_string(),
TypeRef::Optional(inner) => gen_ffi_layout(inner),
TypeRef::Vec(_) => "ValueLayout.ADDRESS".to_string(),
TypeRef::Map(_, _) => "ValueLayout.ADDRESS".to_string(),
TypeRef::Named(_) => "ValueLayout.ADDRESS".to_string(),
TypeRef::Unit => "".to_string(),
TypeRef::Duration => "ValueLayout.JAVA_LONG".to_string(),
}
}
pub(crate) fn marshal_param_to_ffi(
out: &mut String,
name: &str,
ty: &TypeRef,
opaque_types: &AHashSet<String>,
prefix: &str,
) {
match ty {
TypeRef::String | TypeRef::Char | TypeRef::Json => {
let cname = "c".to_string() + name;
writeln!(out, " var {} = arena.allocateFrom({});", cname, name).ok();
}
TypeRef::Path => {
let cname = "c".to_string() + name;
writeln!(
out,
" var {} = arena.allocateFrom({}.toString());",
cname, name
)
.ok();
}
TypeRef::Bytes => {
let cname = "c".to_string() + name;
writeln!(
out,
" var {} = arena.allocateFrom(ValueLayout.JAVA_BYTE, {});",
cname, name
)
.ok();
}
TypeRef::Named(type_name) => {
let cname = "c".to_string() + name;
if opaque_types.contains(type_name.as_str()) {
writeln!(out, " var {} = {}.handle();", cname, name).ok();
} else {
let type_snake = type_name.to_snake_case();
let from_json_handle = format!(
"NativeLib.{}_{}_FROM_JSON",
prefix.to_uppercase(),
type_snake.to_uppercase()
);
let _free_handle = format!("NativeLib.{}_{}_FREE", prefix.to_uppercase(), type_snake.to_uppercase());
writeln!(
out,
" var {}Json = {} != null ? MAPPER.writeValueAsString({}) : null;",
cname, name, name
)
.ok();
writeln!(
out,
" var {}JsonSeg = {}Json != null ? arena.allocateFrom({}Json) : MemorySegment.NULL;",
cname, cname, cname
)
.ok();
writeln!(out, " var {} = {}Json != null", cname, cname).ok();
writeln!(
out,
" ? (MemorySegment) {}.invoke({}JsonSeg)",
from_json_handle, cname
)
.ok();
writeln!(out, " : MemorySegment.NULL;").ok();
}
}
TypeRef::Optional(inner) => {
match inner.as_ref() {
TypeRef::String | TypeRef::Char | TypeRef::Json => {
let cname = "c".to_string() + name;
writeln!(
out,
" var {} = {} != null ? arena.allocateFrom({}) : MemorySegment.NULL;",
cname, name, name
)
.ok();
}
TypeRef::Path => {
let cname = "c".to_string() + name;
writeln!(
out,
" var {} = {} != null ? arena.allocateFrom({}.toString()) : MemorySegment.NULL;",
cname, name, name
)
.ok();
}
TypeRef::Named(type_name) => {
let cname = "c".to_string() + name;
if opaque_types.contains(type_name.as_str()) {
writeln!(
out,
" var {} = {} != null ? {}.handle() : MemorySegment.NULL;",
cname, name, name
)
.ok();
} else {
let type_snake = type_name.to_snake_case();
let from_json_handle = format!(
"NativeLib.{}_{}_FROM_JSON",
prefix.to_uppercase(),
type_snake.to_uppercase()
);
writeln!(
out,
" var {}Json = {} != null ? MAPPER.writeValueAsString({}) : null;",
cname, name, name
)
.ok();
writeln!(out, " var {}JsonSeg = {}Json != null ? arena.allocateFrom({}Json) : MemorySegment.NULL;", cname, cname, cname).ok();
writeln!(out, " var {} = {}Json != null", cname, cname).ok();
writeln!(
out,
" ? (MemorySegment) {}.invoke({}JsonSeg)",
from_json_handle, cname
)
.ok();
writeln!(out, " : MemorySegment.NULL;").ok();
}
}
_ => {
}
}
}
TypeRef::Vec(_) | TypeRef::Map(_, _) => {
let cname = "c".to_string() + name;
writeln!(
out,
" var {}Json = MAPPER.writeValueAsString({});",
cname, name
)
.ok();
writeln!(out, " var {} = arena.allocateFrom({}Json);", cname, cname).ok();
}
_ => {
}
}
}
pub(crate) fn ffi_param_name(name: &str, ty: &TypeRef, _opaque_types: &AHashSet<String>) -> String {
match ty {
TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json | TypeRef::Bytes => "c".to_string() + name,
TypeRef::Named(_) => "c".to_string() + name,
TypeRef::Vec(_) | TypeRef::Map(_, _) => "c".to_string() + name,
TypeRef::Optional(inner) => match inner.as_ref() {
TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json | TypeRef::Named(_) => {
"c".to_string() + name
}
_ => name.to_string(),
},
_ => name.to_string(),
}
}
pub(crate) fn gen_function_descriptor(return_layout: &str, param_layouts: &[String]) -> String {
if return_layout.is_empty() {
if param_layouts.is_empty() {
"FunctionDescriptor.ofVoid()".to_string()
} else {
format!("FunctionDescriptor.ofVoid({})", param_layouts.join(", "))
}
} else {
if param_layouts.is_empty() {
format!("FunctionDescriptor.of({})", return_layout)
} else {
format!("FunctionDescriptor.of({}, {})", return_layout, param_layouts.join(", "))
}
}
}
pub(crate) fn gen_helper_methods(out: &mut String, prefix: &str, class_name: &str) {
let needs_check_last_error = out.contains("checkLastError()");
let needs_read_cstring = out.contains("readCString(");
let needs_read_bytes = out.contains("readBytes(");
let needs_read_json_list = out.contains("readJsonList(");
let needs_create_object_mapper = out.contains("MAPPER.") || needs_read_json_list;
if !needs_check_last_error
&& !needs_read_cstring
&& !needs_read_bytes
&& !needs_read_json_list
&& !needs_create_object_mapper
{
return;
}
writeln!(out, " // Helper methods for FFI marshalling").ok();
writeln!(out).ok();
if needs_check_last_error {
out.push_str(&crate::template_env::render(
"helper_check_last_error.jinja",
minijinja::context! {
prefix_upper => prefix.to_uppercase(),
class_name => class_name,
},
));
}
if needs_create_object_mapper {
out.push_str(&crate::template_env::render(
"helper_object_mapper.jinja",
minijinja::context! {},
));
}
if needs_read_cstring {
out.push_str(&crate::template_env::render(
"helper_read_cstring.jinja",
minijinja::context! {},
));
}
if needs_read_bytes {
out.push_str(&crate::template_env::render(
"helper_read_bytes.jinja",
minijinja::context! {},
));
}
if needs_read_json_list {
let free_handle = format!("NativeLib.{}_FREE_STRING", prefix.to_uppercase());
out.push_str(&crate::template_env::render(
"helper_read_json_list.jinja",
minijinja::context! {
class_name => class_name,
free_handle => free_handle,
},
));
}
}