1use alloc::{string::ToString, vec::Vec};
4
5use proc_macro2::{Span, TokenStream};
6use quote::ToTokens;
7
8use syn::{
9 Expr, Ident, LitInt, LitStr, Path, Token,
10 parse::{Parse, ParseStream},
11 punctuated::Punctuated,
12 token::Paren,
13};
14
15use super::ErrorList;
16
17#[derive(Clone, Debug, PartialEq, Eq, Hash)]
25pub struct Param {
26 pub name: Option<Ident>,
31
32 pub kind: ParamKind,
34}
35
36impl Param {
37 #[inline]
39 #[must_use]
40 pub const fn lone(kind: ParamKind) -> Self {
41 Self { name: None, kind }
42 }
43
44 #[inline]
46 #[must_use]
47 pub const fn identified(name: Ident, kind: ParamKind) -> Self {
48 Self { name: Some(name), kind }
49 }
50}
51
52impl Param {
53 pub fn classify<'a>(iter: impl IntoIterator<Item = &'a syn::Attribute>) -> syn::Result<(Vec<Self>, Vec<&'a syn::Attribute>)> {
56 let attr_iter = iter.into_iter();
57
58 let (attr_len, ..) = attr_iter.size_hint();
59
60 let mut param_list = Vec::with_capacity(attr_len);
61
62 let mut attr_list = Vec::new();
63
64 let mut error_list = ErrorList::empty();
65
66 for attr in attr_iter {
67 if let Some(ident) = attr.path().get_ident() {
68 if ident == "error" {
69 match attr.parse_args_with(Param::parse) {
70 Ok(param) => param_list.push(param),
71 Err(error) => error_list.append(error),
72 }
73 } else {
74 attr_list.push(attr);
75 }
76 } else {
77 attr_list.push(attr);
78 }
79 }
80
81 error_list.compose((param_list, attr_list))
82 }
83}
84
85impl Parse for Param {
86 fn parse(input: ParseStream) -> syn::Result<Self> {
87 let lookahead = input.lookahead1();
88
89 if lookahead.peek(Ident) {
90 let ident: Ident = input.parse()?;
91
92 let param = if ident == "inline" {
93 let inline_opts: InlineOptions = if input.peek(Paren) {
94 let inline_content;
95
96 syn::parenthesized!(inline_content in input);
97
98 inline_content.parse()?
99 } else {
100 InlineOptions::default()
101 };
102
103 Param::identified(ident, ParamKind::Inline(inline_opts))
104 } else if ident == "import" {
105 let import_content;
106
107 syn::parenthesized!(import_content in input);
108
109 let import_root: ImportRoot = import_content.parse()?;
110
111 Param::identified(ident, ParamKind::Import(import_root))
112 } else if ident == "source" {
113 let source_content;
114
115 syn::parenthesized!(source_content in input);
116
117 let source_field: FieldRef = source_content.parse()?;
118
119 Param::identified(ident, ParamKind::Source(source_field))
120 } else if ident == "transparent" {
121 let trans_content;
122
123 syn::parenthesized!(trans_content in input);
124
125 let source_field: Transparent = trans_content.parse()?;
126
127 Param::identified(ident, ParamKind::Transparent(source_field))
128 } else if ident == "from" {
129 Param::identified(ident, ParamKind::From)
130 } else {
131 return Err(syn::Error::new_spanned(
132 ident,
133 "expected `inline`, `import`, `source`, `transparent`, or `from`",
134 ));
135 };
136
137 Ok(param)
138 } else if lookahead.peek(LitStr) {
139 Ok(Param::lone(ParamKind::Format(Format::parse(input)?)))
140 } else {
141 Err(lookahead.error())
142 }
143 }
144}
145
146impl Param {
147 #[inline]
152 pub fn attribute(target_meta: &syn::Meta) -> syn::Result<Self> {
153 syn::parse2::<Self>(target_meta.to_token_stream()).map_err(|error| syn::Error::new_spanned(target_meta, error))
154 }
155}
156
157#[derive(Clone, Debug, PartialEq, Eq, Hash)]
159pub enum ParamKind {
160 Inline(InlineOptions),
162
163 Import(ImportRoot),
165
166 Format(Format),
168
169 Source(FieldRef),
171
172 Transparent(Transparent),
177
178 From,
183}
184
185#[derive(Clone, Debug, PartialEq, Eq, Hash, Default, Copy)]
187pub enum InlineOptions {
188 #[default]
195 Neutral,
196
197 Never,
199
200 Always,
202}
203
204impl Parse for InlineOptions {
205 #[inline]
206 fn parse(input: ParseStream) -> syn::Result<Self> {
207 let ident: Ident = input.parse()?;
208
209 match ident.to_string().as_str() {
210 "neutral" => Ok(Self::Neutral),
211 "never" => Ok(Self::Never),
212 "always" => Ok(Self::Always),
213 _ => Err(syn::Error::new_spanned(ident, "expected `neutral`, `never` or `always`")),
214 }
215 }
216}
217
218#[derive(Clone, Debug, PartialEq, Eq, Hash)]
220pub struct ImportRoot(pub Path);
221
222impl Parse for ImportRoot {
223 #[inline]
224 fn parse(input: ParseStream) -> syn::Result<Self> {
225 let path: Path = input.parse()?;
226
227 Ok(Self(path))
228 }
229}
230
231#[derive(Debug, Clone, PartialEq, Eq, Hash)]
233pub struct Format {
234 pub format: LitStr,
236
237 pub format_args: Punctuated<Expr, Token![,]>,
239}
240
241impl Parse for Format {
242 #[inline]
243 fn parse(input: ParseStream) -> syn::Result<Self> {
244 let format: LitStr = input.parse()?;
245
246 let format_args = if input.peek(Token![,]) {
247 let _ = input.parse::<Token![,]>()?;
248
249 Punctuated::<Expr, Token![,]>::parse_terminated(input)?
250 } else {
251 Punctuated::new()
252 };
253
254 Ok(Self { format, format_args })
255 }
256}
257
258#[derive(Debug, Clone, PartialEq, Eq, Hash)]
260pub enum FieldRef {
261 Named(Ident),
263
264 Indexed(usize),
266}
267
268impl Parse for FieldRef {
269 #[inline]
270 fn parse(input: ParseStream) -> syn::Result<Self> {
271 let lookahead = input.lookahead1();
272
273 if lookahead.peek(Ident) {
274 let ident: Ident = input.parse()?;
275
276 Ok(Self::Named(ident))
277 } else if lookahead.peek(LitInt) {
278 let lit_int: LitInt = input.parse()?;
279
280 let index = lit_int
281 .base10_parse::<usize>()
282 .map_err(|_| syn::Error::new_spanned(lit_int, "expected valid field index"))?;
283
284 Ok(Self::Indexed(index))
285 } else {
286 Err(lookahead.error())
287 }
288 }
289}
290
291impl ToTokens for FieldRef {
292 fn to_tokens(&self, tokens: &mut TokenStream) {
293 match self {
294 FieldRef::Named(ident) => ident.to_tokens(tokens),
295 FieldRef::Indexed(index) => LitInt::new(index.to_string().as_str(), Span::call_site()).to_tokens(tokens),
296 }
297 }
298}
299
300#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
303pub enum ForwardSource {
304 #[default]
308 No,
309
310 Yes,
312}
313
314impl Parse for ForwardSource {
315 fn parse(input: ParseStream) -> syn::Result<Self> {
316 let ident: Ident = input.parse()?;
317
318 if ident == "forward" {
319 Ok(Self::Yes)
320 } else {
321 Err(syn::Error::new_spanned(
322 ident,
323 "expected just `source`, no internal further parameters",
324 ))
325 }
326 }
327}
328
329#[derive(Clone, Debug, PartialEq, Eq, Hash)]
331pub struct Transparent(pub FieldRef);
332
333impl Parse for Transparent {
334 #[inline]
335 fn parse(input: ParseStream) -> syn::Result<Self> {
336 let field_ref: FieldRef = input.parse()?;
337
338 Ok(Self(field_ref))
339 }
340}