token_parser_derive/
lib.rs1#![deny(missing_docs)]
2
3use 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#[proc_macro_derive(Parsable)]
27pub fn derive_default_parsable(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
28 derive_parsable(input, false)
29}
30
31#[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
52#[expect(clippy::panic, reason = "proc-macro compile-time error signal")]
53fn derive_parsable(input: proc_macro::TokenStream, symbol: bool) -> proc_macro::TokenStream {
54 let input = parse_macro_input!(input as DeriveInput);
55 let name = input.ident;
56
57 let trait_bound = if symbol {
58 parse_quote!(::std::str::FromStr)
59 } else {
60 parse_quote!(::token_parser::Parsable<C>)
61 };
62
63 let (_, ty_generics, _) = input.generics.split_for_impl();
64
65 let mut generics = add_trait_bounds(input.generics.clone(), &trait_bound);
66 generics
67 .params
68 .push(parse_quote!(C: ::token_parser::Context));
69 let (impl_generics, _, where_clause) = generics.split_for_impl();
70
71 let body = match input.data {
72 Data::Struct(data) => struct_body(&name, data, symbol),
73 Data::Enum(_) => panic!("Deriving Parsable is not yet supported for enums"),
74 Data::Union(_) => panic!("Deriving Parsable is not supported for unions"),
75 };
76
77 let expanded = quote! {
78 impl #impl_generics ::token_parser::Parsable<C> for #name #ty_generics #where_clause {
79 #body
80 }
81 };
82
83 proc_macro::TokenStream::from(expanded)
84}
85
86#[expect(clippy::panic, reason = "proc-macro compile-time error signal")]
87fn struct_body(name: &Ident, data: DataStruct, symbol: bool) -> TokenStream {
88 if symbol {
89 let Fields::Unnamed(fields) = data.fields else {
90 panic!("SymbolParsable requires a tuple struct");
91 };
92 return symbol_tuple(name, &fields);
93 }
94 match data.fields {
95 Fields::Unnamed(fields) => parse_list_tuple(name, &fields),
96 Fields::Named(fields) => parse_list_named(name, &fields),
97 Fields::Unit => quote! {
98 fn parse_list(_parser: &mut ::token_parser::Parser, _context: &C) -> ::token_parser::Result<Self> {
99 Ok(Self)
100 }
101 },
102 }
103}
104
105fn parse_list_tuple(name: &Ident, fields: &FieldsUnnamed) -> TokenStream {
106 let exprs = fields.unnamed.iter().enumerate().map(|(position, _)| {
107 let description = format!("field {position} of `{name}`");
108 quote! {
109 parser
110 .parse_next(context)
111 .map_err(|error| error.context(#description))?
112 }
113 });
114 quote! {
115 fn parse_list(parser: &mut ::token_parser::Parser, context: &C) -> ::token_parser::Result<Self> {
116 Ok(Self( #( #exprs, )* ))
117 }
118 }
119}
120
121fn parse_list_named(name: &Ident, fields: &FieldsNamed) -> TokenStream {
122 let names: Vec<&Ident> = fields
123 .named
124 .iter()
125 .filter_map(|f| f.ident.as_ref())
126 .collect();
127 let labels: Vec<String> = names.iter().map(|n| Ident::to_string(n)).collect();
128
129 let arms = names.iter().zip(labels.iter()).map(|(field, label)| {
130 let description = format!("field `{label}` of `{name}`");
131 quote! {
132 #label => {
133 result.#field = sub
134 .parse_next(context)
135 .map_err(|error| error.context(#description))?;
136 }
137 }
138 });
139
140 let type_description = format!("`{name}`");
141 quote! {
142 fn parse_list(parser: &mut ::token_parser::Parser, context: &C) -> ::token_parser::Result<Self> {
143 let mut result = <Self as ::std::default::Default>::default();
144 for element in parser {
145 let mut sub = element?;
146 let field_name: Box<str> = sub.parse_next(context)?;
147 match field_name.as_ref() {
148 #( #arms )*
149 _ => {
150 return Err(::token_parser::Error::from(
151 ::token_parser::ErrorKind::UnknownField(field_name),
152 )
153 .at(sub.span())
154 .context(#type_description));
155 }
156 }
157 }
158 Ok(result)
159 }
160 }
161}
162
163fn symbol_tuple(name: &Ident, fields: &FieldsUnnamed) -> TokenStream {
164 let exprs = fields.unnamed.iter().map(|field| {
165 let field_type = &field.ty;
166 quote! {
167 name.parse().map_err(|error| ::token_parser::ErrorKind::StringParsing {
168 type_name: stringify!(#field_type),
169 source: Box::new(error),
170 })?
171 }
172 });
173 quote! {
174 fn parse_symbol(name: Box<str>, _span: ::token_parser::Span, _context: &C) -> ::token_parser::Result<Self> {
175 Ok(#name( #( #exprs, )* ))
176 }
177 }
178}