deno_bindgen_macro 0.3.0

This tool aims to simplify glue code generation for Deno FFI libraries written in Rust.
Documentation
use proc_macro::TokenStream;
use quote::format_ident;
use quote::quote;
use std::env;
use std::fs::OpenOptions;
use std::io::Read;
use std::io::Write;
use syn::parse_macro_input;

use syn::ItemFn;

mod attrs;
mod derive_fn;
mod derive_struct;
mod docs;
mod meta;

use crate::derive_fn::process_function;
use crate::derive_struct::process_struct;
use crate::meta::Glue;
use crate::meta::Type;

const METAFILE: &str = "bindings.json";

#[cfg(target_endian = "little")]
const ENDIANNESS: bool = true;

#[cfg(target_endian = "big")]
const ENDIANNESS: bool = false;

#[proc_macro_attribute]
pub fn deno_bindgen(attr: TokenStream, input: TokenStream) -> TokenStream {
  let mut metadata: Glue = match OpenOptions::new().read(true).open(METAFILE) {
    Ok(mut fd) => {
      let mut meta = String::new();
      fd.read_to_string(&mut meta)
        .expect("Error reading meta file");

      serde_json::from_str(&meta).unwrap_or_default()
    }
    Err(_) => Glue {
      little_endian: ENDIANNESS,
      name: env::var("CARGO_CRATE_NAME").unwrap_or_default(),
      ..Default::default()
    },
  };

  let mut metafile = OpenOptions::new()
    .write(true)
    .create(true)
    .open(METAFILE)
    .expect("Error opening meta file");

  match syn::parse::<ItemFn>(input.clone()) {
    Ok(func) => {
      let attr = parse_macro_input!(attr as syn::AttributeArgs);
      let symbol = process_function(func.clone(), attr, &mut metadata).unwrap();

      let mut params = vec![];
      let mut overrides = vec![];
      let mut input_idents = vec![];
      let mut c_index = 0;

      for parameter in symbol.parameters {
        match parameter {
          Type::StructEnum { .. } => {
            let ident = format_ident!("arg{}", c_index.to_string());
            params.push(quote! { #ident: *const u8 });

            c_index += 1;
            let len_ident = format_ident!("arg{}", c_index.to_string());
            params.push(quote! { #len_ident: usize });

            overrides.push(quote! {
              let buf = unsafe {
                ::std::slice::from_raw_parts(#ident, #len_ident)
              };
              let #ident = deno_bindgen::serde_json::from_slice(buf).unwrap();
            });

            input_idents.push(ident);
          }
          Type::Str | Type::Buffer | Type::BufferMut => {
            let ident = format_ident!("arg{}", c_index.to_string());
            match parameter {
              Type::Str | Type::Buffer => {
                params.push(quote! { #ident: *const u8 })
              }
              Type::BufferMut => params.push(quote! { #ident: *mut u8 }),
              _ => unreachable!(),
            };

            c_index += 1;
            let len_ident = format_ident!("arg{}", c_index.to_string());
            params.push(quote! { #len_ident: usize });

            let return_type = match parameter {
              Type::Str => quote! { ::std::str::from_utf8(buf).unwrap() },
              Type::Buffer | Type::BufferMut => quote! { buf },
              _ => unreachable!(),
            };

            let buf_expr = match parameter {
              Type::Str | Type::Buffer => {
                quote! { let buf = ::std::slice::from_raw_parts(#ident, #len_ident); }
              }
              Type::BufferMut => {
                // https://github.com/littledivy/deno_bindgen/issues/26
                // *mut u8 should never outlive the symbol call. This can lead to UB.
                quote! { let mut buf: &'sym mut [u8] = ::std::slice::from_raw_parts_mut(#ident, #len_ident);
                }
              }
              _ => unreachable!(),
            };

            overrides.push(quote! {
              let #ident = unsafe {
                #buf_expr
                #return_type
              };
            });

            input_idents.push(ident);
          }
          // TODO
          _ => {
            let ident = format_ident!("arg{}", c_index.to_string());
            let ty = syn::Type::from(parameter);
            params.push(quote! { #ident: #ty });
            input_idents.push(ident);
          }
        };

        c_index += 1;
      }

      let name = &func.sig.ident;
      let fn_inputs = &func.sig.inputs;
      let fn_output = &func.sig.output;
      let fn_generics = &func.sig.generics;
      let fn_block = &func.block;

      let overrides = overrides
        .iter()
        .fold(quote! {}, |acc, new| quote! { #acc #new });

      metafile
        .write_all(&serde_json::to_vec(&metadata).unwrap())
        .unwrap();

      TokenStream::from(quote! {
        #[no_mangle]
        pub extern "C" fn #name <'sym> (#(#params,) *) #fn_output {
          fn __inner_impl #fn_generics (#fn_inputs) #fn_output #fn_block
          #overrides
          __inner_impl(#(#input_idents, ) *)
        }
      })
    }
    Err(_) => {
      let input = syn::parse_macro_input!(input as syn::DeriveInput);
      process_struct(&mut metadata, input.clone()).unwrap();

      metafile
        .write_all(&serde_json::to_vec(&metadata).unwrap())
        .unwrap();

      TokenStream::from(quote! {
        #[derive(::serde::Deserialize)]
        #input
      })
    }
  }
}