use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use quote::ToTokens;
use std::borrow::Borrow;
use std::borrow::BorrowMut;
use syn::{parse, parse_str, FnArg, ItemFn, Pat, Stmt};
fn gen_libsql_type(ty: &syn::Type) -> Box<syn::Type> {
match ty.to_owned().into_token_stream().to_string().as_str() {
"i8" | "u8" | "i16" | "u16" | "i32" | "u32" | "i64" | "u64" | "bool" | "char" => {
Box::new(syn::Type::from(parse_str::<syn::TypePath>("i64").unwrap()))
}
"f32" | "f64" => Box::new(syn::Type::from(parse_str::<syn::TypePath>("f64").unwrap())),
_ => Box::new(syn::Type::from(parse_str::<syn::TypePath>("i32").unwrap())),
}
}
fn gen_from_expr(ty: &syn::Type, id: &str) -> String {
let type_str = ty.to_owned().into_token_stream().to_string();
match type_str.as_str() {
"i8" | "u8" | "i16" | "u16" | "i32" | "u32" | "i64" | "u64" | "bool" | "char" => {
format!("{} as {}", id, type_str)
}
"f32" | "f64" => format!("{} as f64", id),
_ => format!("<{}>::from_libsql_type({})", type_str, id),
}
}
fn gen_into_expr(return_type_str: &str, expr: syn::Expr) -> syn::Expr {
let expr_str = expr.into_token_stream().to_string();
parse_str::<syn::Expr>(&match return_type_str {
"i8" | "u8" | "i16" | "u16" | "i32" | "u32" | "i64" | "u64" | "bool" | "char" => {
format!("{} as i64", expr_str)
}
"f32" | "f64" => format!("{} as f64", expr_str),
_ => format!("{}.into_libsql_type()", expr_str),
})
.unwrap()
}
#[proc_macro_attribute]
pub fn libsql_bindgen(_attrs: TokenStream, item: TokenStream) -> TokenStream {
let input = match parse::<ItemFn>(item) {
Ok(i) => i,
Err(_) => {
return TokenStream::from(
syn::Error::new(
Span::call_site(),
"libsql_bindgen operates on function definitions only",
)
.to_compile_error(),
)
}
};
let mut native_sig = input.sig.clone();
let mut generated_sig = input.sig.clone();
native_sig.ident = syn::Ident::new(
format!("__libsql_native_{}", generated_sig.ident).as_str(),
Span::call_site(),
);
for raw_param in &mut generated_sig.inputs {
if let &mut FnArg::Typed(ref mut param) = raw_param {
param.ty = gen_libsql_type(¶m.ty);
if let &mut Pat::Ident(ref mut id) = param.pat.borrow_mut() {
id.mutability = Option::None;
}
}
}
let mut return_type_str = String::new();
if let &syn::ReturnType::Type(_, ref ty) = &generated_sig.output {
return_type_str = ty.to_owned().into_token_stream().to_string();
generated_sig.output =
syn::ReturnType::Type(syn::token::RArrow::default(), gen_libsql_type(ty));
}
let native_block = &input.block;
let mut generated_block = syn::Block {
brace_token: input.block.brace_token,
stmts: Vec::<Stmt>::new(),
};
let mut raw_ret_expr =
parse_str::<syn::Expr>(&format!("{}()", &native_sig.ident.to_string())).unwrap();
if let &mut syn::Expr::Call(ref mut call) = &mut raw_ret_expr {
for raw_param in &native_sig.inputs {
if let &FnArg::Typed(ref param) = raw_param {
if let &Pat::Ident(ref id) = param.pat.borrow() {
let from_expr =
parse_str::<syn::Expr>(&gen_from_expr(¶m.ty, &id.ident.to_string()))
.unwrap();
call.args.push(from_expr);
}
}
}
}
let ret_expr = gen_into_expr(&return_type_str, raw_ret_expr);
generated_block.stmts.push(Stmt::Expr(ret_expr));
TokenStream::from(quote! {
#native_sig
#native_block
#[no_mangle]
pub #generated_sig
#generated_block
})
}