mekena_macros/
lib.rs

1use darling::FromMeta;
2use proc_macro2::TokenStream;
3use quote::quote;
4use syn::{
5    parse_macro_input, punctuated::Punctuated, token::Paren, AttributeArgs, FnArg, Item, ItemFn,
6    Pat, ReturnType, Type, TypeTuple,
7};
8
9#[derive(Debug, FromMeta)]
10struct MainMacroArgs {
11    /// The path to Tokio.
12    #[darling(default)]
13    tokio: Option<String>,
14}
15
16#[derive(Debug, FromMeta)]
17struct NodeMacroArgs {
18    /// The path to `async_trait`.
19    #[darling(default)]
20    async_trait: Option<String>,
21}
22
23/// The `mekena::main` macro, meant to be called on the main function of a program.
24#[proc_macro_attribute]
25pub fn main(
26    args: proc_macro::TokenStream,
27    input: proc_macro::TokenStream,
28) -> proc_macro::TokenStream {
29    let attr_args = parse_macro_input!(args as AttributeArgs);
30    let function = parse_macro_input!(input as ItemFn);
31
32    let args = match MainMacroArgs::from_list(&attr_args) {
33        Ok(v) => v,
34        Err(e) => {
35            return proc_macro::TokenStream::from(e.write_errors());
36        }
37    };
38
39    let tokio: TokenStream = args
40        .tokio
41        .as_deref()
42        .unwrap_or("mekena_tokio")
43        .parse()
44        .unwrap();
45    let tokio_stringified: String = tokio.to_string().split_whitespace().collect();
46
47    let _asyncness = function
48        .sig
49        .asyncness
50        .expect("Could not find `async` marker.");
51
52    let function_output = if let ReturnType::Type(_, t) = function.sig.output {
53        *t
54    } else {
55        Type::Tuple(TypeTuple {
56            paren_token: Paren::default(),
57            elems: Punctuated::new(),
58        })
59    };
60
61    let system_name = {
62        if let Some(first) = function.sig.inputs.first() {
63            if let FnArg::Typed(pattern_type) = first {
64                if let Pat::Ident(identifier) = &*pattern_type.pat {
65                    &identifier.ident
66                } else {
67                    panic!("could not find system variable name")
68                }
69            } else {
70                panic!("could not find system variable pattern")
71            }
72        } else {
73            panic!("could not find system variable in fn sig");
74        }
75    };
76
77    let function_contents = function.block;
78
79    quote! {
80        // An unfortunate workaround for Tokio's macro, since it can't parse
81        // crate = "mekena::re::tokio".
82        use mekena::re::tokio as mekena_tokio;
83
84        #[#tokio::main(crate = #tokio_stringified)]
85        async fn main() -> #function_output {
86            let mut #system_name = mekena::system::System::new();
87            #function_contents
88        }
89    }
90    .into()
91}
92
93/// The `mekena::node` macro, meant to be called on nodes and basically expands
94/// to `mekena::re::async_trait::async_trait`.
95#[proc_macro_attribute]
96pub fn node(
97    args: proc_macro::TokenStream,
98    input: proc_macro::TokenStream,
99) -> proc_macro::TokenStream {
100    let attr_args = parse_macro_input!(args as AttributeArgs);
101    let item = parse_macro_input!(input as Item);
102
103    let args = match NodeMacroArgs::from_list(&attr_args) {
104        Ok(v) => v,
105        Err(e) => {
106            return proc_macro::TokenStream::from(e.write_errors());
107        }
108    };
109
110    let async_trait: TokenStream = args
111        .async_trait
112        .as_deref()
113        .unwrap_or("mekena::re::async_trait")
114        .parse()
115        .unwrap();
116
117    quote! {
118        #[#async_trait::async_trait(?Send)]
119        #item
120    }
121    .into()
122}