derive_syn_parse/
variants.rs1use crate::fields::generate_fn_body;
4use proc_macro2::{Span, TokenStream};
5use quote::{quote, quote_spanned};
6use syn::parse::{Parse, ParseStream};
7use syn::spanned::Spanned;
8use syn::{Attribute, Expr, Ident, LitStr, Result, Token, Variant};
9
10pub(crate) fn generate_impl(
11 variants: impl ExactSizeIterator<Item = Variant>,
12) -> Result<TokenStream> {
13 if variants.len() == 0 {
16 return Err(syn::Error::new(
17 Span::call_site(),
18 "cannot derive `Parse` for an empty `enum`",
19 ));
20 }
21
22 let input_source = crate::parse_input();
23
24 let mut names = Vec::new();
25 let conditional_return_if_peek_success = variants
26 .into_iter()
27 .map(|var| {
28 let (name, if_condition) = impl_for_variant(&input_source, var)?;
29 names.push(name);
30 Ok(if_condition)
31 })
32 .collect::<Result<Vec<_>>>()?;
33
34 let error_msg = implemented_error_msg(names);
35
36 Ok(quote! {
37 #( #conditional_return_if_peek_success )*
38
39 Err(#input_source.error(#error_msg))
40 })
41}
42
43fn implemented_error_msg(names: Vec<LitStr>) -> String {
44 let one_of = match names.len() {
45 1 => "",
46 2 => "either ",
47 _ => "one of ",
48 };
49
50 let name_list = match names.len() {
51 0 => unreachable!(),
52 1 => names[0].value(),
53 2 => format!("{} or {}", names[0].value(), names[1].value()),
54 _ => {
55 let middle = names[1..names.len() - 1]
56 .iter()
57 .map(|name| format!(", {}", name.value()))
58 .collect::<String>();
59 format!(
60 "{}{}, or {}",
61 names[0].value(),
62 middle,
63 names.last().unwrap().value()
64 )
65 }
66 };
67
68 format!("expected {}{}", one_of, name_list)
69}
70
71enum VariantAttr {
72 Peek(PeekInfo),
73 PeekWith(PeekInfo),
74}
75
76mod kwd {
77 syn::custom_keyword!(name);
78}
79
80struct PeekInfo {
81 expr: Expr,
82 _comma: Token![,],
83 _name_token: kwd::name,
84 _eq: Token![=],
85 name: LitStr,
86}
87
88fn impl_for_variant(input_source: &Ident, variant: Variant) -> Result<(LitStr, TokenStream)> {
97 use VariantAttr::{Peek, PeekWith};
98
99 let variant_span = variant.span();
100
101 let diagnositc_name: LitStr;
102 let peek_expr = match extract_single_attr(variant_span, variant.attrs)? {
103 Peek(PeekInfo { expr, name, .. }) => {
104 diagnositc_name = name;
105 quote_spanned! {
106 expr.span()=>
107 #input_source.peek(#expr)
108 }
109 }
110 PeekWith(PeekInfo { expr, name, .. }) => {
111 diagnositc_name = name;
112 quote_spanned! {
113 expr.span()=>
114 (#expr)(#input_source)
115 }
116 }
117 };
118
119 let ident = variant.ident;
120 let variant_path = quote!( Self::#ident );
121 let parse_implementation = generate_fn_body(&variant_path, variant.fields, true)?;
122
123 let output = quote! {
124 if #peek_expr {
125 #parse_implementation;
126 }
127 };
128
129 Ok((diagnositc_name, output))
130}
131
132fn extract_single_attr(variant_span: Span, attrs: Vec<Attribute>) -> Result<VariantAttr> {
133 let mut attrs: Vec<_> = attrs
134 .into_iter()
135 .filter_map(try_as_variant_attr)
136 .collect::<Result<_>>()?;
137
138 match attrs.len() {
139 0 => Err(syn::Error::new(
140 variant_span,
141 "enum variants must have `#[peek(..)]` or `#[peek_with(..)]` to derive `Parse`",
142 )),
143 1 => Ok(attrs.remove(0)),
144 _ => Err(syn::Error::new(
145 variant_span,
146 "more than one peeking attribute is disallowed; please use `#[peek_with(..)]` for a custom function",
147 )),
148 }
149}
150
151fn try_as_variant_attr(attr: Attribute) -> Option<Result<VariantAttr>> {
152 let name = attr.path().get_ident()?.to_string();
153
154 match name.as_str() {
155 "peek" => Some(attr.parse_args().map(VariantAttr::Peek)),
156 "peek_with" => Some(attr.parse_args().map(VariantAttr::PeekWith)),
157 _ => None,
158 }
159}
160
161impl Parse for PeekInfo {
166 fn parse(input: ParseStream) -> Result<Self> {
167 Ok(PeekInfo {
168 expr: input.parse()?,
169 _comma: input.parse()?,
170 _name_token: input.parse()?,
171 _eq: input.parse()?,
172 name: input.parse()?,
173 })
174 }
175}