use super::helpers::{capitalize, gen_param_conversion, rust_to_c_type};
use crate::core::ir::{MethodDef, TypeRef};
use heck::ToPascalCase;
pub(super) fn gen_trampoline(out: &mut String, trait_name: &str, trait_pascal: &str, method: &MethodDef) {
let export_name = format!("go{}{}", trait_pascal, method.name.to_pascal_case());
let mut params = vec!["userData unsafe.Pointer".to_string()];
for p in &method.params {
let c_type = rust_to_c_type(&p.ty);
params.push(format!("{} {}", p.name, c_type));
if matches!(p.ty, TypeRef::Bytes) {
params.push(format!("{}Len C.size_t", p.name));
}
}
let is_simple_primitive = matches!(
&method.return_type,
TypeRef::Primitive(crate::core::ir::PrimitiveType::Bool)
| TypeRef::Primitive(crate::core::ir::PrimitiveType::I32)
| TypeRef::Primitive(crate::core::ir::PrimitiveType::U32)
| TypeRef::Primitive(crate::core::ir::PrimitiveType::I64)
| TypeRef::Primitive(crate::core::ir::PrimitiveType::U64)
| TypeRef::Primitive(crate::core::ir::PrimitiveType::Usize)
| TypeRef::Primitive(crate::core::ir::PrimitiveType::Isize)
);
if is_simple_primitive {
params.push("outError **C.char".to_string());
} else if !matches!(method.return_type, TypeRef::Unit) {
params.push("outResult **C.char".to_string());
params.push("outError **C.char".to_string());
} else {
params.push("outError **C.char".to_string());
}
let go_return_type = if is_simple_primitive {
match &method.return_type {
TypeRef::Primitive(crate::core::ir::PrimitiveType::Bool) => "int32_t",
TypeRef::Primitive(crate::core::ir::PrimitiveType::I32) => "int32_t",
TypeRef::Primitive(crate::core::ir::PrimitiveType::U32) => "uint32_t",
TypeRef::Primitive(crate::core::ir::PrimitiveType::I64) => "int64_t",
TypeRef::Primitive(crate::core::ir::PrimitiveType::U64) => "uint64_t",
TypeRef::Primitive(crate::core::ir::PrimitiveType::Usize) => "uintptr_t",
TypeRef::Primitive(crate::core::ir::PrimitiveType::Isize) => "intptr_t",
_ => "int32_t",
}
} else {
"int32_t"
};
out.push_str(&crate::backends::go::template_env::render(
"trampoline_signature.jinja",
minijinja::context! {
name => export_name,
params => params,
return_type => go_return_type,
},
));
out.push('\n');
out.push_str("\thandle := cgo.Handle(uintptr(unsafe.Pointer(userData)))\n");
out.push_str(&crate::backends::go::template_env::render(
"handle_type_assertion.jinja",
minijinja::context! {
type_name => trait_name,
},
));
out.push('\n');
out.push_str("\tif !ok {\n");
out.push_str("\t\treturn 1 // error: invalid handle\n");
out.push_str("\t}\n");
out.push('\n');
for p in &method.params {
gen_param_conversion(out, p);
}
let mut call_args = Vec::new();
for p in &method.params {
call_args.push(format!("go{}", capitalize(&p.name)));
}
out.push_str("\t// Call the method\n");
if method.error_type.is_some() {
match &method.return_type {
TypeRef::Unit => {
out.push_str(&crate::backends::go::template_env::render(
"impl_method_call_err.jinja",
minijinja::context! {
method => method.name.to_pascal_case(),
args => call_args.join(", "),
},
));
out.push('\n');
}
_ => {
out.push_str(&crate::backends::go::template_env::render(
"impl_method_call_result_err.jinja",
minijinja::context! {
method => method.name.to_pascal_case(),
args => call_args.join(", "),
},
));
out.push('\n');
}
}
out.push_str("\tif err != nil {\n");
out.push_str("\t\tcErr := C.CString(err.Error())\n");
out.push_str("\t\t*outError = cErr\n");
out.push_str("\t\treturn 1\n");
out.push_str("\t}\n");
if !matches!(&method.return_type, TypeRef::Unit) {
gen_result_conversion(out, &method.return_type, is_simple_primitive);
}
} else {
out.push_str(&crate::backends::go::template_env::render(
"impl_method_call_result.jinja",
minijinja::context! {
method => method.name.to_pascal_case(),
args => call_args.join(", "),
},
));
out.push('\n');
if !matches!(&method.return_type, TypeRef::Unit) {
gen_result_conversion(out, &method.return_type, is_simple_primitive);
}
}
out.push_str("\treturn 0 // success\n");
out.push_str("}\n");
out.push('\n');
}
fn gen_result_conversion(out: &mut String, return_type: &TypeRef, is_simple_primitive: bool) {
if is_simple_primitive {
match return_type {
TypeRef::Primitive(crate::core::ir::PrimitiveType::Bool) => {
out.push_str("\tif result {\n");
out.push_str("\t\treturn 1\n");
out.push_str("\t}\n");
out.push_str("\treturn 0\n");
}
TypeRef::Primitive(p) => {
use crate::core::ir::PrimitiveType::*;
let c_type = match p {
Bool => "int32_t", U8 => "uint8_t",
U16 => "uint16_t",
U32 => "uint32_t",
U64 => "uint64_t",
I8 => "int8_t",
I16 => "int16_t",
I32 => "int32_t",
I64 => "int64_t",
F32 => "float",
F64 => "double",
Usize => "uintptr_t",
Isize => "intptr_t",
};
out.push_str(&format!("\treturn C.{}(result)\n", c_type));
}
_ => {
out.push_str("\treturn 0 // error: unexpected type for simple primitive path\n");
}
}
} else {
match return_type {
TypeRef::String | TypeRef::Char | TypeRef::Path => {
out.push_str("\tcResult := C.CString(result)\n");
out.push_str("\t*outResult = cResult\n");
}
TypeRef::Json => {
out.push_str("\tcResult := C.CString(string(result))\n");
out.push_str("\t*outResult = cResult\n");
}
_ => {
out.push_str("\tjsonBytes, _ := json.Marshal(result)\n");
out.push_str("\tcResult := C.CString(string(jsonBytes))\n");
out.push_str("\t*outResult = cResult\n");
}
}
}
}
pub(super) fn gen_plugin_trampolines(out: &mut String, trait_name: &str, trait_pascal: &str) {
out.push_str(&crate::backends::go::template_env::render(
"export_marker.jinja",
minijinja::context! {
name => format!("go{trait_pascal}Name"),
},
));
out.push_str(&crate::backends::go::template_env::render(
"plugin_method_trampoline_header.jinja",
minijinja::context! {
pascal => &trait_pascal,
method => "Name",
params => "userData unsafe.Pointer, outResult **C.char, outError **C.char",
},
));
out.push('\n');
out.push_str("\thandle := cgo.Handle(uintptr(unsafe.Pointer(userData)))\n");
out.push_str(&crate::backends::go::template_env::render(
"handle_type_assertion.jinja",
minijinja::context! {
type_name => trait_name,
},
));
out.push('\n');
out.push_str("\tif !ok {\n");
out.push_str("\t\treturn 1\n");
out.push_str("\t}\n");
out.push_str("\tname := impl.Name()\n");
out.push_str("\tcName := C.CString(name)\n");
out.push_str("\t*outResult = cName\n");
out.push_str("\treturn 0\n");
out.push_str("}\n");
out.push('\n');
out.push_str(&crate::backends::go::template_env::render(
"export_marker.jinja",
minijinja::context! {
name => format!("go{trait_pascal}Version"),
},
));
out.push_str(&crate::backends::go::template_env::render(
"plugin_method_trampoline_header.jinja",
minijinja::context! {
pascal => &trait_pascal,
method => "Version",
params => "userData unsafe.Pointer, outResult **C.char, outError **C.char",
},
));
out.push('\n');
out.push_str("\thandle := cgo.Handle(uintptr(unsafe.Pointer(userData)))\n");
out.push_str(&crate::backends::go::template_env::render(
"handle_type_assertion.jinja",
minijinja::context! {
type_name => trait_name,
},
));
out.push('\n');
out.push_str("\tif !ok {\n");
out.push_str("\t\treturn 1\n");
out.push_str("\t}\n");
out.push_str("\tversion := impl.Version()\n");
out.push_str("\tcVersion := C.CString(version)\n");
out.push_str("\t*outResult = cVersion\n");
out.push_str("\treturn 0\n");
out.push_str("}\n");
out.push('\n');
out.push_str(&crate::backends::go::template_env::render(
"export_marker.jinja",
minijinja::context! {
name => format!("go{trait_pascal}Initialize"),
},
));
out.push_str(&crate::backends::go::template_env::render(
"plugin_method_trampoline_header.jinja",
minijinja::context! {
pascal => &trait_pascal,
method => "Initialize",
params => "userData unsafe.Pointer, outError **C.char",
},
));
out.push('\n');
out.push_str("\thandle := cgo.Handle(uintptr(unsafe.Pointer(userData)))\n");
out.push_str(&crate::backends::go::template_env::render(
"handle_type_assertion.jinja",
minijinja::context! {
type_name => trait_name,
},
));
out.push('\n');
out.push_str("\tif !ok {\n");
out.push_str("\t\treturn 1\n");
out.push_str("\t}\n");
out.push_str("\terr := impl.Initialize()\n");
out.push_str("\tif err != nil {\n");
out.push_str("\t\tcErr := C.CString(err.Error())\n");
out.push_str("\t\t*outError = cErr\n");
out.push_str("\t\treturn 1\n");
out.push_str("\t}\n");
out.push_str("\treturn 0\n");
out.push_str("}\n");
out.push('\n');
out.push_str(&crate::backends::go::template_env::render(
"export_marker.jinja",
minijinja::context! {
name => format!("go{trait_pascal}Shutdown"),
},
));
out.push_str(&crate::backends::go::template_env::render(
"plugin_method_trampoline_header.jinja",
minijinja::context! {
pascal => &trait_pascal,
method => "Shutdown",
params => "userData unsafe.Pointer, outError **C.char",
},
));
out.push('\n');
out.push_str("\thandle := cgo.Handle(uintptr(unsafe.Pointer(userData)))\n");
out.push_str(&crate::backends::go::template_env::render(
"handle_type_assertion.jinja",
minijinja::context! {
type_name => trait_name,
},
));
out.push('\n');
out.push_str("\tif !ok {\n");
out.push_str("\t\treturn 1\n");
out.push_str("\t}\n");
out.push_str("\terr := impl.Shutdown()\n");
out.push_str("\tif err != nil {\n");
out.push_str("\t\tcErr := C.CString(err.Error())\n");
out.push_str("\t\t*outError = cErr\n");
out.push_str("\t\treturn 1\n");
out.push_str("\t}\n");
out.push_str("\treturn 0\n");
out.push_str("}\n");
out.push('\n');
out.push_str(&crate::backends::go::template_env::render(
"export_marker.jinja",
minijinja::context! {
name => format!("go{trait_pascal}FreeUserData"),
},
));
out.push_str(&crate::backends::go::template_env::render(
"plugin_free_user_data_func.jinja",
minijinja::context! {
pascal => &trait_pascal,
},
));
out.push('\n');
out.push_str("\t// No-op to avoid cleanup-queue panics. Handles cleaned in Unregister().\n");
out.push_str("}\n");
out.push('\n');
out.push_str(&crate::backends::go::template_env::render(
"export_marker.jinja",
minijinja::context! {
name => format!("go{trait_pascal}FreeString"),
},
));
out.push_str(&crate::backends::go::template_env::render(
"trait_free_string_func.jinja",
minijinja::context! {
trait_pascal => &trait_pascal,
},
));
}