1#![allow(
2 clippy::missing_const_for_fn, clippy::missing_docs_in_private_items, clippy::std_instead_of_core, clippy::std_instead_of_alloc, clippy::alloc_instead_of_core, missing_docs, )]
9
10#[allow(unused_macros)]
11macro_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]
19mod quote;
20
21mod date;
22mod datetime;
23mod error;
24#[cfg(any(feature = "formatting", feature = "parsing"))]
25mod format_description;
26mod helpers;
27mod offset;
28#[cfg(all(feature = "serde", any(feature = "formatting", feature = "parsing")))]
29mod serde_format_description;
30mod time;
31mod to_tokens;
32mod utc_datetime;
33
34#[cfg(any(feature = "formatting", feature = "parsing"))]
35use std::iter::Peekable;
36
37use proc_macro::TokenStream;
38#[cfg(any(feature = "formatting", feature = "parsing"))]
39use proc_macro::{Ident, TokenTree};
40
41use self::error::Error;
42
43macro_rules! impl_macros {
44 ($($name:ident)*) => {$(
45 #[proc_macro]
46 pub fn $name(input: TokenStream) -> TokenStream {
47 use crate::to_tokens::ToTokenTree;
48
49 let mut iter = input.into_iter().peekable();
50 match $name::parse(&mut iter) {
51 Ok(value) => match iter.peek() {
52 Some(tree) => Error::UnexpectedToken { tree: tree.clone() }.to_compile_error(),
53 None => TokenStream::from(value.into_token_tree()),
54 },
55 Err(err) => err.to_compile_error(),
56 }
57 }
58 )*};
59}
60
61impl_macros![date datetime utc_datetime offset time];
62
63#[cfg(any(feature = "formatting", feature = "parsing"))]
64enum FormatDescriptionVersion {
65 V1,
66 V2,
67}
68
69#[cfg(any(feature = "formatting", feature = "parsing"))]
70enum VersionOrModuleName {
71 Version(FormatDescriptionVersion),
72 #[cfg_attr(not(feature = "serde"), allow(dead_code))]
73 ModuleName(Ident),
74}
75
76#[cfg(any(feature = "formatting", feature = "parsing"))]
77fn parse_format_description_version<const NO_EQUALS_IS_MOD_NAME: bool>(
78 iter: &mut Peekable<proc_macro::token_stream::IntoIter>,
79) -> Result<Option<VersionOrModuleName>, Error> {
80 let version_ident = match iter.peek() {
81 Some(TokenTree::Ident(ident)) if ident.to_string() == "version" => match iter.next() {
82 Some(TokenTree::Ident(ident)) => ident,
83 _ => unreachable!(),
84 },
85 _ => return Ok(None),
86 };
87 match iter.peek() {
88 Some(TokenTree::Punct(punct)) if punct.as_char() == '=' => iter.next(),
89 _ if NO_EQUALS_IS_MOD_NAME => {
90 return Ok(Some(VersionOrModuleName::ModuleName(version_ident)));
91 }
92 Some(token) => {
93 return Err(Error::Custom {
94 message: "expected `=`".into(),
95 span_start: Some(token.span()),
96 span_end: Some(token.span()),
97 });
98 }
99 None => {
100 return Err(Error::Custom {
101 message: "expected `=`".into(),
102 span_start: None,
103 span_end: None,
104 });
105 }
106 };
107 let version_literal = match iter.next() {
108 Some(TokenTree::Literal(literal)) => literal,
109 Some(token) => {
110 return Err(Error::Custom {
111 message: "expected 1 or 2".into(),
112 span_start: Some(token.span()),
113 span_end: Some(token.span()),
114 });
115 }
116 None => {
117 return Err(Error::Custom {
118 message: "expected 1 or 2".into(),
119 span_start: None,
120 span_end: None,
121 });
122 }
123 };
124 let version = match version_literal.to_string().as_str() {
125 "1" => FormatDescriptionVersion::V1,
126 "2" => FormatDescriptionVersion::V2,
127 _ => {
128 return Err(Error::Custom {
129 message: "invalid format description version".into(),
130 span_start: Some(version_literal.span()),
131 span_end: Some(version_literal.span()),
132 });
133 }
134 };
135 helpers::consume_punct(',', iter)?;
136
137 Ok(Some(VersionOrModuleName::Version(version)))
138}
139
140#[cfg(any(feature = "formatting", feature = "parsing"))]
141#[proc_macro]
142pub fn format_description(input: TokenStream) -> TokenStream {
143 (|| {
144 let mut input = input.into_iter().peekable();
145 let version = match parse_format_description_version::<false>(&mut input)? {
146 Some(VersionOrModuleName::Version(version)) => Some(version),
147 None => None,
148 Some(VersionOrModuleName::ModuleName(_)) => bug!("branch should never occur"),
150 };
151 let (span, string) = helpers::get_string_literal(input)?;
152 let items = format_description::parse_with_version(version, &string, span)?;
153
154 Ok(quote! {{
155 const DESCRIPTION: &[::time::format_description::BorrowedFormatItem<'_>] = &[#S(
156 items
157 .into_iter()
158 .map(|item| quote! { #S(item), })
159 .collect::<TokenStream>()
160 )];
161 DESCRIPTION
162 }})
163 })()
164 .unwrap_or_else(|err: Error| err.to_compile_error())
165}
166
167#[cfg(all(feature = "serde", any(feature = "formatting", feature = "parsing")))]
168#[proc_macro]
169pub fn serde_format_description(input: TokenStream) -> TokenStream {
170 (|| {
171 let mut tokens = input.into_iter().peekable();
172
173 let version = parse_format_description_version::<true>(&mut tokens)?;
175 let (version, mod_name) = match version {
176 Some(VersionOrModuleName::ModuleName(module_name)) => (None, Some(module_name)),
177 Some(VersionOrModuleName::Version(version)) => (Some(version), None),
178 None => (None, None),
179 };
180
181 let mod_name = match mod_name {
184 Some(mod_name) => mod_name,
185 None => match tokens.next() {
186 Some(TokenTree::Ident(ident)) => Ok(ident),
187 Some(tree) => Err(Error::UnexpectedToken { tree }),
188 None => Err(Error::UnexpectedEndOfInput),
189 }?,
190 };
191
192 helpers::consume_punct(',', &mut tokens)?;
194
195 let formattable = match tokens.next() {
197 Some(tree @ TokenTree::Ident(_)) => Ok(tree),
198 Some(tree) => Err(Error::UnexpectedToken { tree }),
199 None => Err(Error::UnexpectedEndOfInput),
200 }?;
201
202 helpers::consume_punct(',', &mut tokens)?;
204
205 let (format, format_description_display) = match tokens.peek() {
209 Some(TokenTree::Literal(_)) => {
211 let (span, format_string) = helpers::get_string_literal(tokens)?;
212 let items = format_description::parse_with_version(version, &format_string, span)?;
213 let items: TokenStream =
214 items.into_iter().map(|item| quote! { #S(item), }).collect();
215 let items = quote! {
216 const ITEMS: &[::time::format_description::BorrowedFormatItem<'_>]
217 = &[#S(items)];
218 ITEMS
219 };
220
221 (items, String::from_utf8_lossy(&format_string).into_owned())
222 }
223 Some(_) => {
225 let tokens = tokens.collect::<TokenStream>();
226 let tokens_string = tokens.to_string();
227 (tokens, tokens_string)
228 }
229 None => return Err(Error::UnexpectedEndOfInput),
230 };
231
232 Ok(serde_format_description::build(
233 mod_name,
234 formattable,
235 format,
236 format_description_display,
237 ))
238 })()
239 .unwrap_or_else(|err: Error| err.to_compile_error_standalone())
240}