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
116
117
118
119
#![feature(extend_one, let_chains, anonymous_lifetime_in_impl_trait, extract_if)]

use std::collections::HashMap;

use proc_macro::TokenStream;
use quote::{format_ident, quote, quote_spanned, ToTokens};
use syn::{spanned::Spanned, ItemEnum, Path, TraitItemFn, parse_macro_input};

use crate::{feature_gates::FeatureGates, util::generate_gate};

mod enums;
mod feature_gates;
mod generics;
mod handle;
mod plugin_interface;
mod util;

/// Generate a plugin-interface, based on an enum definition for its' operations
/// From the plugin's perspective - input types are all fields except the last one, and the output type is the last one
/// (of course - you can use tuples to output multiple values).
///
/// The provided enum's variant must contain only owned data (no &'a) - otherwise, deserialiastaion will cause a compile-time error.
/// The variants must be [`serde::Serialize`] + [`serde::Deserialize`].
///
/// Note that the enum this attribute applies to won't exist.  
/// Instead, there will be a `message` enum, `response` enum, plugin `trait`, plugin `handle` (a struct) - postfixed with the highlighted words.
#[proc_macro_attribute]
pub fn io_plugin(attribute_data: TokenStream, input: TokenStream) -> TokenStream {
    let gates = syn::parse::<FeatureGates>(attribute_data).ok();
    let gates = gates.map(|g| g.hashmap()).unwrap_or(HashMap::new());
    let mut input = parse_macro_input!(input as ItemEnum);

    if let Some(lifetime) = input.generics.lifetimes().last() {
        return quote_spanned!(lifetime.span()=>compile_error!("lifetimes are not supported in `io_plugin`");).into();
    }

    input.ident = format_ident!("{}", input.ident.to_string().trim_start_matches("_"));

    let (message, response, response_impl) = enums::split_enum(&mut input);

    for ty in input.generics.type_params_mut() {
        ty.default = None;
    }

    #[allow(unused_variables)]
    let handle = handle::generate_handle(
        input.clone(),
        message.clone(),
        response.clone(),
        generate_gate(gates.get("handle")),
    );

    let gate = gates.get("plugin_trait");
    let (plugin_trait, main_loop_iteration) =
        plugin_interface::generate_trait(input.clone(), message.clone(), response.clone(), gate);
    let plugin_trait_gate = generate_gate(gate);

    quote_spanned!(message.span()=>
    #message

    #response
    #response_impl

    #plugin_trait_gate
    #plugin_trait

    #plugin_trait_gate
    #main_loop_iteration

    #handle
    )
    .into()
}

/// Allows customising the documentation of the handle generated by [`io_plugin`]
#[proc_macro_attribute]
pub fn handle_doc(_attr: TokenStream, item: TokenStream) -> TokenStream {
    item
}

/// Allows customising the documentation of the plugin trait generated by [`io_plugin`]
#[proc_macro_attribute]
pub fn plugin_trait_doc(_attr: TokenStream, item: TokenStream) -> TokenStream {
    item
}

/// Attributes which only apply to the plugin message enum
#[proc_macro_attribute]
pub fn message_attributes(_attr: TokenStream, item: TokenStream) -> TokenStream {
    item
}

/// Attributes which only apply to the plugin response enum
#[proc_macro_attribute]
pub fn response_attributes(_attr: TokenStream, item: TokenStream) -> TokenStream {
    item
}

/// Provide a default implementation for a plugin method
#[proc_macro_attribute]
pub fn trait_method_default(attr: TokenStream, item: TokenStream) -> TokenStream {
    let mut method = parse_macro_input!(item as TraitItemFn);
    let signature = &method.sig;
    let args = signature.inputs.iter().filter_map(|arg| {
        if let syn::FnArg::Typed(arg) = arg {
            Some(arg.pat.to_token_stream())
        } else {
            None
        }
    });
    let implementation = parse_macro_input!(attr as Path);
    method.semi_token = None;
    quote!(#signature {
        async move {
            #implementation(self, #(#args),*).await
        }
    })
    .into()
}