Skip to main content

tuna_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::{format_ident, quote};
3use syn::{
4    braced,
5    ext::IdentExt,
6    parse::{Parse, ParseStream},
7    parse_macro_input,
8    punctuated::Punctuated,
9    token::{Brace, Const},
10    Attribute, Expr, Ident, Meta, Token, Visibility,
11};
12
13struct FieldLike {
14    pub attrs: Vec<Meta>,
15    pub vis: Visibility,
16    pub constness: Option<Const>,
17    pub ident: Ident,
18    pub colon_token: Option<Token![:]>,
19    pub ty: Ident,
20    pub equals: Token![=],
21    pub defaults: Expr,
22}
23
24impl quote::ToTokens for FieldLike {
25    fn to_tokens(&self, tokens: &mut quote::__private::TokenStream) {
26        let FieldLike {
27            attrs,
28            vis,
29            constness,
30            ident,
31            colon_token,
32            ty,
33            equals,
34            defaults,
35        } = self;
36
37        let ty = format!("{}", ty);
38
39        let (variable_type, numeric) = match ty.as_str() {
40            "f32" => ("Float32", true),
41            "i32" => ("Int32", true),
42            "f64" => ("Float64", true),
43            "i64" => ("Int64", true),
44            "bool" => ("Boolean", false),
45            _ => panic!("unknown variable type"),
46        };
47
48        let ty = format_ident!("{}", variable_type);
49        let default = quote! {
50            #defaults
51        };
52
53        let out = if numeric {
54            let min = attrs
55                .iter()
56                .find(|v| v.path().is_ident("min"))
57                .map(|v| match v {
58                    Meta::NameValue(v) => v,
59                    _ => panic!("accepts only kv pairs"),
60                })
61                .map_or(quote! {None}, |v| {
62                    let lit = &v.lit;
63                    quote! { Some(#lit) }
64                });
65
66            let max = attrs
67                .iter()
68                .find(|v| v.path().is_ident("max"))
69                .map(|v| match v {
70                    Meta::NameValue(v) => v,
71                    _ => panic!("accepts only kv pairs"),
72                })
73                .map_or(quote! {None}, |v| {
74                    let lit = &v.lit;
75                    quote! { Some(#lit) }
76                });
77
78            quote! {
79                #vis #constness #ident #colon_token tuna::#ty #equals tuna::#ty::new(NAME, stringify!(#ident), #default, #min, #max)
80            }
81        } else {
82            quote! {
83                #vis #constness #ident #colon_token tuna::#ty #equals tuna::#ty::new(NAME, stringify!(#ident), #default)
84            }
85        };
86
87        tokens.extend(out);
88    }
89}
90
91impl Parse for FieldLike {
92    fn parse(input: ParseStream) -> syn::Result<Self> {
93        Ok(FieldLike {
94            attrs: input
95                .call(Attribute::parse_outer)?
96                .iter()
97                .map(|a| a.parse_meta())
98                .collect::<Result<Vec<_>, _>>()?,
99            vis: input.parse()?,
100            constness: input.parse()?,
101            ident: if input.peek(Token![_]) {
102                input.call(Ident::parse_any)
103            } else {
104                input.parse()
105            }?,
106            colon_token: Some(input.parse()?),
107            ty: input.parse()?,
108            equals: input.parse()?,
109            defaults: input.parse()?,
110        })
111    }
112}
113
114#[allow(unused)]
115struct Input {
116    visibility: Visibility,
117    struct_token: Token![mod],
118    name: Ident,
119    brace_token: Brace,
120    fields: Punctuated<FieldLike, Token![;]>,
121}
122
123impl Parse for Input {
124    fn parse(input: ParseStream) -> syn::Result<Self> {
125        let content;
126        let visibility = input.parse()?;
127        let struct_token = input.parse()?;
128        let name = input.parse()?;
129        let brace_token = braced!(content in input);
130        let fields = content.parse_terminated(FieldLike::parse)?;
131        Ok(Input {
132            visibility,
133            struct_token,
134            name,
135            brace_token,
136            fields,
137        })
138    }
139}
140
141#[proc_macro_attribute]
142pub fn tuna(_attr: TokenStream, item: TokenStream) -> TokenStream {
143    let Input { name, fields, .. } = parse_macro_input!(item as Input);
144
145    let fns = fields
146        .iter()
147        .map(|f| {
148            let name = &f.ident;
149            quote! {
150                        #name.register();
151            }
152        })
153        .collect::<Vec<_>>();
154    let res = quote!(
155        mod #name {
156            const NAME: &str = stringify!(#name);
157            #fields
158
159            pub fn register() {
160                #(#fns)*
161            }
162        }
163    );
164
165    res.into()
166}