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));
}
}
if !matches!(method.return_type, TypeRef::Unit) {
params.push("outResult **C.char".to_string());
}
params.push("outError **C.char".to_string());
out.push_str(&crate::backends::go::template_env::render(
"trampoline_signature.jinja",
minijinja::context! {
name => export_name,
params => params,
},
));
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);
}
} 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);
}
}
out.push_str("\treturn 0 // success\n");
out.push_str("}\n");
out.push('\n');
}
fn gen_result_conversion(out: &mut String, return_type: &TypeRef) {
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,
},
));
}