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 Preinterned {
146 idx: u32,
147 span_of_name: Span,
148}
149
150struct Entries {
151 map: HashMap<String, Preinterned>,
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(), Preinterned { 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 mut entries = Entries::with_capacity(input.keywords.len() + input.symbols.len() + 10);
193 let mut prev_key: Option<(Span, String)> = None;
194
195 let mut check_order = |span: Span, s: &str, errors: &mut Errors| {
196 if let Some((prev_span, ref prev_str)) = prev_key {
197 if s < prev_str {
198 errors.error(span, format!("Symbol `{s}` must precede `{prev_str}`"));
199 errors.error(prev_span, format!("location of previous symbol `{prev_str}`"));
200 }
201 }
202 prev_key = Some((span, s.to_string()));
203 };
204
205 for keyword in input.keywords.iter() {
207 let name = &keyword.name;
208 let value = &keyword.value;
209 let value_string = value.value();
210 let idx = entries.insert(keyword.name.span(), &value_string, &mut errors);
211 prefill_stream.extend(quote! {
212 #value,
213 });
214 keyword_stream.extend(quote! {
215 pub const #name: Symbol = Symbol::new(#idx);
216 });
217 }
218
219 for symbol in input.symbols.iter() {
221 let name = &symbol.name;
222 check_order(symbol.name.span(), &name.to_string(), &mut errors);
223
224 let value = match &symbol.value {
225 Value::SameAsName => name.to_string(),
226 Value::String(lit) => lit.value(),
227 Value::Env(..) => continue, Value::Unsupported(expr) => {
229 errors.list.push(syn::Error::new_spanned(
230 expr,
231 concat!(
232 "unsupported expression for symbol value; implement support for this in ",
233 file!(),
234 ),
235 ));
236 continue;
237 }
238 };
239 let idx = entries.insert(symbol.name.span(), &value, &mut errors);
240
241 prefill_stream.extend(quote! {
242 #value,
243 });
244 symbols_stream.extend(quote! {
245 pub const #name: Symbol = Symbol::new(#idx);
246 });
247 }
248
249 for n in 0..10 {
251 let n = n.to_string();
252 entries.insert(Span::call_site(), &n, &mut errors);
253 prefill_stream.extend(quote! {
254 #n,
255 });
256 }
257
258 for symbol in &input.symbols {
261 let (env_var, expr) = match &symbol.value {
262 Value::Env(lit, expr) => (lit, expr),
263 Value::SameAsName | Value::String(_) | Value::Unsupported(_) => continue,
264 };
265
266 if !proc_macro::is_available() {
267 errors.error(
268 Span::call_site(),
269 "proc_macro::tracked_env is not available in unit test".to_owned(),
270 );
271 break;
272 }
273
274 let value = match proc_macro::tracked_env::var(env_var.value()) {
275 Ok(value) => value,
276 Err(err) => {
277 errors.list.push(syn::Error::new_spanned(expr, err));
278 continue;
279 }
280 };
281
282 let idx = if let Some(prev) = entries.map.get(&value) {
283 prev.idx
284 } else {
285 prefill_stream.extend(quote! {
286 #value,
287 });
288 entries.insert(symbol.name.span(), &value, &mut errors)
289 };
290
291 let name = &symbol.name;
292 symbols_stream.extend(quote! {
293 pub const #name: Symbol = Symbol::new(#idx);
294 });
295 }
296
297 let symbol_digits_base = entries.map["0"].idx;
298 let preinterned_symbols_count = entries.len();
299 let output = quote! {
300 const SYMBOL_DIGITS_BASE: u32 = #symbol_digits_base;
301 const PREINTERNED_SYMBOLS_COUNT: u32 = #preinterned_symbols_count;
302
303 #[doc(hidden)]
304 #[allow(non_upper_case_globals)]
305 mod kw_generated {
306 use super::Symbol;
307 #keyword_stream
308 }
309
310 #[allow(non_upper_case_globals)]
311 #[doc(hidden)]
312 pub mod sym_generated {
313 use super::Symbol;
314 #symbols_stream
315 }
316
317 impl Interner {
318 pub(crate) fn fresh() -> Self {
319 Interner::prefill(&[
320 #prefill_stream
321 ])
322 }
323 }
324 };
325
326 (output, errors.list)
327}