Skip to main content

pspp_derive/
lib.rs

1// PSPP - a program for statistical analysis.
2// Copyright (C) 2025 Free Software Foundation, Inc.
3//
4// This program is free software: you can redistribute it and/or modify it under
5// the terms of the GNU General Public License as published by the Free Software
6// Foundation, either version 3 of the License, or (at your option) any later
7// version.
8//
9// This program is distributed in the hope that it will be useful, but WITHOUT
10// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11// FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
12// details.
13//
14// You should have received a copy of the GNU General Public License along with
15// this program.  If not, see <http://www.gnu.org/licenses/>.
16
17use proc_macro::TokenStream;
18use proc_macro2::{Literal, TokenStream as TokenStream2};
19use quote::{format_ident, quote, ToTokens};
20use syn::{spanned::Spanned, Attribute, DataEnum, DataStruct, DeriveInput, Error, Fields, Token};
21
22#[proc_macro_derive(FromTokens, attributes(pspp))]
23pub fn from_tokens_derive(input: TokenStream) -> TokenStream {
24    // Construct a representation of Rust code as a syntax tree
25    // that we can manipulate
26    let ast: DeriveInput = syn::parse(input).unwrap();
27
28    match parse_derive_input(ast) {
29        Ok(output) => output.into(),
30        Err(error) => error.to_compile_error().into(),
31    }
32}
33
34fn parse_derive_input(ast: DeriveInput) -> Result<TokenStream2, Error> {
35    match &ast.data {
36        syn::Data::Enum(e) => derive_enum(&ast, e),
37        syn::Data::Struct(s) => derive_struct(&ast, s),
38        syn::Data::Union(_) => Err(Error::new(
39            ast.span(),
40            "Only struct and enums may currently be derived",
41        )),
42    }
43}
44
45fn derive_enum(ast: &DeriveInput, e: &DataEnum) -> Result<TokenStream2, Error> {
46    let struct_attrs = StructAttrs::parse(&ast.attrs)?;
47    let mut body = TokenStream2::new();
48    let name = &ast.ident;
49    let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
50    for variant in &e.variants {
51        let ident = &variant.ident;
52        let field_attrs = FieldAttrs::parse(&variant.attrs)?;
53        let selector = field_attrs.selector.unwrap_or(struct_attrs.selector);
54        let construction =
55            construct_fields(&variant.fields, quote! { #name::#ident }, selector, None);
56        let fnname = format_ident!("construct_{ident}");
57        body.extend(quote! {
58            fn #fnname #impl_generics(input: &TokenSlice) -> ParseResult<#name #ty_generics> #where_clause { let input = input.clone();  #construction }
59        });
60    }
61
62    for variant in &e.variants {
63        let ident = &variant.ident;
64        let fnname = format_ident!("construct_{ident}");
65        let field_attrs = FieldAttrs::parse(&variant.attrs)?;
66        let selector = field_attrs.selector.unwrap_or(struct_attrs.selector);
67        if selector {
68            let ident_string = ident.to_string();
69            let select_expr = if let Some(syntax) = &field_attrs.syntax {
70                quote! { input.skip_syntax(#syntax) }
71            } else if ident_string.eq_ignore_ascii_case("all") {
72                quote! { input.skip(&Token::Punct(Punct::All))}
73            } else {
74                quote! { input.skip_keyword(#ident_string)}
75            };
76            body.extend(quote! { if let Some(input) = #select_expr { return #fnname(&input); } });
77        } else {
78            body.extend(quote! {
79                let result = #fnname(&input);
80                if let Ok(_) | Err(ParseError::Error(_)) = result {
81                    return result;
82                }
83            });
84        }
85    }
86    body.extend(quote! { Err(ParseError::Mismatch(input.error("Syntax error.").into())) });
87
88    let output = quote! {
89        impl #impl_generics FromTokens for #name #ty_generics #where_clause {
90            fn from_tokens(input: &TokenSlice) -> ParseResult<Self> {
91                #body
92            }
93        }
94    };
95    //println!("{output}");
96    Ok(output)
97}
98
99fn construct_fields(
100    fields: &Fields,
101    name: impl ToTokens,
102    mismatch_to_error: bool,
103    syntax: Option<&Literal>,
104) -> impl ToTokens {
105    let mut construction = TokenStream2::new();
106    if !fields.is_empty() {
107        construction
108            .extend(quote! { let mut diagnostics = crate::command::Diagnostics::default(); });
109    }
110    let convert = if mismatch_to_error {
111        quote! { .mismatch_to_error() }
112    } else {
113        quote! {}
114    };
115    for (index, _field) in fields.iter().enumerate() {
116        let varname = format_ident!("field{index}");
117        construction
118            .extend(quote! { let (#varname, input) = FromTokens::from_tokens(&input) #convert ?.take_diagnostics(&mut diagnostics); });
119    }
120    match fields {
121        Fields::Named(named) => {
122            let mut body = TokenStream2::new();
123            for (index, field) in named.named.iter().enumerate() {
124                let varname = format_ident!("field{index}");
125                let field_name = &field.ident;
126                body.extend(quote! { #field_name: #varname, });
127            }
128            quote! { #construction Ok(Parsed::new(#name { #body }, input, diagnostics)) }
129        }
130        Fields::Unnamed(unnamed) => {
131            let mut body = TokenStream2::new();
132            for (index, _field) in unnamed.unnamed.iter().enumerate() {
133                let varname = format_ident!("field{index}");
134                body.extend(quote! { #varname, });
135            }
136            quote! { #construction Ok(Parsed::new(#name ( #body ), input, diagnostics)) }
137        }
138        Fields::Unit => {
139            if let Some(syntax) = syntax {
140                quote! { crate::command::parse_syntax(input, #syntax).map(|p| p.map(|()| #name)) }
141            } else {
142                quote! { Ok(Parsed::ok(#name, input)) }
143            }
144        }
145    }
146}
147
148fn derive_struct(ast: &DeriveInput, s: &DataStruct) -> Result<TokenStream2, Error> {
149    let struct_attrs = StructAttrs::parse(&ast.attrs)?;
150    let name = &ast.ident;
151    let syntax = if let Some(syntax) = struct_attrs.syntax.as_ref() {
152        syntax.clone()
153    } else {
154        Literal::string(&name.to_string())
155    };
156    let construction = construct_fields(&s.fields, quote! {#name}, false, Some(&syntax));
157    let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
158    let output = quote! {
159        impl #impl_generics FromTokens for #name #ty_generics #where_clause {
160            fn from_tokens(input: &TokenSlice) -> ParseResult<Self> {
161                #construction
162            }
163        }
164    };
165    //println!("{output}");
166    Ok(output)
167}
168
169#[derive(Default)]
170struct FieldAttrs {
171    syntax: Option<Literal>,
172    selector: Option<bool>,
173}
174
175impl FieldAttrs {
176    fn parse(attributes: &[Attribute]) -> Result<Self, Error> {
177        let mut field_attrs = Self::default();
178        for attr in attributes {
179            if !attr.path().is_ident("pspp") {
180                continue;
181            }
182            attr.parse_nested_meta(|meta| {
183                if meta.path.is_ident("syntax") {
184                    meta.input.parse::<Token![=]>()?;
185                    let syntax = meta.input.parse::<Literal>()?;
186                    field_attrs.syntax = Some(syntax);
187                } else if meta.path.is_ident("no_selector") {
188                    field_attrs.selector = Some(false);
189                } else {
190                    return Err(Error::new(meta.path.span(), "Unknown attribute"));
191                }
192                Ok(())
193            })?;
194        }
195        Ok(field_attrs)
196    }
197}
198
199struct StructAttrs {
200    syntax: Option<Literal>,
201    selector: bool,
202}
203
204impl Default for StructAttrs {
205    fn default() -> Self {
206        Self {
207            syntax: None,
208            selector: true,
209        }
210    }
211}
212
213impl StructAttrs {
214    fn parse(attributes: &[Attribute]) -> Result<Self, Error> {
215        //println!("{:?}", &attributes);
216        let mut field_attrs = Self::default();
217        for attr in attributes {
218            if !attr.path().is_ident("pspp") {
219                continue;
220            }
221            attr.parse_nested_meta(|meta| {
222                if meta.path.is_ident("syntax") {
223                    meta.input.parse::<Token![=]>()?;
224                    let syntax = meta.input.parse::<Literal>()?;
225                    field_attrs.syntax = Some(syntax);
226                } else if meta.path.is_ident("no_selector") {
227                    field_attrs.selector = false;
228                } else {
229                    return Err(Error::new(meta.path.span(), "Unknown attribute"));
230                }
231                Ok(())
232            })?;
233        }
234        Ok(field_attrs)
235    }
236}