1 | #![allow ( |
2 | clippy::missing_const_for_fn, // irrelevant for proc macros |
3 | clippy::missing_docs_in_private_items, // TODO remove |
4 | clippy::std_instead_of_core, // irrelevant for proc macros |
5 | clippy::std_instead_of_alloc, // irrelevant for proc macros |
6 | clippy::alloc_instead_of_core, // irrelevant for proc macros |
7 | missing_docs, // TODO remove |
8 | )] |
9 | |
10 | #[allow (unused_macros)] |
11 | macro_rules! bug { |
12 | () => { compile_error!("provide an error message to help fix a possible bug" ) }; |
13 | ($descr:literal $($rest:tt)?) => { |
14 | unreachable!(concat!("internal error: " , $descr) $($rest)?) |
15 | } |
16 | } |
17 | |
18 | #[macro_use ] |
19 | mod quote; |
20 | |
21 | mod date; |
22 | mod datetime; |
23 | mod error; |
24 | #[cfg (any(feature = "formatting" , feature = "parsing" ))] |
25 | mod format_description; |
26 | mod helpers; |
27 | mod offset; |
28 | #[cfg (all(feature = "serde" , any(feature = "formatting" , feature = "parsing" )))] |
29 | mod serde_format_description; |
30 | mod time; |
31 | mod to_tokens; |
32 | |
33 | #[cfg (any(feature = "formatting" , feature = "parsing" ))] |
34 | use std::iter::Peekable; |
35 | |
36 | use proc_macro::TokenStream; |
37 | #[cfg (any(feature = "formatting" , feature = "parsing" ))] |
38 | use proc_macro::{Ident, TokenTree}; |
39 | |
40 | use self::error::Error; |
41 | |
42 | macro_rules! impl_macros { |
43 | ($($name:ident)*) => {$( |
44 | #[proc_macro] |
45 | pub fn $name(input: TokenStream) -> TokenStream { |
46 | use crate::to_tokens::ToTokenTree; |
47 | |
48 | let mut iter = input.into_iter().peekable(); |
49 | match $name::parse(&mut iter) { |
50 | Ok(value) => match iter.peek() { |
51 | Some(tree) => Error::UnexpectedToken { tree: tree.clone() }.to_compile_error(), |
52 | None => TokenStream::from(value.into_token_tree()), |
53 | }, |
54 | Err(err) => err.to_compile_error(), |
55 | } |
56 | } |
57 | )*}; |
58 | } |
59 | |
60 | impl_macros![date datetime offset time]; |
61 | |
62 | #[cfg (any(feature = "formatting" , feature = "parsing" ))] |
63 | enum FormatDescriptionVersion { |
64 | V1, |
65 | V2, |
66 | } |
67 | |
68 | #[cfg (any(feature = "formatting" , feature = "parsing" ))] |
69 | enum VersionOrModuleName { |
70 | Version(FormatDescriptionVersion), |
71 | #[cfg_attr (not(feature = "serde" ), allow(dead_code))] |
72 | ModuleName(Ident), |
73 | } |
74 | |
75 | #[cfg (any(feature = "formatting" , feature = "parsing" ))] |
76 | fn parse_format_description_version<const NO_EQUALS_IS_MOD_NAME: bool>( |
77 | iter: &mut Peekable<proc_macro::token_stream::IntoIter>, |
78 | ) -> Result<Option<VersionOrModuleName>, Error> { |
79 | let version_ident = match iter.peek() { |
80 | Some(TokenTree::Ident(ident)) if ident.to_string() == "version" => match iter.next() { |
81 | Some(TokenTree::Ident(ident)) => ident, |
82 | _ => unreachable!(), |
83 | }, |
84 | _ => return Ok(None), |
85 | }; |
86 | match iter.peek() { |
87 | Some(TokenTree::Punct(punct)) if punct.as_char() == '=' => iter.next(), |
88 | _ if NO_EQUALS_IS_MOD_NAME => { |
89 | return Ok(Some(VersionOrModuleName::ModuleName(version_ident))); |
90 | } |
91 | Some(token) => { |
92 | return Err(Error::Custom { |
93 | message: "expected `=`" .into(), |
94 | span_start: Some(token.span()), |
95 | span_end: Some(token.span()), |
96 | }); |
97 | } |
98 | None => { |
99 | return Err(Error::Custom { |
100 | message: "expected `=`" .into(), |
101 | span_start: None, |
102 | span_end: None, |
103 | }); |
104 | } |
105 | }; |
106 | let version_literal = match iter.next() { |
107 | Some(TokenTree::Literal(literal)) => literal, |
108 | Some(token) => { |
109 | return Err(Error::Custom { |
110 | message: "expected 1 or 2" .into(), |
111 | span_start: Some(token.span()), |
112 | span_end: Some(token.span()), |
113 | }); |
114 | } |
115 | None => { |
116 | return Err(Error::Custom { |
117 | message: "expected 1 or 2" .into(), |
118 | span_start: None, |
119 | span_end: None, |
120 | }); |
121 | } |
122 | }; |
123 | let version = match version_literal.to_string().as_str() { |
124 | "1" => FormatDescriptionVersion::V1, |
125 | "2" => FormatDescriptionVersion::V2, |
126 | _ => { |
127 | return Err(Error::Custom { |
128 | message: "invalid format description version" .into(), |
129 | span_start: Some(version_literal.span()), |
130 | span_end: Some(version_literal.span()), |
131 | }); |
132 | } |
133 | }; |
134 | helpers::consume_punct(',' , iter)?; |
135 | |
136 | Ok(Some(VersionOrModuleName::Version(version))) |
137 | } |
138 | |
139 | #[cfg (any(feature = "formatting" , feature = "parsing" ))] |
140 | #[proc_macro ] |
141 | pub fn format_description(input: TokenStream) -> TokenStream { |
142 | (|| { |
143 | let mut input = input.into_iter().peekable(); |
144 | let version = match parse_format_description_version::<false>(&mut input)? { |
145 | Some(VersionOrModuleName::Version(version)) => Some(version), |
146 | None => None, |
147 | // This branch should never occur here, as `false` is the provided as a const parameter. |
148 | Some(VersionOrModuleName::ModuleName(_)) => bug!("branch should never occur" ), |
149 | }; |
150 | let (span, string) = helpers::get_string_literal(input)?; |
151 | let items = format_description::parse_with_version(version, &string, span)?; |
152 | |
153 | Ok(quote! {{ |
154 | const DESCRIPTION: &[::time::format_description::BorrowedFormatItem<'_>] = &[#S( |
155 | items |
156 | .into_iter() |
157 | .map(|item| quote! { #S(item), }) |
158 | .collect::<TokenStream>() |
159 | )]; |
160 | DESCRIPTION |
161 | }}) |
162 | })() |
163 | .unwrap_or_else(|err: Error| err.to_compile_error()) |
164 | } |
165 | |
166 | #[cfg (all(feature = "serde" , any(feature = "formatting" , feature = "parsing" )))] |
167 | #[proc_macro ] |
168 | pub fn serde_format_description(input: TokenStream) -> TokenStream { |
169 | (|| { |
170 | let mut tokens = input.into_iter().peekable(); |
171 | |
172 | // First, the optional format description version. |
173 | let version = parse_format_description_version::<true>(&mut tokens)?; |
174 | let (version, mod_name) = match version { |
175 | Some(VersionOrModuleName::ModuleName(module_name)) => (None, Some(module_name)), |
176 | Some(VersionOrModuleName::Version(version)) => (Some(version), None), |
177 | None => (None, None), |
178 | }; |
179 | |
180 | // Next, an identifier (the desired module name) |
181 | // Only parse this if it wasn't parsed when attempting to get the version. |
182 | let mod_name = match mod_name { |
183 | Some(mod_name) => mod_name, |
184 | None => match tokens.next() { |
185 | Some(TokenTree::Ident(ident)) => Ok(ident), |
186 | Some(tree) => Err(Error::UnexpectedToken { tree }), |
187 | None => Err(Error::UnexpectedEndOfInput), |
188 | }?, |
189 | }; |
190 | |
191 | // Followed by a comma |
192 | helpers::consume_punct(',' , &mut tokens)?; |
193 | |
194 | // Then, the type to create serde serializers for (e.g., `OffsetDateTime`). |
195 | let formattable = match tokens.next() { |
196 | Some(tree @ TokenTree::Ident(_)) => Ok(tree), |
197 | Some(tree) => Err(Error::UnexpectedToken { tree }), |
198 | None => Err(Error::UnexpectedEndOfInput), |
199 | }?; |
200 | |
201 | // Another comma |
202 | helpers::consume_punct(',' , &mut tokens)?; |
203 | |
204 | // We now have two options. The user can either provide a format description as a string or |
205 | // they can provide a path to a format description. If the latter, all remaining tokens are |
206 | // assumed to be part of the path. |
207 | let (format, format_description_display) = match tokens.peek() { |
208 | // string literal |
209 | Some(TokenTree::Literal(_)) => { |
210 | let (span, format_string) = helpers::get_string_literal(tokens)?; |
211 | let items = format_description::parse_with_version(version, &format_string, span)?; |
212 | let items: TokenStream = |
213 | items.into_iter().map(|item| quote! { #S(item), }).collect(); |
214 | let items = quote! { |
215 | const ITEMS: &[::time::format_description::BorrowedFormatItem<'_>] |
216 | = &[#S(items)]; |
217 | ITEMS |
218 | }; |
219 | |
220 | (items, String::from_utf8_lossy(&format_string).into_owned()) |
221 | } |
222 | // path |
223 | Some(_) => { |
224 | let tokens = tokens.collect::<TokenStream>(); |
225 | let tokens_string = tokens.to_string(); |
226 | (tokens, tokens_string) |
227 | } |
228 | None => return Err(Error::UnexpectedEndOfInput), |
229 | }; |
230 | |
231 | Ok(serde_format_description::build( |
232 | mod_name, |
233 | formattable, |
234 | format, |
235 | format_description_display, |
236 | )) |
237 | })() |
238 | .unwrap_or_else(|err: Error| err.to_compile_error_standalone()) |
239 | } |
240 | |