#![recursion_limit = "128"]
extern crate proc_macro;
extern crate proc_macro2;
#[macro_use]
extern crate syn;
#[macro_use]
extern crate quote;
use proc_macro2::{Ident, Span, TokenStream};
use quote::ToTokens;
use syn::punctuated::Punctuated;
use syn::token::Comma;
fn create_function_params(num_args: usize) -> TokenStream {
let mut tokens = TokenStream::new();
for i in 0..num_args {
let arg_name = Ident::new(&format!("arg_{}", i), Span::call_site());
tokens.extend(quote!(
#arg_name,
));
}
tokens
}
fn extract_arg_data(inputs: &Punctuated<syn::FnArg, Comma>) -> TokenStream {
let mut get_args_stream = TokenStream::new();
for (i, arg) in inputs.iter().enumerate() {
let arg_type: &syn::Type = match *arg {
syn::FnArg::SelfRef(_) | syn::FnArg::SelfValue(_) => {
panic!("self functions not supported")
}
syn::FnArg::Inferred(_) => panic!("inferred function parameters not supported"),
syn::FnArg::Captured(ref captured) => &captured.ty,
syn::FnArg::Ignored(ref ty) => ty,
};
let arg_name = Ident::new(&format!("arg_{}", i), Span::call_site());
let arg_error = format!("unsupported function argument type for {}", arg_name);
let get_arg = quote!(
let #arg_name: #arg_type = pg_extend::pg_datum::TryFromPgDatum::try_from(
pg_extend::pg_datum::PgDatum::from_raw(
args[#i],
args_null[#i]
),
)
.expect(#arg_error);
);
get_args_stream.extend(get_arg);
}
get_args_stream
}
fn impl_info_for_fn(item: &syn::Item) -> TokenStream {
let func = if let syn::Item::Fn(func) = item {
func
} else {
panic!("annotation only supported on functions");
};
let func_name = &func.ident;
let func_decl = &func.decl;
if func_decl.variadic.is_some() {
panic!("variadic functions (...) not supported")
}
let inputs = &func_decl.inputs;
let mut function = item.clone().into_token_stream();
let func_wrapper_name = syn::Ident::new(&format!("pg_{}", func_name), Span::call_site());
let func_info_name = syn::Ident::new(
&format!("pg_finfo_{}", func_wrapper_name),
Span::call_site(),
);
let func_info = quote!(
#[no_mangle]
pub extern "C" fn #func_info_name () -> &'static pg_extend::pg_sys::Pg_finfo_record {
const my_finfo: pg_extend::pg_sys::Pg_finfo_record = pg_extend::pg_sys::Pg_finfo_record { api_version: 1 };
&my_finfo
}
);
function.extend(func_info);
let get_args_from_datums = extract_arg_data(inputs);
let func_params = create_function_params(inputs.len());
let func_wrapper = quote!(
#[no_mangle]
pub extern "C" fn #func_wrapper_name (func_call_info: pg_extend::pg_sys::FunctionCallInfo) -> pg_extend::pg_sys::Datum {
use std::panic;
let func_info: &mut pg_extend::pg_sys::FunctionCallInfoData = unsafe {
func_call_info
.as_mut()
.expect("func_call_info was unexpectedly NULL")
};
let panic_result = panic::catch_unwind(|| {
let (args, args_null) = pg_extend::get_args(func_info);
#get_args_from_datums
let result = #func_name(#func_params);
pg_extend::pg_datum::PgDatum::from(result)
});
match panic_result {
Ok(result) => {
func_info.isnull = result.is_null();
result.into_datum()
}
Err(err) => {
func_info.isnull = true;
panic::resume_unwind(err)
}
}
}
);
function.extend(func_wrapper);
function
}
#[proc_macro_attribute]
pub fn pg_extern(
_attr: proc_macro::TokenStream,
item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let ast: syn::Item = parse_macro_input!(item as syn::Item);
let expanded: TokenStream = impl_info_for_fn(&ast);
proc_macro::TokenStream::from(expanded)
}