dremoc-macro 0.1.6

Procedural macros for dremoc
Documentation
use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, ToTokens, TokenStreamExt};
use syn::{parse::Parse, Ident, Token, Type};

use crate::utils::{to_snake_case, to_snake_case_ext};

use super::method::ServiceMethod;

pub mod kw {
    syn::custom_keyword!(readonly);
    syn::custom_keyword!(unwatched);
    syn::custom_keyword!(err);
}

pub struct ServiceState {
    pub access_level: ServiceStateAccessLevel,
    pub ident: Ident,
    pub colon_token: Token![:],
    pub ty: Type,
    pub err_ty: Option<Type>,
    pub get_method: Option<ServiceMethod>,
    pub set_method: Option<ServiceMethod>,
    pub subscribe_method: Option<ServiceMethod>,
}

impl ServiceState {
    pub fn to_state_trait_def_item(&self, tokens: &mut TokenStream2) {
        if let Some(get_method) = &self.get_method {
            get_method.to_tokens(tokens);
        }

        if let Some(set_method) = &self.set_method {
            set_method.to_tokens(tokens);
        }

        if let Some(subscribe_method) = &self.subscribe_method {
            subscribe_method.to_tokens(tokens);
        }
    }

    pub fn methods(&self) -> impl Iterator<Item = &ServiceMethod> {
        self.get_method
            .iter()
            .chain(self.set_method.iter())
            .chain(self.subscribe_method.iter())
    }

    pub fn stateful_auto_impl_methods(&self) -> TokenStream2 {
        let Self { ty, err_ty, .. } = &self;
        let mut tokens = quote! {};

        let field_ident = to_snake_case(&self.ident);
        let err_ty = err_ty
            .clone()
            .unwrap_or(syn::parse2(quote! { ::dremoc::remoc::rtc::CallError }).unwrap());

        if let Some(get_method) = &self.get_method {
            let method_ident = &get_method.sig.ident;
            tokens.append_all(quote! {
                #[inline(always)]
                async fn #method_ident(&self) -> Result<#ty, #err_ty> {
                    Ok(self.get_state().#field_ident.remote_get())
                }
            });
        }

        if let Some(set_method) = &self.set_method {
            let method_ident = &set_method.sig.ident;
            tokens.append_all(quote! {
                #[inline(always)]
                async fn #method_ident(&mut self, value: #ty) -> Result<(), #err_ty> {
                    Ok(self.get_state_mut().#field_ident.remote_set(value))
                }
            });
        }

        if let Some(subscribe_method) = &self.subscribe_method {
            let method_ident = &subscribe_method.sig.ident;
            tokens.append_all(quote! {
                #[inline(always)]
                async fn #method_ident(&self) -> Result<::dremoc::sync::watch::Receiver<#ty>, #err_ty> {
                    Ok(self.get_state().#field_ident.subscribe())
                }
            });
        }

        tokens
    }

