use super::parser::*;
pub fn generate_api_file(kind: ApiKind, methods: &[MethodInfo], config: &CodegenConfig) -> String {
let mut code = String::with_capacity(16 * 1024);
code.push_str(&generate_api_header(kind));
code.push_str(&generate_api_struct_def(kind));
code.push_str(&generate_api_impl_block(kind, methods, config));
code
}
fn generate_api_header(kind: ApiKind) -> String {
let version = "v1alpha1";
let spi_mod = kind.spi_mod();
format!(
r#"/* generated by codegen */
use std::{{
ffi::{{CStr, CString}},
os::raw::c_char,
path::Path,
ptr::null_mut,
sync::atomic::{{AtomicBool, AtomicPtr, Ordering}},
}};
use libloading::Library;
use crate::{version}::bindings::*;
use crate::{version}::{spi_mod}::*;
"#
)
}
fn generate_api_struct_def(kind: ApiKind) -> String {
let api_name = kind.api_name();
let api_class = kind.api_class();
let spi_class = kind.spi_class();
format!(
r#"
#[derive(Debug)]
pub struct {api_name} {{
pub api_ptr: *mut {api_class},
pub spi_ptr: AtomicPtr<{spi_class}Ext>,
pub dynlib: Option<Library>,
pub released: AtomicBool,
}}
unsafe impl Sync for {api_name} {{}}
unsafe impl Send for {api_name} {{}}
"#
)
}
fn generate_api_impl_block(
kind: ApiKind,
methods: &[MethodInfo],
config: &CodegenConfig,
) -> String {
let api_name = kind.api_name();
let mut code = format!("impl {} {{\n", api_name);
for m in methods.iter().filter(|m| !m.is_static) {
code.push_str(&generate_api_method(kind, m, config));
}
code.push_str("}\n");
code
}
fn generate_api_method(kind: ApiKind, m: &MethodInfo, config: &CodegenConfig) -> String {
match &m.method_kind {
MethodKind::RegisterSpi => generate_register_spi(kind, m),
MethodKind::Release => generate_release(kind, m),
MethodKind::RegisterString => generate_register_string(kind, m),
MethodKind::Subscribe => generate_subscribe(kind, m),
MethodKind::General => generate_general(kind, m, config),
MethodKind::Static => String::new(),
}
}
fn generate_general(kind: ApiKind, m: &MethodInfo, config: &CodegenConfig) -> String {
let api_class = kind.api_class();
let cpp_name = &m.cpp_name;
let rust_name = &m.rust_name;
let comment = &m.comment;
let mut arg_parts: Vec<String> = vec!["&self".to_string()];
let mut call_parts: Vec<String> = vec!["self.api_ptr".to_string()];
for p in &m.params {
arg_parts.push(format!("{}: {}", p.rust_name, param_api_type(p, config)));
call_parts.push(param_api_call(p, config));
}
let args_text = arg_parts.join(", ");
let params_text = call_parts.join(", ");
let result_sig = return_type_api_sig(&m.return_type);
let body = match m.return_type {
ReturnType::CharPtr => format!(
r#"
unsafe {{
let cstr_ptr = ((*(*self.api_ptr).vtable_).{api_class}_{cpp_name})({params_text});
let c_str = CStr::from_ptr(cstr_ptr);
c_str.to_string_lossy().to_string()
}}"#
),
_ => format!(
r#"
unsafe {{
((*(*self.api_ptr).vtable_).{api_class}_{cpp_name})({params_text})
}}"#
),
};
format!(
r#" {comment}
pub fn {rust_name}({args_text}){result_sig}{{{body}
}}
"#
)
}
fn generate_register_spi(kind: ApiKind, m: &MethodInfo) -> String {
let api_class = kind.api_class();
let spi_class = kind.spi_class();
let spi_trait = kind.spi_name();
let arg_name = &m.params[0].rust_name;
let safety_doc = format!(
r#" /// # Safety
///
/// `{arg_name}` 必须指向一个通过 `Box::into_raw(Box::new(...))` 创建的有效 `dyn {spi_trait}` 对象。
/// 调用者需确保该 SPI 对象在以下时间段内保持有效(不被释放):
/// - 从调用 `register_spi` 开始,直到下一次调用 `register_spi`(届时旧 SPI 会被自动释放)
/// - 或直到所属的 API 对象被 Drop
///
/// 若 SPI 回调中持有 API 指针以实现双向调用,需确保 API 的生命周期长于 SPI。
/// 典型用法:通过 `Box::into_raw` 创建裸指针传入,由内部自动管理释放。"#
);
format!(
r#" {comment}
{safety_doc}
pub fn register_spi(&self, {arg_name}: *mut dyn {spi_trait}) {{
let spi_ptr = Box::into_raw(Box::new({spi_class}Ext::new({arg_name})));
let last_spi_ptr = self.spi_ptr.swap(spi_ptr, Ordering::AcqRel);
unsafe {{
((*(*self.api_ptr).vtable_).{api_class}_RegisterSpi)(self.api_ptr, spi_ptr as _)
}}
if !last_spi_ptr.is_null() {{
unsafe {{
let _ = Box::from_raw(last_spi_ptr); // 释放动态分配的内存
}}
}}
}}
"#,
comment = m.comment,
)
}
fn generate_release(kind: ApiKind, m: &MethodInfo) -> String {
let api_class = kind.api_class();
let comment = &m.comment;
format!(
r#" {comment}
pub fn release(&self) {{
// 只调用一次底层 Release
if !self.released.swap(true, Ordering::SeqCst) {{
unsafe {{
if !self.api_ptr.is_null() {{
((*(*self.api_ptr).vtable_).{api_class}_Release)(self.api_ptr);
}}
}}
}}
}}
"#
)
}
fn generate_register_string(kind: ApiKind, m: &MethodInfo) -> String {
let api_class = kind.api_class();
let cpp_name = &m.cpp_name;
let rust_name = &m.rust_name;
let comment = &m.comment;
let arg_name = &m.params[0].rust_name;
format!(
r#" {comment}
pub fn {rust_name}(&self, {arg_name}: &str) {{
unsafe {{
let {arg_name} = CString::new({arg_name}).unwrap();
((*(*self.api_ptr).vtable_).{api_class}_{cpp_name})(self.api_ptr, {arg_name}.as_ptr() as *mut i8)
}}
}}
"#
)
}
fn generate_subscribe(kind: ApiKind, m: &MethodInfo) -> String {
let api_class = kind.api_class();
let cpp_name = &m.cpp_name;
let rust_name = &m.rust_name;
let comment = &m.comment;
let arg_name = &m.params[0].rust_name;
let result_sig = return_type_api_sig(&m.return_type);
format!(
r#" {comment}
pub fn {rust_name}(&self, {arg_name}: &[impl AsRef<str>]){result_sig}{{
let cstrings: Vec<_> = {arg_name}.iter().map(|x| CString::new(x.as_ref()).unwrap()).collect();
let cstr_slice: Vec<*const i8> = cstrings.iter().map(|cstr| cstr.as_ptr()).collect();
unsafe {{
((*(*self.api_ptr).vtable_).{api_class}_{cpp_name})(self.api_ptr, cstr_slice.as_ptr() as *mut *mut i8, {arg_name}.len() as i32)
}}
}}
"#
)
}
pub fn generate_spi_file(kind: ApiKind, methods: &[MethodInfo], config: &CodegenConfig) -> String {
let mut code = String::with_capacity(16 * 1024);
code.push_str(&generate_spi_header(kind));
code.push_str(&generate_spi_trait(kind, methods, config));
code.push_str(&generate_vtable_struct(kind, methods));
code.push_str(&generate_static_table_and_ext(kind, methods));
code.push_str(&generate_extern_fns(kind, methods, config));
code
}
fn generate_spi_header(_kind: ApiKind) -> String {
let version = "v1alpha1";
format!(
r#"/* generated by codegen */
use crate::{version}::bindings::*;
"#
)
}
fn generate_spi_trait(kind: ApiKind, methods: &[MethodInfo], config: &CodegenConfig) -> String {
let spi_name = kind.spi_name();
let mut entries = String::new();
for m in methods {
let mut arg_parts: Vec<String> = vec!["&mut self".to_string()];
for p in &m.params {
arg_parts.push(format!("{}: {}", p.rust_name, param_trait_type(p, config)));
}
let comment = &m.comment;
let rust_name = &m.rust_name;
let arg_list = arg_parts.join(", ");
entries.push_str(&format!(
r#"
{comment}
fn {rust_name}({arg_list}) {{
}}"#
));
}
format!(
r#"
pub trait {spi_name}: Send {{{entries}
}}"#
)
}
fn generate_vtable_struct(kind: ApiKind, methods: &[MethodInfo]) -> String {
let spi_name = kind.spi_name();
let spi_class = kind.spi_class();
let mut fields = String::new();
for m in methods {
let rust_name = &m.rust_name;
let mut arg_parts: Vec<String> = vec![format!("spi: *mut {spi_class}Ext")];
for p in &m.params {
arg_parts.push(format!("{}: {}", p.rust_name, param_ffi_type(p)));
}
fields.push_str(&format!(
" {rust_name}: unsafe extern \"C\" fn({}),\n",
arg_parts.join(", ")
));
}
format!(
r#"
#[repr(C)]
#[derive(Debug)]
pub struct {spi_name}VTable {{
{fields}}}"#
)
}
fn generate_static_table_and_ext(kind: ApiKind, methods: &[MethodInfo]) -> String {
let spi_name = kind.spi_name();
let spi_class = kind.spi_class();
let mut members = String::new();
for m in methods {
let rust_name = &m.rust_name;
members.push_str(&format!(" {rust_name}: spi_{rust_name},\n"));
}
format!(
r#"
static SPI_VTABLE: {spi_name}VTable = {spi_name}VTable {{
{members}}};
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct {spi_class}Ext {{
vtable: *const {spi_name}VTable,
pub spi_ptr: *mut dyn {spi_name},
}}
impl {spi_class}Ext {{
pub fn new(spi: *mut dyn {spi_name}) -> Self {{
Self {{
vtable: &SPI_VTABLE,
spi_ptr: spi,
}}
}}
}}
"#
)
}
fn generate_extern_fns(kind: ApiKind, methods: &[MethodInfo], config: &CodegenConfig) -> String {
let spi_class = kind.spi_class();
let mut code = String::new();
for m in methods {
let rust_name = &m.rust_name;
let mut sig_parts: Vec<String> = vec![format!("spi: *mut {spi_class}Ext")];
for p in &m.params {
sig_parts.push(format!("{}: {}", p.rust_name, param_ffi_type(p)));
}
let call_parts: Vec<String> = m
.params
.iter()
.map(|p| param_ffi_to_trait(p, config))
.collect();
let call_args = call_parts.join(", ");
code.push_str(&format!(
r#"
extern "C" fn spi_{rust_name}({sig}) {{
unsafe {{
(*(*spi).spi_ptr).{rust_name}({call_args})
}}
}}"#,
sig = sig_parts.join(", "),
));
}
code
}
fn param_api_type(p: &ParamInfo, config: &CodegenConfig) -> String {
match &p.kind {
ParamKind::Int => "i32".into(),
ParamKind::Bool => "bool".into(),
ParamKind::Enum(name) => name.clone(),
ParamKind::CharPtr => "&str".into(),
ParamKind::StructPtr(name) => match config.api_pointer_style {
PointerStyle::RawConst => format!("*const {}", name),
PointerStyle::RawMut => format!("*mut {}", name),
PointerStyle::MutRef => format!("&mut {}", name),
PointerStyle::Ref => format!("&{}", name),
PointerStyle::OptionRef => format!("Option<&{}>", name),
},
ParamKind::IncompleteArray => "&[impl AsRef<str>]".into(),
ParamKind::ConstantArray(name) => name.clone(),
}
}
fn param_api_call(p: &ParamInfo, config: &CodegenConfig) -> String {
let name = &p.rust_name;
match &p.kind {
ParamKind::Int | ParamKind::Bool | ParamKind::Enum(_) => name.clone(),
ParamKind::StructPtr(type_name) => match config.api_pointer_style {
PointerStyle::RawConst => format!("{name} as *mut {type_name}"),
PointerStyle::RawMut => name.clone(),
PointerStyle::MutRef => format!("{name} as *mut {type_name}"),
PointerStyle::Ref => format!("{name} as *const {type_name} as *mut {type_name}"),
PointerStyle::OptionRef => format!("{name}.map_or(std::ptr::null_mut(), |p| p as *const {type_name} as *mut {type_name})"),
},
ParamKind::CharPtr => format!("CString::new({name}).unwrap().as_ptr() as *mut i8"),
ParamKind::ConstantArray(_) => format!("{name}.as_ptr() as *mut i8"),
ParamKind::IncompleteArray => {
panic!("IncompleteArray 应由 generate_subscribe 处理")
}
}
}
fn param_trait_type(p: &ParamInfo, config: &CodegenConfig) -> String {
match &p.kind {
ParamKind::Int => "i32".into(),
ParamKind::Bool => "bool".into(),
ParamKind::Enum(name) => name.clone(),
ParamKind::StructPtr(name) => match config.spi_pointer_style {
PointerStyle::RawConst => format!("*const {}", name),
PointerStyle::RawMut => format!("*mut {}", name),
PointerStyle::MutRef => format!("&mut {}", name),
PointerStyle::Ref => format!("&{}", name),
PointerStyle::OptionRef => format!("Option<&{}>", name),
},
other => panic!("SPI 方法不应有此参数类型: {:?}", other),
}
}
fn param_ffi_type(p: &ParamInfo) -> String {
match &p.kind {
ParamKind::Int => "std::os::raw::c_int".into(),
ParamKind::Bool => "bool".into(),
ParamKind::Enum(name) => name.clone(),
ParamKind::StructPtr(name) => format!("*const {}", name),
other => panic!("SPI 方法不应有此参数类型: {:?}", other),
}
}
fn param_ffi_to_trait(p: &ParamInfo, config: &CodegenConfig) -> String {
match &p.kind {
ParamKind::StructPtr(_) => match config.spi_pointer_style {
PointerStyle::RawConst => p.rust_name.clone(),
PointerStyle::RawMut => format!("{} as *mut _", p.rust_name),
PointerStyle::MutRef => format!("&mut *{}", p.rust_name),
PointerStyle::Ref => format!("&*{}", p.rust_name),
PointerStyle::OptionRef => format!("{}.as_ref()", p.rust_name),
},
_ => p.rust_name.clone(),
}
}
fn return_type_api_sig(rt: &ReturnType) -> String {
match rt {
ReturnType::Void => " ".into(),
ReturnType::Int => " -> i32 ".into(),
ReturnType::CharPtr => " -> String ".into(),
}
}