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;
32
33#[cfg(any(feature = "formatting", feature = "parsing"))]
34use std::iter::Peekable;
35
36use proc_macro::TokenStream;
37#[cfg(any(feature = "formatting", feature = "parsing"))]
38use proc_macro::{Ident, TokenTree};
39
40use self::error::Error;
41
42macro_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
60impl_macros![date datetime offset time];
61
62#[cfg(any(feature = "formatting", feature = "parsing"))]
63enum FormatDescriptionVersion {
64 V1,
65 V2,
66}
67
68#[cfg(any(feature = "formatting", feature = "parsing"))]
69enum 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"))]
76fn 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]
141pub 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 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]
168pub fn serde_format_description(input: TokenStream) -> TokenStream {
169 (|| {
170 let mut tokens = input.into_iter().peekable();
171
172 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 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 helpers::consume_punct(',', &mut tokens)?;
193
194 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 helpers::consume_punct(',', &mut tokens)?;
203
204 let (format, format_description_display) = match tokens.peek() {
208 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 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}