    pub fn function_argument(&self) -> TokenStream2 {
        let Self { ident, ty, .. } = &self;
        let ident = to_snake_case(&ident);

        quote! { #ident: #ty }
    }

    pub fn ctor_argument(&self) -> TokenStream2 {
        let Self { ident, ty, .. } = &self;
        let ident = to_snake_case(&ident);

        let read_access = self.access_level.read_access();
        let write_access = self.access_level.write_access();
        let watch_access = self.access_level.watch_access();

        quote! { #ident: ::dremoc::service::State::<#ty, #read_access, #write_access, #watch_access>::new(#ident) }
    }

    pub fn state_set_field(&self) -> TokenStream2 {
        let Self { ident, ty, .. } = &self;

        let read_access = self.access_level.read_access();
        let write_access = self.access_level.write_access();
        let watch_access = self.access_level.watch_access();

        let ident = to_snake_case(&ident);
        quote! { #ident: ::dremoc::service::State<#ty, #read_access, #write_access, #watch_access> }
    }

    fn with_service_methods(mut self) -> Self {
        let Self {
            ident, ty, err_ty, ..
        } = &self;

        let err_ty = err_ty
            .clone()
            .unwrap_or(syn::parse2(quote! { ::dremoc::remoc::rtc::CallError }).unwrap());

        let get_method = if self.access_level.read_access() {
            let method_ident = to_snake_case_ext(ident, "get", "");
            Some(
                syn::parse2(quote! { async fn #method_ident(&self) -> Result<#ty, #err_ty>; })
                    .unwrap(),
            )
        } else {
            None
        };

        let set_method = if self.access_level.write_access() {
            let method_ident = to_snake_case_ext(ident, "set", "");
            Some(
                syn::parse2(
                    quote! { async fn #method_ident(&mut self, value: #ty) -> Result<(), #err_ty>; },
                )
                .unwrap(),
            )
        } else {
            None
        };

        let subscribe_method = if self.access_level.watch_access() {
            let method_ident = to_snake_case_ext(ident, "subscribe", "");
            Some(
                syn::parse2(
                    quote! { async fn #method_ident(&self) -> Result<::dremoc::sync::watch::Receiver<#ty>, #err_ty>; },
                )
                .unwrap(),
            )
        } else {
            None
        };

        self.get_method = get_method;
        self.set_method = set_method;
        self.subscribe_method = subscribe_method;

        self
    }
}

impl Parse for ServiceState {
    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
        let access_level = input.parse()?;
        let ident = input.parse()?;
        let colon_token = input.parse()?;
        let ty = input.parse()?;

        let err_ty = if input.peek(kw::err) {
            let _err_token = input.parse::<kw::err>()?;
            let content;
            syn::parenthesized!(content in input);
            Some(content.parse()?)
        } else {
            None
        };

        Ok(Self {
            access_level,
            ident,
            colon_token,
            ty,
            err_ty,
            get_method: None,
            set_method: None,
            subscribe_method: None,
        }
        .with_service_methods())
    }
}

impl ToTokens for ServiceState {
    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
        self.access_level.to_tokens(tokens);
        self.ident.to_tokens(tokens);
        self.colon_token.to_tokens(tokens);
        self.ty.to_tokens(tokens);
        self.err_ty.to_tokens(tokens);
    }
}

pub enum ServiceStateAccessLevel {
    ReadWrite {
        unwatched_token: Option<kw::unwatched>,
    },
    Readonly {
        readonly_token: kw::readonly,
        unwatched_token: Option<kw::unwatched>,
    },
}

impl ServiceStateAccessLevel {
    pub fn watch_access(&self) -> bool {
        match self {
            Self::ReadWrite {
                unwatched_token: unwatched,
            } => unwatched.is_none(),
            Self::Readonly {
                unwatched_token: unwatched,
                ..
            } => unwatched.is_none(),
        }
    }

    pub fn read_access(&self) -> bool {
        match self {
            Self::ReadWrite { .. } => true,
            Self::Readonly { .. } => true,
        }
    }

    pub fn write_access(&self) -> bool {
        match self {
            Self::ReadWrite { .. } => true,
            Self::Readonly { .. } => false,
        }
    }
}

impl Parse for ServiceStateAccessLevel {
    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
        if input.peek(kw::readonly) {
            let readonly_token = input.parse::<kw::readonly>()?;
            Ok(Self::Readonly {
                readonly_token,
                unwatched_token: input.parse()?,
            })
        } else {
            Ok(Self::ReadWrite {
                unwatched_token: input.parse()?,
            })
        }
    }
}

impl ToTokens for ServiceStateAccessLevel {
    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
        match self {
            Self::ReadWrite { unwatched_token } => {
                if let Some(unwatched_token) = unwatched_token {
                    unwatched_token.to_tokens(tokens);
                }
            }
            Self::Readonly {
                readonly_token,
                unwatched_token,
            } => {
                readonly_token.to_tokens(tokens);
                if let Some(unwatched_token) = unwatched_token {
                    unwatched_token.to_tokens(tokens);
                }
            }
        }
    }
}