tuna-macros 0.1.0

Macros used by tuna.
Documentation
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{
    braced,
    ext::IdentExt,
    parse::{Parse, ParseStream},
    parse_macro_input,
    punctuated::Punctuated,
    token::{Brace, Const},
    Attribute, Expr, Ident, Meta, Token, Visibility,
};

struct FieldLike {
    pub attrs: Vec<Meta>,
    pub vis: Visibility,
    pub constness: Option<Const>,
    pub ident: Ident,
    pub colon_token: Option<Token![:]>,
    pub ty: Ident,
    pub equals: Token![=],
    pub defaults: Expr,
}

impl quote::ToTokens for FieldLike {
    fn to_tokens(&self, tokens: &mut quote::__private::TokenStream) {
        let FieldLike {
            attrs,
            vis,
            constness,
            ident,
            colon_token,
            ty,
            equals,
            defaults,
        } = self;

        let ty = format!("{}", ty);

        let (variable_type, numeric) = match ty.as_str() {
            "f32" => ("Float32", true),
            "i32" => ("Int32", true),
            "f64" => ("Float64", true),
            "i64" => ("Int64", true),
            "bool" => ("Boolean", false),
            _ => panic!("unknown variable type"),
        };

        let ty = format_ident!("{}", variable_type);
        let default = quote! {
            #defaults
        };

        let out = if numeric {
            let min = attrs
                .iter()
                .find(|v| v.path().is_ident("min"))
                .map(|v| match v {
                    Meta::NameValue(v) => v,
                    _ => panic!("accepts only kv pairs"),
                })
                .map_or(quote! {None}, |v| {
                    let lit = &v.lit;
                    quote! { Some(#lit) }
                });

            let max = attrs
                .iter()
                .find(|v| v.path().is_ident("max"))
                .map(|v| match v {
                    Meta::NameValue(v) => v,
                    _ => panic!("accepts only kv pairs"),
                })
                .map_or(quote! {None}, |v| {
                    let lit = &v.lit;
                    quote! { Some(#lit) }
                });

            quote! {
                #vis #constness #ident #colon_token tuna::#ty #equals tuna::#ty::new(NAME, stringify!(#ident), #default, #min, #max)
            }
        } else {
            quote! {
                #vis #constness #ident #colon_token tuna::#ty #equals tuna::#ty::new(NAME, stringify!(#ident), #default)
            }
        };

        tokens.extend(out);
    }
}

impl Parse for FieldLike {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        Ok(FieldLike {
            attrs: input
                .call(Attribute::parse_outer)?
                .iter()
                .map(|a| a.parse_meta())
                .collect::<Result<Vec<_>, _>>()?,
            vis: input.parse()?,
            constness: input.parse()?,
            ident: if input.peek(Token![_]) {
                input.call(Ident::parse_any)
            } else {
                input.parse()
            }?,
            colon_token: Some(input.parse()?),
            ty: input.parse()?,
            equals: input.parse()?,
            defaults: input.parse()?,
        })
    }
}

#[allow(unused)]
struct Input {
    visibility: Visibility,
    struct_token: Token![mod],
    name: Ident,
    brace_token: Brace,
    fields: Punctuated<FieldLike, Token![;]>,
}

impl Parse for Input {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let content;
        let visibility = input.parse()?;
        let struct_token = input.parse()?;
        let name = input.parse()?;
        let brace_token = braced!(content in input);
        let fields = content.parse_terminated(FieldLike::parse)?;
        Ok(Input {
            visibility,
            struct_token,
            name,
            brace_token,
            fields,
        })
    }
}

#[proc_macro_attribute]
pub fn tuna(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let Input { name, fields, .. } = parse_macro_input!(item as Input);

    let fns = fields
        .iter()
        .map(|f| {
            let name = &f.ident;
            quote! {
                        #name.register();
            }
        })
        .collect::<Vec<_>>();
    let res = quote!(
        mod #name {
            const NAME: &str = stringify!(#name);
            #fields

            pub fn register() {
                #(#fns)*
            }
        }
    );

    res.into()
}