Skip to main content

gtk3_macros/
attribute_parser.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use proc_macro2::{Span, TokenStream};
4use syn::spanned::Spanned;
5use syn::{Attribute, DeriveInput, Field, Fields, Ident, LitStr, Meta, Token};
6use syn::{
7    parse::{Error, Parse, ParseStream},
8    punctuated::Punctuated,
9};
10
11mod kw {
12    syn::custom_keyword!(file);
13    syn::custom_keyword!(resource);
14    syn::custom_keyword!(string);
15
16    syn::custom_keyword!(id);
17}
18
19pub enum TemplateSource {
20    File(String),
21    Resource(String),
22    String(String),
23}
24
25impl Parse for TemplateSource {
26    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
27        let lookahead = input.lookahead1();
28        let variant = if lookahead.peek(kw::file) {
29            let _: kw::file = input.parse()?;
30            TemplateSource::File
31        } else if lookahead.peek(kw::resource) {
32            let _: kw::resource = input.parse()?;
33            TemplateSource::Resource
34        } else if lookahead.peek(kw::string) {
35            let _: kw::string = input.parse()?;
36            TemplateSource::String
37        } else {
38            return Err(lookahead.error());
39        };
40
41        let _: Token![=] = input.parse()?;
42        let lit: LitStr = input.parse()?;
43        Ok(variant(lit.value()))
44    }
45}
46
47#[derive(Debug)]
48pub enum ParseTemplateSourceError {
49    MissingAttribute(Span),
50    Parse(syn::Error),
51}
52
53impl ParseTemplateSourceError {
54    pub fn into_compile_error_with_message(self, message: &str) -> TokenStream {
55        match self {
56            err @ Self::MissingAttribute(span) => {
57                syn::Error::new(span, format!("{err}: {message}"))
58            }
59            Self::Parse(err) => syn::Error::new(err.span(), format!("{err}: {message}")),
60        }
61        .into_compile_error()
62    }
63}
64
65impl std::fmt::Display for ParseTemplateSourceError {
66    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67        match self {
68            Self::MissingAttribute(_) => write!(f, "Missing 'template' attribute"),
69            Self::Parse(err) => write!(f, "{}", err),
70        }
71    }
72}
73
74impl std::error::Error for ParseTemplateSourceError {
75    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
76        match self {
77            Self::MissingAttribute(_) => None,
78            Self::Parse(err) => Some(err),
79        }
80    }
81}
82
83pub fn parse_template_source(
84    input: &DeriveInput,
85) -> Result<TemplateSource, ParseTemplateSourceError> {
86    input
87        .attrs
88        .iter()
89        .find(|a| a.path().is_ident("template"))
90        .ok_or(ParseTemplateSourceError::MissingAttribute(Span::call_site()))?
91        .parse_args()
92        .map_err(ParseTemplateSourceError::Parse)
93}
94
95pub enum FieldAttributeArg {
96    Id(String),
97}
98
99impl Parse for FieldAttributeArg {
100    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
101        let lookahead = input.lookahead1();
102        if lookahead.peek(kw::id) {
103            let _: kw::id = input.parse()?;
104            let _: Token![=] = input.parse()?;
105            let lit: LitStr = input.parse()?;
106            Ok(Self::Id(lit.value()))
107        } else {
108            Err(lookahead.error())
109        }
110    }
111}
112
113#[derive(Debug)]
114pub enum FieldAttributeType {
115    TemplateChild,
116}
117
118pub struct FieldAttribute {
119    pub ty: FieldAttributeType,
120    pub args: Vec<FieldAttributeArg>,
121}
122
123pub struct AttributedField {
124    pub ident: Ident,
125    pub attr: FieldAttribute,
126}
127
128fn parse_field_attr_args(attr: &Attribute) -> Result<Vec<FieldAttributeArg>, Error> {
129    let mut field_attribute_args = Vec::new();
130    match &attr.meta {
131        Meta::List(list) => {
132            let args =
133                list.parse_args_with(Punctuated::<FieldAttributeArg, Token![,]>::parse_terminated)?;
134            for arg in args {
135                for prev_arg in &field_attribute_args {
136                    // Comparison of enum variants, not data
137                    if std::mem::discriminant(prev_arg) == std::mem::discriminant(&arg) {
138                        return Err(Error::new(
139                            attr.span(),
140                            "two instances of the same attribute \
141                            argument, each argument must be specified only once",
142                        ));
143                    }
144                }
145                field_attribute_args.push(arg);
146            }
147        }
148        Meta::Path(_) => (),
149        meta => {
150            return Err(Error::new(
151                meta.span(),
152                "invalid attribute argument type, expected `name = value` list or nothing",
153            ));
154        }
155    }
156
157    Ok(field_attribute_args)
158}
159
160fn parse_field(field: &Field) -> Result<Option<AttributedField>, Error> {
161    let field_attrs = &field.attrs;
162    let ident = match &field.ident {
163        Some(ident) => ident,
164        None => return Err(Error::new(field.span(), "expected identifier")),
165    };
166
167    let mut attr = None;
168
169    for field_attr in field_attrs {
170        let span = field_attr.span();
171        let ty = if field_attr.path().is_ident("template_child") {
172            Some(FieldAttributeType::TemplateChild)
173        } else {
174            None
175        };
176
177        if let Some(ty) = ty {
178            let args = parse_field_attr_args(field_attr)?;
179
180            if attr.is_none() {
181                attr = Some(FieldAttribute { ty, args })
182            } else {
183                return Err(Error::new(
184                    span,
185                    "multiple attributes on the same field are not supported",
186                ));
187            }
188        }
189    }
190
191    if let Some(attr) = attr {
192        Ok(Some(AttributedField {
193            ident: ident.clone(),
194            attr,
195        }))
196    } else {
197        Ok(None)
198    }
199}
200
201pub fn parse_fields(fields: &Fields) -> Result<Vec<AttributedField>, Error> {
202    let mut attributed_fields = Vec::new();
203
204    for field in fields {
205        if !field.attrs.is_empty()
206            && let Some(attributed_field) = parse_field(field)?
207        {
208            attributed_fields.push(attributed_field)
209        }
210    }
211
212    Ok(attributed_fields)
213}