1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
extern crate proc_macro;
use proc_macro::TokenStream;
use proc_macro2::TokenTree;
use quote::{format_ident, quote};

/// A macro that takes the functions to be exported for use in Deno as arguments
/// # Syntax
/// ```
/// export!(function1,function2,...);
/// ```
#[proc_macro]
pub fn export(tokens: TokenStream) -> TokenStream {
    let tokens = proc_macro2::TokenStream::from(tokens)
        .into_iter()
        .filter(|token| match token {
            TokenTree::Ident(_) => true,
            _ => false,
        });
    (quote! {
        #[no_mangle]
        pub fn deno_plugin_init(interface: &mut dyn deno_core::plugin_api::Interface) {
            #(interface.register_op(stringify!(#tokens), #tokens);)*
        }
    })
    .into()
}

/// When placed above a function it converts the function to a deno op of the same name.
#[proc_macro_attribute]
pub fn deno_op(_attr: TokenStream, function: TokenStream) -> TokenStream {
    let mut function = proc_macro2::TokenStream::from(function).into_iter();
    let mut ispub = false;
    let mut isasync = false;
    loop {
        match function.next() {
            Some(TokenTree::Ident(ident)) => match &*ident.to_string() {
                "fn" => break,
                "pub" => ispub = true,
                "async" => isasync = true,
                _ => continue,
            },
            _ => continue,
        }
    }

    let fn_name = function.next().expect("function name missing");
    let fn_name = match fn_name {
        TokenTree::Ident(ident) => ident,
        _ => panic!(),
    };
    let __impl_fn_name = format_ident!("__impl_{}", fn_name);

    let fn_args = match function.next().unwrap() {
        TokenTree::Group(g) => g.stream(),
        _ => panic!(),
    };
    let fn_args: Vec<TokenTree> = fn_args.into_iter().collect();
    let mut passed_args = vec![];
    let mut arg_count = if isasync { 1usize } else { 0usize };

    for (i, token) in fn_args.iter().enumerate() {
        if let TokenTree::Punct(p) = token {
            if p.to_string() == "," && i != fn_args.len() - 1 {
                passed_args.push(quote! {
                    calcite::to_argument_type(&args[#arg_count])
                });
                arg_count += 1;
            }
        }
    }
    if !fn_args.is_empty() {
        passed_args.push(quote! {
            calcite::to_argument_type(&args[#arg_count])
        });
    }

    let fn_rest: Vec<TokenTree> = function.collect();
    let pub_token = if ispub {
        quote! {pub}
    } else {
        quote! {}
    };
    let async_token = if isasync {
        quote! {async}
    } else {
        quote! {}
    };

    let generated_fn_body = if isasync {
        quote! {
            use calcite::futures::future::FutureExt;
            let args = args.to_vec();
            let fut = async move {
                let command_id:usize = calcite::to_argument_type(&args[0]);
                let result = #__impl_fn_name(#(#passed_args),*).await;
                let result = calcite::AsyncResult {
                    command_id,
                    result
                };
                Into::<calcite::ReturnBuffer>::into(result).inner()
            };
            deno_core::plugin_api::Op::Async(fut.boxed())
        }
    } else {
        quote! {deno_core::plugin_api::Op::Sync(Into::<calcite::ReturnBuffer>::into(#__impl_fn_name(#(#passed_args),*)).inner() )}
    };

    (quote! {
        #pub_token fn #fn_name (_: &mut dyn deno_core::plugin_api::Interface, args: &mut [deno_core::plugin_api::ZeroCopyBuf]) -> deno_core::plugin_api::Op {
            #generated_fn_body
        }

        #async_token fn #__impl_fn_name ( #(#fn_args)* ) #(#fn_rest)*
    }).into()
}