use proc_macro::{Span, TokenStream};
use quote::quote;
use syn::{spanned::Spanned, ImplItem, ItemFn, ItemImpl, ItemStruct, LitStr, Type};
macro_rules! panic_span {
($span: expr, $msg: expr) => {
return syn::Error::new($span.span(), $msg)
.to_compile_error()
.into()
};
}
#[proc_macro_attribute]
pub fn service(attrs: TokenStream, tokens: TokenStream) -> TokenStream {
let mut item = syn::parse_macro_input!(tokens as ItemFn);
let vis = &item.vis;
let mut has_pipeline = true;
let pipeline = match syn::parse::<Type>(attrs) {
Ok(t) => t,
Err(_) => {
has_pipeline = false;
Type::Verbatim(quote!(()))
}
};
let endpoint = LitStr::new(
&format!("{}", item.sig.ident.clone()),
Span::call_site().into(),
);
let name = item.sig.ident.clone();
if let None = item.sig.asyncness {
panic_span!(item.sig, "function has to be async");
}
let mut meta = quote!(());
if item.sig.inputs.len() == 1 {
item.sig
.inputs
.insert(0, syn::parse2(quote!(_: ())).unwrap())
}
match has_pipeline {
true => {
if let syn::FnArg::Typed(s) = item.sig.inputs.first_mut().unwrap() {
let ty = *s.ty.clone();
if quote!(_).to_string() == quote!(#ty).to_string() {
s.ty = Box::new(syn::parse2(quote!(::sia::comms::Typed<#pipeline>)).unwrap());
}
}
}
false => {
let mut index = 0;
item.sig.inputs.iter_mut().for_each(|s| {
match index {
0 => {
if let syn::FnArg::Typed(s) = s {
let ty = &s.ty;
meta = quote!(#ty);
}
}
1 => {
if let syn::FnArg::Typed(s) = s {
let ty = *s.ty.clone();
if quote!(_).to_string() == quote!(#ty).to_string() {
s.ty = Box::new(
syn::parse2(quote!(::sia::comms::Typed<#pipeline>)).unwrap(),
);
}
}
}
_ => panic!("functions cannot have more than two parameters"),
}
if let syn::FnArg::Typed(s) = s {
let ty = *s.ty.clone();
if quote!(_).to_string() == quote!(#ty).to_string() {
s.ty = Box::new(syn::parse2(quote!(::sia::Typed<#pipeline>)).unwrap());
}
}
index += 1;
});
}
}
quote!(
#[allow(non_camel_case_types)]
#vis struct #name;
impl ::sia::service::Service for #name {
const ENDPOINT: &'static str = #endpoint;
type Pipeline = #pipeline;
type Meta = #meta;
fn service(__sia_inner_meta: #meta) -> Box<dyn Fn(::sia::igcp::BareChannel) + Send + Sync + 'static> {
#item
::sia::service::run_metadata(__sia_inner_meta, #name)
}
}
)
.into()
}
#[proc_macro_attribute]
pub fn route(attrs: TokenStream, tokens: TokenStream) -> TokenStream {
match syn::parse::<ItemStruct>(tokens.clone()) {
Ok(s) => struct_route(attrs, s),
Err(_) => {
let p = syn::parse::<ItemImpl>(tokens).unwrap();
impl_route(attrs, p)
}
}
}
#[proc_macro_attribute]
pub fn main(_: TokenStream, item: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(item as syn::ItemFn);
let ret = &input.sig.output;
let name = &input.sig.ident;
let inputs = &input.sig.inputs;
if input.sig.asyncness.is_none() {
let msg = "the async keyword is missing from the function declaration";
return syn::Error::new_spanned(input.sig.fn_token, msg)
.to_compile_error()
.into();
} else if name == "main" && !inputs.is_empty() {
let msg = "the main function cannot accept arguments";
return syn::Error::new_spanned(&input.sig.inputs, msg)
.to_compile_error()
.into();
}
quote!(
fn main() #ret {
#input
::sia::runtime::block_on(main())
}
)
.into()
}
fn struct_route(attrs: TokenStream, item: ItemStruct) -> TokenStream {
let ident = &item.ident;
let endpoint = syn::parse::<LitStr>(attrs)
.unwrap_or(LitStr::new(&ident.clone().to_string(), ident.span()));
quote!(
#item
impl ::sia::routes::RegisterEndpoint for #ident {
const ENDPOINT: &'static str = #endpoint;
}
)
.into()
}
fn impl_route(_attrs: TokenStream, mut item: ItemImpl) -> TokenStream {
let methods = item
.items
.clone()
.into_iter()
.map(|s| match s {
ImplItem::Method(method) => Some(method),
_ => None,
})
.filter(|s| s.is_some())
.map(|s| s.unwrap());
item.attrs.clear();
let names = methods.map(|method| method.sig.ident).collect::<Vec<_>>();
let name = &item.self_ty;
quote!(
const _: () = {
#item
#(
#[::sia::service]
async fn #names(__inner: ::std::sync::Arc<#name>, channel: ::sia::Channel) -> ::sia::Result<()> {
__inner.#names(channel).await
}
)*
impl ::sia::routes::Register for #name {
type Meta = ::std::sync::Arc<Self>;
fn register(top_route: &::sia::routes::Route, meta: Self::Meta) -> ::sia::Result<()> {
#(
top_route.add_service::<#names>(meta.clone())?;
)*
Ok(())
}
}
};
).into()
}