Skip to main content

token_parser_derive/
lib.rs

1#![deny(missing_docs)]
2
3/*!
4Derive macros to automatically implement the `Parsable` trait from `token-parser`.
5
6Supports:
7
8- Tuple structs: parsed positionally (`(value1 value2 …)`).
9- Named structs: parsed with one sub-list per field in any order (`((name1 value1) (name2 value2) …)`). Requires the struct to implement `Default`. Unknown field names yield `UnknownField`. Last value wins on duplicates. Missing fields keep their default.
10- Unit structs: parsed as the empty list `()`.
11- `SymbolParsable`: parses a tuple struct's fields from the symbol string via `FromStr`.
12*/
13
14use proc_macro2::TokenStream;
15use quote::quote;
16use syn::{
17    Data, DataStruct, DeriveInput, Fields, FieldsNamed, FieldsUnnamed, GenericParam, Generics,
18    Ident, TypeParamBound, parse_macro_input, parse_quote,
19};
20
21/// Derive the `Parsable` trait.
22///
23/// # Panics
24///
25/// Panics on union or enum types.
26#[proc_macro_derive(Parsable)]
27pub fn derive_default_parsable(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
28    derive_parsable(input, false)
29}
30
31/// Derive the `Parsable` trait by parsing unnamed fields using the `FromStr` trait.
32///
33/// Parses the entire `name` string into each field.
34///
35/// # Panics
36///
37/// Panics if the input is not a tuple struct.
38#[proc_macro_derive(SymbolParsable)]
39pub fn derive_symbol_parsable(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
40    derive_parsable(input, true)
41}
42
43fn add_trait_bounds(mut generics: Generics, bound: TypeParamBound) -> Generics {
44    for param in &mut generics.params {
45        if let GenericParam::Type(type_param) = param {
46            type_param.bounds.push(bound.clone());
47        }
48    }
49    generics
50}
51
52fn derive_parsable(input: proc_macro::TokenStream, symbol: bool) -> proc_macro::TokenStream {
53    let input = parse_macro_input!(input as DeriveInput);
54    let name = input.ident;
55
56    let trait_bound = if symbol {
57        parse_quote!(::std::str::FromStr)
58    } else {
59        parse_quote!(::token_parser::Parsable<C>)
60    };
61
62    let (_, ty_generics, _) = input.generics.split_for_impl();
63
64    let mut generics = add_trait_bounds(input.generics.clone(), trait_bound);
65    generics
66        .params
67        .push(parse_quote!(C: ::token_parser::Context));
68    let (impl_generics, _, where_clause) = generics.split_for_impl();
69
70    let body = match input.data {
71        Data::Struct(data) => struct_body(&name, data, symbol),
72        Data::Enum(_) => panic!("Deriving Parsable is not yet supported for enums"),
73        Data::Union(_) => panic!("Deriving Parsable is not supported for unions"),
74    };
75
76    let expanded = quote! {
77        impl #impl_generics ::token_parser::Parsable<C> for #name #ty_generics #where_clause {
78            #body
79        }
80    };
81
82    proc_macro::TokenStream::from(expanded)
83}
84
85fn struct_body(name: &Ident, data: DataStruct, symbol: bool) -> TokenStream {
86    if symbol {
87        let Fields::Unnamed(fields) = data.fields else {
88            panic!("SymbolParsable requires a tuple struct");
89        };
90        return symbol_tuple(name, &fields);
91    }
92    match data.fields {
93        Fields::Unnamed(fields) => parse_list_tuple(name, &fields),
94        Fields::Named(fields) => parse_list_named(name, &fields),
95        Fields::Unit => quote! {
96            fn parse_list(_parser: &mut ::token_parser::Parser, _context: &C) -> ::token_parser::Result<Self> {
97                Ok(Self)
98            }
99        },
100    }
101}
102
103fn parse_list_tuple(name: &Ident, fields: &FieldsUnnamed) -> TokenStream {
104    let exprs = fields.unnamed.iter().enumerate().map(|(position, _)| {
105        let description = format!("field {position} of `{name}`");
106        quote! {
107            parser
108                .parse_next(context)
109                .map_err(|error| error.context(#description))?
110        }
111    });
112    quote! {
113        fn parse_list(parser: &mut ::token_parser::Parser, context: &C) -> ::token_parser::Result<Self> {
114            Ok(Self( #( #exprs, )* ))
115        }
116    }
117}
118
119fn parse_list_named(name: &Ident, fields: &FieldsNamed) -> TokenStream {
120    let names: Vec<&Ident> = fields
121        .named
122        .iter()
123        .map(|f| f.ident.as_ref().unwrap())
124        .collect();
125    let labels: Vec<String> = names.iter().map(|n| Ident::to_string(n)).collect();
126
127    let arms = names.iter().zip(labels.iter()).map(|(field, label)| {
128        let description = format!("field `{label}` of `{name}`");
129        quote! {
130            #label => {
131                result.#field = sub
132                    .parse_next(context)
133                    .map_err(|error| error.context(#description))?;
134            }
135        }
136    });
137
138    let type_description = format!("`{name}`");
139    quote! {
140        fn parse_list(parser: &mut ::token_parser::Parser, context: &C) -> ::token_parser::Result<Self> {
141            let mut result = <Self as ::std::default::Default>::default();
142            for element in parser {
143                let mut sub = element?;
144                let field_name: Box<str> = sub.parse_next(context)?;
145                match field_name.as_ref() {
146                    #( #arms )*
147                    _ => {
148                        return Err(::token_parser::Error::from(
149                            ::token_parser::ErrorKind::UnknownField(field_name),
150                        )
151                        .at(sub.span())
152                        .context(#type_description));
153                    }
154                }
155            }
156            Ok(result)
157        }
158    }
159}
160
161fn symbol_tuple(name: &Ident, fields: &FieldsUnnamed) -> TokenStream {
162    let exprs = fields.unnamed.iter().map(|field| {
163        let field_type = &field.ty;
164        quote! {
165            name.parse().map_err(|_| ::token_parser::ErrorKind::StringParsing(stringify!(#field_type)))?
166        }
167    });
168    quote! {
169        fn parse_symbol(name: Box<str>, _span: ::token_parser::Span, _context: &C) -> ::token_parser::Result<Self> {
170            Ok(#name( #( #exprs, )* ))
171        }
172    }
173}