use crate::codegen::{File, FnParam, Function, Interface, InterfaceDefn, Toplevel};
use crate::parser::Ctype;
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
use std::io;
use std::io::Write;
use std::path::Path;
use syn::token;
pub fn write_rust_module<W: Write>(mut w: W, f: &File, source: &Path) -> io::Result<()> {
let tokens = rust_module(f);
let st = syn::parse2(tokens).unwrap();
let text = prettyplease::unparse(&st);
writeln!(w, "// Auto-generated by nuidl from {}.", source.display())?;
writeln!(w, "{}", text)?;
Ok(())
}
pub fn rust_module(f: &File) -> TokenStream {
fn make_imports(
pub_t: TokenStream,
prefix: TokenStream,
f: &File,
) -> impl Iterator<Item = TokenStream> + '_ {
f.imports
.iter()
.filter_map(|i| i.rust_name.as_ref())
.map(move |name| quote! { #pub_t use #prefix::#name::scope::*; })
}
let imports = make_imports(quote! {}, quote! { super }, f);
let pub_imports = make_imports(quote! { pub }, quote! { super::super }, f);
let elems = f.elems.iter().map(|elem| match elem {
Toplevel::CppQuote(_) => quote! {},
Toplevel::Interface(itf) => make_interface(itf),
});
quote! {
use ::nucomcore::idl::prelude::*;
#[doc(hidden)]
pub mod scope {
pub use ::nucomcore::idl::prelude::*;
pub use super::*;
#(#pub_imports)*
}
#(#imports)*
#(#elems)*
}
}
struct InterfaceInfo<'a> {
itf: &'a Interface,
defn: &'a InterfaceDefn,
all_fns: Vec<&'a Function>,
}
fn make_interface(itf: &Interface) -> TokenStream {
let Some(defn) = &itf.defn else {
println!(
"forward-declaration for interface {} not supported when generating Rust",
itf.name
);
return quote! {};
};
let mut all_fns = Vec::new();
fn collect_fns<'a>(fns: &mut Vec<&'a Function>, itf: &'a Interface) {
let defn = itf.defn.as_ref().unwrap();
if let Some(base) = &defn.base {
collect_fns(fns, &base);
}
fns.extend(&defn.fns);
}
collect_fns(&mut all_fns, itf);
let info = InterfaceInfo { itf, defn, all_fns };
let name = make_ident(&itf.name);
let vtbl_name = make_ident(&format!("{}Vtbl", name));
let impl_name = make_ident(&format!("{}Impl", name));
let safe_ext_name = make_ident(&format!("{}Ext", name));
let unsafe_ext_name = make_ident(&format!("{}UnsafeExt", name));
let base_impl = defn
.base
.as_deref()
.map(|base| Ident::new(&format!("{}Impl", base.name), Span::call_site()))
.map(|ident| quote! { : #ident })
.unwrap_or_default();
let ffi_functions = make_ffi_functions(&info);
let impl_functions = make_impl_functions(&info);
let safe_ext_functions = make_ext_functions(&info, ExtVariant::Safe);
let unsafe_ext_functions = make_ext_functions(&info, ExtVariant::Unsafe);
let dispatch = make_dispatch(&info);
let (d1, d2, d3, &d4) = defn.uuid.unwrap().as_fields();
let mut hierarchy = Vec::new();
{
let mut cur = Some(itf);
while let Some(c) = cur {
let base_name = make_ident(&c.name);
let imp = quote! { unsafe impl ::nucomcore::interface::ComHierarchy<#base_name> for #name {} };
hierarchy.push(imp);
cur = c.defn.as_ref().and_then(|v| v.base.as_ref()).map(|v| &**v);
}
}
quote! {
#[repr(C)]
pub struct #name {
pub vtbl: *const #vtbl_name,
_data: ()
}
#[repr(C)]
#[allow(non_snake_case)]
pub struct #vtbl_name {
#ffi_functions
}
impl ::nucomcore::Identify for #name {
const GUID: ::nucomcore::GUID = ::nucomcore::GUID(#d1, #d2, #d3, [#(#d4),*]);
}
impl ::nucomcore::interface::ComInterface for #name {
type Vtbl = #vtbl_name;
type Mt = ::nucomcore::interface::Apartment;
}
#(#hierarchy)*
#[allow(non_snake_case)]
impl #vtbl_name {
#dispatch
}
#[allow(non_snake_case)]
pub trait #impl_name #base_impl {
#impl_functions
}
#[allow(non_snake_case)]
pub trait #safe_ext_name: ::nucomcore::interface::ComDeref
where
<Self as ::nucomcore::interface::ComDeref>::Target: ::nucomcore::interface::ComHierarchy<#name>,
{
#safe_ext_functions
}
impl <T> #safe_ext_name for T
where
T: ::nucomcore::interface::ComDeref<Safety = ::nucomcore::interface::Safe>,
<T as ::nucomcore::interface::ComDeref>::Target: ::nucomcore::interface::ComHierarchy<#name>,
{}
#[allow(non_snake_case)]
pub trait #unsafe_ext_name: ::nucomcore::interface::ComDeref
where
<Self as ::nucomcore::interface::ComDeref>::Target: ::nucomcore::interface::ComHierarchy<#name>,
{
#unsafe_ext_functions
}
impl <T> #unsafe_ext_name for T
where
T: ::nucomcore::interface::ComDeref<Safety = ::nucomcore::interface::Unsafe>,
<T as ::nucomcore::interface::ComDeref>::Target: ::nucomcore::interface::ComHierarchy<#name>,
{}
}
}
fn make_ffi_functions(info: &InterfaceInfo) -> TokenStream {
let fns = info.all_fns.iter().map(|f| make_ffi_function(info.itf, f));
quote! {
#(#fns,)*
}
}
fn make_ffi_function(itf: &Interface, f: &Function) -> TokenStream {
fn make_fn_param(p: &FnParam) -> TokenStream {
let name = p.name.iter().map(|n| Ident::new(n, Span::call_site()));
let ty = make_ctype(&p.ty);
quote! {
#(#name:)* #ty
}
}
let name = Ident::new(&f.name, Span::call_site());
let itf_name = Ident::new(&itf.name, Span::call_site());
let args = f.params.iter().map(|par| make_fn_param(par));
let ret = make_ctype(&f.ret);
quote! {
pub #name: unsafe extern "system" fn(this: *mut #itf_name #(, #args)*) -> #ret
}
}
fn make_impl_functions(info: &InterfaceInfo) -> TokenStream {
let fns = info.defn.fns.iter().map(|f| make_impl_function(f));
quote! {
#(#fns;)*
}
}
fn make_impl_function(f: &Function) -> TokenStream {
fn make_fn_param(p: &FnParam) -> TokenStream {
let name = p.name.iter().map(|n| Ident::new(n, Span::call_site()));
let ty = make_ctype(&p.ty);
quote! {
#(#name:)* #ty
}
}
let name = make_ident(&f.name);
let args = f.params.iter().map(|par| make_fn_param(par));
let ret = make_ctype(&f.ret);
quote! {
unsafe extern "system" fn #name(this: *mut Self #(, #args)*) -> #ret
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum ExtVariant {
Safe,
Unsafe,
}
fn make_ext_functions(info: &InterfaceInfo, variant: ExtVariant) -> TokenStream {
let fns = info
.defn
.fns
.iter()
.map(|f| make_ext_function(info, f, variant));
quote! {
#(#fns)*
}
}
fn make_ext_function(info: &InterfaceInfo, f: &Function, mut variant: ExtVariant) -> TokenStream {
if f.name == "Release" {
variant = ExtVariant::Unsafe;
}
let itf_name = make_ident(&info.itf.name);
let name = make_ident(&f.name);
let ret = make_ctype(&f.ret);
let args_in = make_params_in(f);
let args_out = make_params_out(f);
let unsafe_t = match variant {
ExtVariant::Safe => None,
ExtVariant::Unsafe => Some(token::Unsafe::default()),
};
quote! {
#[inline]
#unsafe_t fn #name(&self, #args_in) -> #ret {
let ptr = ::nucomcore::interface::ComDeref::com_deref(self).upcast::<#itf_name>();
let ptr = ptr.into_raw();
unsafe { ((*ptr.as_ref().vtbl).#name)(ptr.as_ptr(), #args_out) }
}
}
}
fn make_param_name(idx: usize, p: &FnParam) -> TokenStream {
let name = p
.name
.as_deref()
.cloned()
.unwrap_or_else(|| format!("param_{}", idx));
let name = Ident::new(&name, Span::call_site());
quote! {
#name
}
}
fn make_params_in(f: &Function) -> TokenStream {
let args = f
.params
.iter()
.enumerate()
.map(|(idx, p)| make_param_name(idx, p));
let arg_types = f.params.iter().map(|p| make_ctype(&p.ty));
quote! {
#(#args: #arg_types,)*
}
}
fn make_params_out(f: &Function) -> TokenStream {
let args = f
.params
.iter()
.enumerate()
.map(|(idx, p)| make_param_name(idx, p));
quote! {
#(#args),*
}
}
fn make_dispatch(info: &InterfaceInfo) -> TokenStream {
fn make_function_wrapper(info: &InterfaceInfo, f: &Function) -> TokenStream {
let name = make_ident(&f.name);
let itf_impl = make_ident(&format!("{}Impl", info.itf.name));
let itf = make_ident(&info.itf.name);
let ret = make_ctype(&f.ret);
let params_in = make_params_in(f);
let params_out = make_params_out(f);
quote! {
unsafe extern "system" fn #name<const OFFSET: usize, T: #itf_impl>(this: *mut #itf, #params_in) -> #ret {
let this = (this as *mut *mut ()).offset(-(OFFSET as isize)) as *mut T;
T::#name(this, #params_out)
}
}
}
let name = Ident::new(&info.itf.name, Span::call_site());
let vtbl_name = Ident::new(&format!("{}Vtbl", name), Span::call_site());
let impl_name = Ident::new(&format!("{}Impl", name), Span::call_site());
let wrappers = info.all_fns.iter().map(|f| make_function_wrapper(info, f));
let vtable_elems = info.all_fns.iter().map(|f| {
let name = Ident::new(&f.name, Span::call_site());
quote! { #name: #name::<OFFSET, T> }
});
quote! {
pub const fn dispatch<const OFFSET: usize, T: #impl_name>() -> &'static #vtbl_name {
#(#wrappers)*
& #vtbl_name {
#(#vtable_elems,)*
}
}
}
}
fn make_ctype(ty: &Ctype) -> TokenStream {
if ty.is_void() {
return quote! {
()
};
}
let ptrs = [ty.is_const]
.into_iter()
.chain(ty.indirection.iter().map(|v| v.is_const))
.rev()
.skip(1)
.map(|is_const| {
if is_const {
quote! { *const }
} else {
quote! { *mut }
}
});
let inner = Ident::new(&ty.typename, Span::call_site());
quote! {
#(#ptrs)* #inner
}
}
fn make_ident(id: &str) -> Ident {
Ident::new(id, Span::call_site())
}
#[cfg(test)]
mod test {
use super::make_ctype;
use crate::parser::{Ctype, Ptr};
use quote::quote;
#[test]
fn ptr() {
let ty = Ctype {
typename: "char".to_string(),
is_const: true,
indirection: vec![Ptr { is_const: true }, Ptr { is_const: false }],
};
let expected = quote! { *const *const char };
assert_eq!(expected.to_string(), make_ctype(&ty).to_string());
}
}