use quote::quote;
use syn::Type;
use crate::types::{
get_option_inner_type, get_rc_inner_type, get_v8_local_inner_type, v8_local_extraction,
};
pub fn generate_state_extraction(
has_state: bool,
state_type: &Option<Type>,
) -> proc_macro2::TokenStream {
if !has_state {
return quote! {};
}
if let Some(state_ty) = state_type {
let state_ty_str = quote!(#state_ty).to_string();
let inner_ty = if let Some(inner) = get_rc_inner_type(state_ty) {
inner.clone()
} else {
state_ty.clone()
};
quote! {
let state: #state_ty = unsafe {
let data = args.data();
if data.is_undefined() || data.is_null() {
let msg = v8::String::new(scope, concat!("internal error: state data not set for ", #state_ty_str)).unwrap();
let err = v8::Exception::error(scope, msg);
scope.throw_exception(err);
return;
}
let external = v8::Local::<v8::External>::try_from(data).unwrap();
let ptr = external.value() as *const #inner_ty;
std::rc::Rc::clone(&*std::mem::ManuallyDrop::new(std::rc::Rc::from_raw(ptr)))
};
}
} else {
quote! {
compile_error!("Function has 'state' parameter but no state type specified. Use #[glue_v8::method(state = YourStateType)]");
}
}
}
pub fn generate_arg_extractions(
params: &[(syn::Ident, Box<Type>)],
) -> Vec<proc_macro2::TokenStream> {
params
.iter()
.enumerate()
.map(|(i, (name, ty))| {
let idx = i as i32;
if let Some(inner_ty) = get_option_inner_type(ty) {
let inner_type_str = quote!(#inner_ty).to_string();
let error_prefix = format!("argument {}: expected {}", idx, inner_type_str);
quote! {
let #name: #ty = {
let __v8g_arg = args.get(#idx);
if __v8g_arg.is_undefined() || __v8g_arg.is_null() {
None
} else {
match serde_v8::from_v8_any(scope, __v8g_arg) {
Ok(v) => Some(v),
Err(e) => {
let msg = v8::String::new(scope, &format!("{}: {}", #error_prefix, e)).unwrap();
let err = v8::Exception::type_error(scope, msg);
scope.throw_exception(err);
return;
}
}
}
};
}
} else if let Some(inner_type) = get_v8_local_inner_type(ty) {
match inner_type.as_str() {
"Function" => v8_local_extraction(name, idx, "Function", "is_function"),
"Object" => v8_local_extraction(name, idx, "Object", "is_object"),
"Array" => v8_local_extraction(name, idx, "Array", "is_array"),
"Uint8Array" => v8_local_extraction(name, idx, "Uint8Array", "is_uint8_array"),
"ArrayBuffer" => {
v8_local_extraction(name, idx, "ArrayBuffer", "is_array_buffer")
}
"String" => v8_local_extraction(name, idx, "String", "is_string"),
"Number" => v8_local_extraction(name, idx, "Number", "is_number"),
"Value" => {
quote! {
let #name: v8::Local<v8::Value> = args.get(#idx);
}
}
_ => {
let type_str = quote!(#ty).to_string();
let error_msg = format!("argument {}: expected {}", idx, type_str);
quote! {
let #name: #ty = match args.get(#idx).try_into() {
Ok(v) => v,
Err(_) => {
let msg = v8::String::new(scope, #error_msg).unwrap();
let err = v8::Exception::type_error(scope, msg);
scope.throw_exception(err);
return;
}
};
}
}
}
} else {
let type_str = quote!(#ty).to_string();
let error_prefix = format!("argument {}: expected {}", idx, type_str);
quote! {
let #name: #ty = match serde_v8::from_v8_any(scope, args.get(#idx)) {
Ok(v) => v,
Err(e) => {
let msg = v8::String::new(scope, &format!("{}: {}", #error_prefix, e)).unwrap();
let err = v8::Exception::type_error(scope, msg);
scope.throw_exception(err);
return;
}
};
}
}
})
.collect()
}
pub fn generate_call_and_return(
fn_name: &syn::Ident,
call_args: &[proc_macro2::TokenStream],
has_return: bool,
returns_result: bool,
is_promise: bool,
) -> proc_macro2::TokenStream {
if is_promise {
if returns_result {
quote! {
let resolver = v8::PromiseResolver::new(scope).unwrap();
let promise = resolver.get_promise(scope);
rv.set(promise.into());
match #fn_name(#(#call_args),*) {
Ok(value) => {
if let Ok(v8_value) = serde_v8::to_v8(scope, value) {
resolver.resolve(scope, v8_value);
}
}
Err(err) => {
let err_str = format!("{}", err);
let msg = v8::String::new(scope, &err_str).unwrap();
let error = v8::Exception::error(scope, msg);
resolver.reject(scope, error);
}
}
}
} else if has_return {
quote! {
let resolver = v8::PromiseResolver::new(scope).unwrap();
let promise = resolver.get_promise(scope);
rv.set(promise.into());
let result = #fn_name(#(#call_args),*);
if let Ok(v8_value) = serde_v8::to_v8(scope, result) {
resolver.resolve(scope, v8_value);
}
}
} else {
quote! {
let resolver = v8::PromiseResolver::new(scope).unwrap();
let promise = resolver.get_promise(scope);
rv.set(promise.into());
#fn_name(#(#call_args),*);
resolver.resolve(scope, v8::undefined(scope).into());
}
}
} else if returns_result {
quote! {
match #fn_name(#(#call_args),*) {
Ok(value) => {
if let Ok(v8_value) = serde_v8::to_v8(scope, value) {
rv.set(v8_value);
}
}
Err(err) => {
let err_str = format!("{}", err);
let msg = v8::String::new(scope, &err_str).unwrap();
let error = v8::Exception::error(scope, msg);
scope.throw_exception(error);
}
}
}
} else if has_return {
quote! {
let result = #fn_name(#(#call_args),*);
if let Ok(v8_result) = serde_v8::to_v8(scope, result) {
rv.set(v8_result);
}
}
} else {
quote! {
#fn_name(#(#call_args),*);
}
}
}
pub fn generate_state_template(
wrapper_name: &syn::Ident,
template_fn_name: &syn::Ident,
state_type: &Type,
) -> proc_macro2::TokenStream {
use crate::types::get_rc_inner_type;
let inner_state_type = if let Some(inner) = get_rc_inner_type(state_type) {
inner.clone()
} else {
state_type.clone()
};
quote! {
pub fn #template_fn_name<'s>(
scope: &mut v8::PinScope<'s, '_>,
state: &std::rc::Rc<#inner_state_type>,
) -> v8::Local<'s, v8::FunctionTemplate> {
let ptr = std::rc::Rc::as_ptr(state);
let external = v8::External::new(scope, ptr as *mut std::ffi::c_void);
v8::FunctionTemplate::builder(#wrapper_name)
.data(external.into())
.build(scope)
}
}
}