use std::{fs, path::Path};
use quote::ToTokens;
use syn::{FnArg, ForeignItem, Item, Pat, PathArguments, ReturnType, Type};
pub fn generate(bindings: &bindgen::Bindings, out_path: &Path) {
let source = bindings_to_string(bindings);
let syntax = syn::parse_file(&source).expect("failed to parse generated bindings");
let mut declarations = Vec::new();
for item in syntax.items {
let Item::ForeignMod(foreign_mod) = item else {
continue;
};
for item in foreign_mod.items {
let ForeignItem::Fn(function) = item else {
continue;
};
let name = function.sig.ident.to_string();
if name.starts_with("aic_") {
declarations.push(render_symbol_declaration(&function));
}
}
}
assert!(
!declarations.is_empty(),
"bindgen did not emit any aic_* function declarations"
);
declarations.push("fn aic_set_sdk_wrapper_id(id: u32);".to_string());
let mut output =
String::from("// @generated by aic-sdk-sys/build-utils/runtime_linking.rs\n\n");
output.push_str("aic_symbols! {\n");
for declaration in declarations {
output.push_str(" ");
output.push_str(&declaration);
output.push('\n');
}
output.push_str("}\n");
fs::write(out_path, output).expect("failed to write runtime-linking symbols");
}
fn bindings_to_string(bindings: &bindgen::Bindings) -> String {
let mut bytes = Vec::new();
bindings
.write(Box::new(&mut bytes))
.expect("failed to render generated bindings");
String::from_utf8(bytes).expect("bindgen emitted non-UTF-8 bindings")
}
fn render_symbol_declaration(function: &syn::ForeignItemFn) -> String {
let name = &function.sig.ident;
let args = function
.sig
.inputs
.iter()
.enumerate()
.map(render_arg)
.collect::<Vec<_>>()
.join(", ");
let output = match &function.sig.output {
ReturnType::Default => String::new(),
ReturnType::Type(_, ty) => format!(" -> {}", render_type(ty)),
};
format!("fn {name}({args}){output};")
}
fn render_arg((index, arg): (usize, &FnArg)) -> String {
let FnArg::Typed(arg) = arg else {
panic!("bindgen emitted a method receiver in a foreign function")
};
let name = match arg.pat.as_ref() {
Pat::Ident(ident) => ident.ident.to_string(),
_ => format!("arg_{index}"),
};
let ty = render_type(&arg.ty);
format!("{name}: {ty}")
}
fn render_type(ty: &Type) -> String {
match ty {
Type::Path(ty) => render_path(&ty.path),
Type::Ptr(ty) => {
let mutability = if ty.const_token.is_some() {
"const"
} else {
"mut"
};
format!("*{mutability} {}", render_type(&ty.elem))
}
_ => render_tokens(ty),
}
}
fn render_path(path: &syn::Path) -> String {
let mut rendered = String::new();
if path.leading_colon.is_some() {
rendered.push_str("::");
}
rendered.push_str(
&path
.segments
.iter()
.map(|segment| {
let ident = segment.ident.to_string();
match &segment.arguments {
PathArguments::None => ident,
arguments => format!("{ident}{}", render_tokens(arguments)),
}
})
.collect::<Vec<_>>()
.join("::"),
);
rendered
}
fn render_tokens(tokens: &impl ToTokens) -> String {
let mut rendered = tokens.to_token_stream().to_string();
for (from, to) in [
(" :: ", "::"),
(" ::", "::"),
(":: ", "::"),
("* const", "*const"),
("* mut", "*mut"),
] {
rendered = rendered.replace(from, to);
}
rendered
}