1use std::collections::HashMap;
28
29use proc_macro2::{Span, TokenStream};
30use quote::quote;
31use syn::parse::{Parse, ParseStream, Result};
32use syn::punctuated::Punctuated;
33use syn::{Expr, Ident, Lit, LitStr, Macro, Token, braced};
34
35#[cfg(test)]
36mod tests;
37
38mod kw {
39 syn::custom_keyword!(Keywords);
40 syn::custom_keyword!(Symbols);
41}
42
43struct Keyword {
44 name: Ident,
45 value: LitStr,
46}
47
48impl Parse for Keyword {
49 fn parse(input: ParseStream<'_>) -> Result<Self> {
50 let name = input.parse()?;
51 input.parse::<Token![:]>()?;
52 let value = input.parse()?;
53
54 Ok(Keyword { name, value })
55 }
56}
57
58struct Symbol {
59 name: Ident,
60 value: Value,
61}
62
63enum Value {
64 SameAsName,
65 String(LitStr),
66 Env(LitStr, Macro),
67 Unsupported(Expr),
68}
69
70impl Parse for Symbol {
71 fn parse(input: ParseStream<'_>) -> Result<Self> {
72 let name = input.parse()?;
73 let colon_token: Option<Token![:]> = input.parse()?;
74 let value = if colon_token.is_some() { input.parse()? } else { Value::SameAsName };
75
76 Ok(Symbol { name, value })
77 }
78}
79
80impl Parse for Value {
81 fn parse(input: ParseStream<'_>) -> Result<Self> {
82 let expr: Expr = input.parse()?;
83 match &expr {
84 Expr::Lit(expr) => {
85 if let Lit::Str(lit) = &expr.lit {
86 return Ok(Value::String(lit.clone()));
87 }
88 }
89 Expr::Macro(expr) => {
90 if expr.mac.path.is_ident("env")
91 && let Ok(lit) = expr.mac.parse_body()
92 {
93 return Ok(Value::Env(lit, expr.mac.clone()));
94 }
95 }
96 _ => {}
97 }
98 Ok(Value::Unsupported(expr))
99 }
100}
101
102struct Input {
103 keywords: Punctuated<Keyword, Token![,]>,
104 symbols: Punctuated<Symbol, Token![,]>,
105}
106
107impl Parse for Input {
108 fn parse(input: ParseStream<'_>) -> Result<Self> {
109 input.parse::<kw::Keywords>()?;
110 let content;
111 braced!(content in input);
112 let keywords = Punctuated::parse_terminated(&content)?;
113
114 input.parse::<kw::Symbols>()?;
115 let content;
116 braced!(content in input);
117 let symbols = Punctuated::parse_terminated(&content)?;
118
119 Ok(Input { keywords, symbols })
120 }
121}
122
123#[derive(Default)]
124struct Errors {
125 list: Vec<syn::Error>,
126}
127
128impl Errors {
129 fn error(&mut self, span: Span, message: String) {
130 self.list.push(syn::Error::new(span, message));
131 }
132}
133
134pub(super) fn symbols(input: TokenStream) -> TokenStream {
135 let (mut output, errors) = symbols_with_errors(input);
136
137 output.extend(errors.into_iter().map(|e| e.to_compile_error()));
141
142 output
143}
144
145struct Predefined {
146 idx: u32,
147 span_of_name: Span,
148}
149
150struct Entries {
151 map: HashMap<String, Predefined>,
152}
153
154impl Entries {
155 fn with_capacity(capacity: usize) -> Self {
156 Entries { map: HashMap::with_capacity(capacity) }
157 }
158
159 fn insert(&mut self, span: Span, s: &str, errors: &mut Errors) -> u32 {
160 if let Some(prev) = self.map.get(s) {
161 errors.error(span, format!("Symbol `{s}` is duplicated"));
162 errors.error(prev.span_of_name, "location of previous definition".to_string());
163 prev.idx
164 } else {
165 let idx = self.len();
166 self.map.insert(s.to_string(), Predefined { idx, span_of_name: span });
167 idx
168 }
169 }
170
171 fn len(&self) -> u32 {
172 u32::try_from(self.map.len()).expect("way too many symbols")
173 }
174}
175
176fn symbols_with_errors(input: TokenStream) -> (TokenStream, Vec<syn::Error>) {
177 let mut errors = Errors::default();
178
179 let input: Input = match syn::parse2(input) {
180 Ok(input) => input,
181 Err(e) => {
182 errors.list.push(e);
185 Input { keywords: Default::default(), symbols: Default::default() }
186 }
187 };
188
189 let mut keyword_stream = quote! {};
190 let mut symbols_stream = quote! {};
191 let mut prefill_stream = quote! {};
192 let prefill_ints = 0..=9;
193 let prefill_letters = ('A'..='Z').chain('a'..='z');
194 let mut entries = Entries::with_capacity(
195 input.keywords.len()
196 + input.symbols.len()
197 + prefill_ints.clone().count()
198 + prefill_letters.clone().count(),
199 );
200
201 for keyword in input.keywords.iter() {
203 let name = &keyword.name;
204 let value = &keyword.value;
205 let value_string = value.value();
206 let idx = entries.insert(keyword.name.span(), &value_string, &mut errors);
207 prefill_stream.extend(quote! {
208 #value,
209 });
210 keyword_stream.extend(quote! {
211 pub const #name: Symbol = Symbol::new(#idx);
212 });
213 }
214
215 for symbol in input.symbols.iter() {
217 let name = &symbol.name;
218
219 let value = match &symbol.value {
220 Value::SameAsName => name.to_string(),
221 Value::String(lit) => lit.value(),
222 Value::Env(..) => continue, Value::Unsupported(expr) => {
224 errors.list.push(syn::Error::new_spanned(
225 expr,
226 concat!(
227 "unsupported expression for symbol value; implement support for this in ",
228 file!(),
229 ),
230 ));
231 continue;
232 }
233 };
234 let idx = entries.insert(symbol.name.span(), &value, &mut errors);
235
236 prefill_stream.extend(quote! {
237 #value,
238 });
239 symbols_stream.extend(quote! {
240 pub const #name: Symbol = Symbol::new(#idx);
241 });
242 }
243
244 for s in prefill_ints.map(|n| n.to_string()).chain(prefill_letters.map(|c| c.to_string())) {
246 entries.insert(Span::call_site(), &s, &mut errors);
247 prefill_stream.extend(quote! {
248 #s,
249 });
250 }
251
252 for symbol in &input.symbols {
255 let (env_var, expr) = match &symbol.value {
256 Value::Env(lit, expr) => (lit, expr),
257 Value::SameAsName | Value::String(_) | Value::Unsupported(_) => continue,
258 };
259
260 if !proc_macro::is_available() {
261 errors.error(
262 Span::call_site(),
263 "proc_macro::tracked_env is not available in unit test".to_owned(),
264 );
265 break;
266 }
267
268 let tracked_env = proc_macro::tracked::env_var(env_var.value());
269
270 let value = match tracked_env {
271 Ok(value) => value,
272 Err(err) => {
273 errors.list.push(syn::Error::new_spanned(expr, err));
274 continue;
275 }
276 };
277
278 let idx = if let Some(prev) = entries.map.get(&value) {
279 prev.idx
280 } else {
281 prefill_stream.extend(quote! {
282 #value,
283 });
284 entries.insert(symbol.name.span(), &value, &mut errors)
285 };
286
287 let name = &symbol.name;
288 symbols_stream.extend(quote! {
289 pub const #name: Symbol = Symbol::new(#idx);
290 });
291 }
292
293 let symbol_digits_base = entries.map["0"].idx;
294 let symbol_uppercase_letters_base = entries.map["A"].idx;
295 let symbol_lowercase_letters_base = entries.map["a"].idx;
296 let predefined_symbols_count = entries.len();
297 let output = quote! {
298 const SYMBOL_DIGITS_BASE: u32 = #symbol_digits_base;
299 const SYMBOL_UPPERCASE_LETTERS_BASE: u32 = #symbol_uppercase_letters_base;
300 const SYMBOL_LOWERCASE_LETTERS_BASE: u32 = #symbol_lowercase_letters_base;
301
302 pub const PREDEFINED_SYMBOLS_COUNT: u32 = #predefined_symbols_count;
306
307 #[doc(hidden)]
308 #[allow(non_upper_case_globals)]
309 mod kw_generated {
310 use super::Symbol;
311 #keyword_stream
312 }
313
314 #[allow(non_upper_case_globals)]
315 #[doc(hidden)]
316 pub mod sym_generated {
317 use super::Symbol;
318 #symbols_stream
319 }
320
321 impl Interner {
322 pub(crate) fn with_extra_symbols(extra_symbols: &[&'static str]) -> Self {
325 Interner::prefill(
326 &[#prefill_stream],
327 extra_symbols,
328 )
329 }
330 }
331 };
332
333 (output, errors.list)
334}