diff options
author | Justus Winter <justus@sequoia-pgp.org> | 2019-01-28 13:58:34 +0100 |
---|---|---|
committer | Justus Winter <justus@sequoia-pgp.org> | 2019-05-16 13:19:38 +0200 |
commit | b2c8db84f798b422cf93959031e9c234f8595d78 (patch) | |
tree | c35b15a418fa086a3bafae9c2b0c366c1d0e5d82 /ffi-macros | |
parent | 0caa1b471ecc1305e78b1e7988fe2c692ca41fa9 (diff) |
ffi-macros: Generate function prototypes for C.
Diffstat (limited to 'ffi-macros')
-rw-r--r-- | ffi-macros/src/lib.rs | 69 | ||||
-rw-r--r-- | ffi-macros/src/rust2c.rs | 165 |
2 files changed, 233 insertions, 1 deletions
diff --git a/ffi-macros/src/lib.rs b/ffi-macros/src/lib.rs index e28c0c85..26c1beb9 100644 --- a/ffi-macros/src/lib.rs +++ b/ffi-macros/src/lib.rs @@ -8,6 +8,7 @@ use std::io::Write; extern crate lazy_static; use lazy_static::lazy_static; extern crate syn; +use syn::parse_quote; use syn::spanned::Spanned; extern crate quote; extern crate proc_macro; @@ -19,15 +20,81 @@ use proc_macro2::TokenStream as TokenStream2; use quote::{quote, ToTokens}; +mod rust2c; + /// Transforms exported functions. /// /// This macro is used to decorate every function exported from /// Sequoia. It applies the following transformations: /// /// - [ffi_catch_abort](attr.ffi_catch_abort.html) +/// - [cdecl](attr.cdecl.html) #[proc_macro_attribute] pub fn extern_fn(attr: TokenStream, item: TokenStream) -> TokenStream { - ffi_catch_abort(attr, item) + cdecl(attr.clone(), ffi_catch_abort(attr, item)) +} + +/// Generates a C function declaration. +#[proc_macro_attribute] +pub fn cdecl(_attr: TokenStream, item: TokenStream) -> TokenStream { + // Parse tokens into a function declaration. + let fun = syn::parse_macro_input!(item as syn::ItemFn); + + // Extract all information from the parsed function that we need + // to compose the new function. + let summary = fun.attrs.iter().next(); + let attrs = fun.attrs.iter().skip(1) + .fold(TokenStream2::new(), + |mut acc, attr| { + acc.extend(attr.clone().into_token_stream()); + acc + }); + let vis = &fun.vis; + let constness = &fun.constness; + let unsafety = &fun.unsafety; + let asyncness = &fun.asyncness; + let abi = &fun.abi; + let ident = &fun.ident; + + let decl = &fun.decl; + let fn_token = &decl.fn_token; + let fn_generics = &decl.generics; + let fn_out = &decl.output; + + let mut fn_params = TokenStream2::new(); + decl.paren_token.surround(&mut fn_params, |ts| decl.inputs.to_tokens(ts)); + + let block = &fun.block; + + let mut cdecl = TokenStream2::new(); + doc(" # C Declaration", &mut cdecl); + doc(" ```c", &mut cdecl); + for line in rust2c::rust2c(&fun).split("\n") { + doc(&format!(" {}", line), &mut cdecl); + } + doc(" ```", &mut cdecl); + + let expanded = quote! { + #summary + #cdecl + #attrs + #vis #constness #unsafety #asyncness #abi + #fn_token #ident #fn_generics #fn_params #fn_out + #block + //#fun + }; + + // To debug problems with the generated code, just eprintln it: + // + // eprintln!("{}", expanded); + + expanded.into() +} + +/// Creates an doc attribute. +fn doc(s: &str, ts: &mut TokenStream2) { + let attr: syn::Attribute = parse_quote!(#[doc = #s]); + attr.to_tokens(ts); } /// Wraps a function's body in a catch_unwind block, aborting on diff --git a/ffi-macros/src/rust2c.rs b/ffi-macros/src/rust2c.rs new file mode 100644 index 00000000..84117ce5 --- /dev/null +++ b/ffi-macros/src/rust2c.rs @@ -0,0 +1,165 @@ +extern crate syn; +use syn::export::ToTokens; +extern crate proc_macro2; + +const PREFIX: &'static str = "pgp_"; + +/// Derives the C type from the Rust type. +/// +/// Returns the corresponding C type, and whether or not that type is +/// a pointer type. +fn ident2c(ident: &syn::Ident) -> (String, bool) { + let ident_string = ident.to_string(); + + // Special cases :( + match ident_string.as_str() { + "KeyID" => return ("pgp_keyid_t".into(), true), + "TPKBuilder" => return ("pgp_tpk_builder_t".into(), true), + "UserID" => return ("pgp_userid_t".into(), true), + "UserIDBinding" => return ("pgp_user_id_binding_t".into(), true), + "UserIDBindingIter" => + return ("pgp_user_id_binding_iter_t".into(), true), + + // Types from the libc crate. + "c_char" => return ("char".into(), false), + "c_int" => return ("int".into(), false), + "c_uint" => return ("uint".into(), false), + "bool" => return ("bool".into(), false), + "size_t" | "ssize_t" | "time_t" | + "int8_t" | "int16_t" | "int32_t" | "int64_t" | + "uint8_t" | "uint16_t" | "uint32_t" | "uint64_t" + => return (ident_string.clone(), false), + _ => (), + } + + let mut s = String::new(); + s += PREFIX; + let mut last_was_uppercase = false; + for (i, c) in ident_string.chars().enumerate() { + if c.is_uppercase() { + if ! last_was_uppercase && i > 0 { + s += "_"; + } + s += &c.to_lowercase().to_string(); + } else { + s += &c.to_string(); + } + + last_was_uppercase = c.is_uppercase(); + } + + s += "_t"; + (s, true) +} + +fn type2c<T: ToTokens>(typ: T) -> String { + let mut tokens = proc_macro2::TokenStream::new(); + typ.to_tokens(&mut tokens); + let mut c_typ = String::new(); + let mut is_mutable = false; + let mut is_pointer = 0; + let mut ident = None; + for tok in tokens { + use proc_macro2::TokenTree::*; + if false { + eprintln!("mut: {}, ptr: {}, ident: {:?}, tok: {:?}", + is_mutable, is_pointer, ident, tok); + } + + match tok { + Ident(ref i) => { + let i_ = format!("{}", i); + match i_.as_str() { + "mut" => is_mutable = true, + "const" => is_mutable = false, + "Option" => (), + "Maybe" => { + is_pointer += 1; + is_mutable = true; + } + _ => { + if is_pointer > 0 && ! is_mutable { + c_typ += "const "; + } + + ident = Some(i.clone()); + }, + } + }, + Punct(ref p) => { + if ident.is_some() && p.as_char() != ':' { + // We already found the ident, now skip the <...>. + break; + } + match p.as_char() { + '*' | '&' => { + is_pointer += 1; + }, + _ => (), + } + //eprintln!("{:?}", p); + }, + Literal(ref l) => panic!("Unexpected {:?}", l), + Group(ref g) => panic!("Unexpected {:?}", g), + } + } + if let Some(c_ident) = ident { + let (c_ident, is_pointer_type) = ident2c(&c_ident); + if is_pointer_type { + is_pointer -= 1; + } + c_typ += &c_ident; + } else { + panic!(); + } + + c_typ.push(' '); + while is_pointer > 0 { + c_typ.push('*'); + is_pointer -= 1; + } + + //eprintln!("==> {:?} // {}", c_typ, is_pointer); + c_typ +} + +pub fn rust2c(fun: &syn::ItemFn) -> String { + let decl = &fun.decl; + let return_type = match &decl.output { + syn::ReturnType::Default => "void".into(), + syn::ReturnType::Type(_, ref typ) => type2c(typ).trim_end().to_string(), + }; + let fun_ident = format!("{}", fun.ident); + + let mut s = String::new(); + s += &format!("{}\n{} (", return_type, fun_ident); + let indent = fun_ident.len() + 2; + + for (i, arg) in decl.inputs.iter().enumerate() { + // All but the first line need to be indented. + if i > 0 { + for _ in 0..indent { + s.push(' '); + } + } + + match arg { + &syn::FnArg::Captured(ref cap) => { + let pat_ident = match &cap.pat { + &syn::Pat::Ident(ref i) => i, + _ => unimplemented!(), + }; + s += &format!("{}{}", type2c(&cap.ty), pat_ident.ident); + }, + _ => (), + } + + // All but the last one need a comma. + if i < decl.inputs.len() - 1 { + s += ",\n"; + } + } + + s += ");"; + s +} |