summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJustus Winter <justus@sequoia-pgp.org>2019-01-28 13:58:34 +0100
committerJustus Winter <justus@sequoia-pgp.org>2019-05-16 13:19:38 +0200
commitb2c8db84f798b422cf93959031e9c234f8595d78 (patch)
treec35b15a418fa086a3bafae9c2b0c366c1d0e5d82
parent0caa1b471ecc1305e78b1e7988fe2c692ca41fa9 (diff)
ffi-macros: Generate function prototypes for C.
-rw-r--r--ffi-macros/src/lib.rs69
-rw-r--r--ffi-macros/src/rust2c.rs165
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
+}