@@ -26,7 +26,7 @@ use proc_macro2::{Span, TokenStream};
2626use quote:: quote;
2727use std:: collections:: HashMap ;
2828use syn:: parse:: { Parse , ParseStream , Result } ;
29- use syn:: { braced, punctuated:: Punctuated , Ident , LitStr , Token } ;
29+ use syn:: { braced, punctuated:: Punctuated , Expr , Ident , Lit , LitStr , Macro , Token } ;
3030
3131#[ cfg( test) ]
3232mod tests;
@@ -53,21 +53,46 @@ impl Parse for Keyword {
5353
5454struct Symbol {
5555 name : Ident ,
56- value : Option < LitStr > ,
56+ value : Value ,
57+ }
58+
59+ enum Value {
60+ SameAsName ,
61+ String ( LitStr ) ,
62+ Env ( LitStr , Macro ) ,
63+ Unsupported ( Expr ) ,
5764}
5865
5966impl Parse for Symbol {
6067 fn parse ( input : ParseStream < ' _ > ) -> Result < Self > {
6168 let name = input. parse ( ) ?;
62- let value = match input. parse :: < Token ! [ : ] > ( ) {
63- Ok ( _) => Some ( input. parse ( ) ?) ,
64- Err ( _) => None ,
65- } ;
69+ let colon_token: Option < Token ! [ : ] > = input. parse ( ) ?;
70+ let value = if colon_token. is_some ( ) { input. parse ( ) ? } else { Value :: SameAsName } ;
6671
6772 Ok ( Symbol { name, value } )
6873 }
6974}
7075
76+ impl Parse for Value {
77+ fn parse ( input : ParseStream < ' _ > ) -> Result < Self > {
78+ let expr: Expr = input. parse ( ) ?;
79+ match & expr {
80+ Expr :: Lit ( expr) => {
81+ if let Lit :: Str ( lit) = & expr. lit {
82+ return Ok ( Value :: String ( lit. clone ( ) ) ) ;
83+ }
84+ }
85+ Expr :: Macro ( expr) => {
86+ if expr. mac . path . is_ident ( "env" ) && let Ok ( lit) = expr. mac . parse_body ( ) {
87+ return Ok ( Value :: Env ( lit, expr. mac . clone ( ) ) ) ;
88+ }
89+ }
90+ _ => { }
91+ }
92+ Ok ( Value :: Unsupported ( expr) )
93+ }
94+ }
95+
7196struct Input {
7297 keywords : Punctuated < Keyword , Token ! [ , ] > ,
7398 symbols : Punctuated < Symbol , Token ! [ , ] > ,
@@ -111,6 +136,37 @@ pub fn symbols(input: TokenStream) -> TokenStream {
111136 output
112137}
113138
139+ struct Preinterned {
140+ idx : u32 ,
141+ span_of_name : Span ,
142+ }
143+
144+ struct Entries {
145+ map : HashMap < String , Preinterned > ,
146+ }
147+
148+ impl Entries {
149+ fn with_capacity ( capacity : usize ) -> Self {
150+ Entries { map : HashMap :: with_capacity ( capacity) }
151+ }
152+
153+ fn insert ( & mut self , span : Span , str : & str , errors : & mut Errors ) -> u32 {
154+ if let Some ( prev) = self . map . get ( str) {
155+ errors. error ( span, format ! ( "Symbol `{str}` is duplicated" ) ) ;
156+ errors. error ( prev. span_of_name , "location of previous definition" . to_string ( ) ) ;
157+ prev. idx
158+ } else {
159+ let idx = self . len ( ) ;
160+ self . map . insert ( str. to_string ( ) , Preinterned { idx, span_of_name : span } ) ;
161+ idx
162+ }
163+ }
164+
165+ fn len ( & self ) -> u32 {
166+ u32:: try_from ( self . map . len ( ) ) . expect ( "way too many symbols" )
167+ }
168+ }
169+
114170fn symbols_with_errors ( input : TokenStream ) -> ( TokenStream , Vec < syn:: Error > ) {
115171 let mut errors = Errors :: default ( ) ;
116172
@@ -127,20 +183,9 @@ fn symbols_with_errors(input: TokenStream) -> (TokenStream, Vec<syn::Error>) {
127183 let mut keyword_stream = quote ! { } ;
128184 let mut symbols_stream = quote ! { } ;
129185 let mut prefill_stream = quote ! { } ;
130- let mut counter = 0u32 ;
131- let mut keys =
132- HashMap :: < String , Span > :: with_capacity ( input. keywords . len ( ) + input. symbols . len ( ) + 10 ) ;
186+ let mut entries = Entries :: with_capacity ( input. keywords . len ( ) + input. symbols . len ( ) + 10 ) ;
133187 let mut prev_key: Option < ( Span , String ) > = None ;
134188
135- let mut check_dup = |span : Span , str : & str , errors : & mut Errors | {
136- if let Some ( prev_span) = keys. get ( str) {
137- errors. error ( span, format ! ( "Symbol `{str}` is duplicated" ) ) ;
138- errors. error ( * prev_span, "location of previous definition" . to_string ( ) ) ;
139- } else {
140- keys. insert ( str. to_string ( ) , span) ;
141- }
142- } ;
143-
144189 let mut check_order = |span : Span , str : & str , errors : & mut Errors | {
145190 if let Some ( ( prev_span, ref prev_str) ) = prev_key {
146191 if str < prev_str {
@@ -156,49 +201,98 @@ fn symbols_with_errors(input: TokenStream) -> (TokenStream, Vec<syn::Error>) {
156201 let name = & keyword. name ;
157202 let value = & keyword. value ;
158203 let value_string = value. value ( ) ;
159- check_dup ( keyword. name . span ( ) , & value_string, & mut errors) ;
204+ let idx = entries . insert ( keyword. name . span ( ) , & value_string, & mut errors) ;
160205 prefill_stream. extend ( quote ! {
161206 #value,
162207 } ) ;
163208 keyword_stream. extend ( quote ! {
164- pub const #name: Symbol = Symbol :: new( #counter ) ;
209+ pub const #name: Symbol = Symbol :: new( #idx ) ;
165210 } ) ;
166- counter += 1 ;
167211 }
168212
169213 // Generate the listed symbols.
170214 for symbol in input. symbols . iter ( ) {
171215 let name = & symbol. name ;
216+ check_order ( symbol. name . span ( ) , & name. to_string ( ) , & mut errors) ;
217+
172218 let value = match & symbol. value {
173- Some ( value) => value. value ( ) ,
174- None => name. to_string ( ) ,
219+ Value :: SameAsName => name. to_string ( ) ,
220+ Value :: String ( lit) => lit. value ( ) ,
221+ Value :: Env ( ..) => continue , // in another loop below
222+ Value :: Unsupported ( expr) => {
223+ errors. list . push ( syn:: Error :: new_spanned (
224+ expr,
225+ concat ! (
226+ "unsupported expression for symbol value; implement support for this in " ,
227+ file!( ) ,
228+ ) ,
229+ ) ) ;
230+ continue ;
231+ }
175232 } ;
176- check_dup ( symbol. name . span ( ) , & value, & mut errors) ;
177- check_order ( symbol. name . span ( ) , & name. to_string ( ) , & mut errors) ;
233+ let idx = entries. insert ( symbol. name . span ( ) , & value, & mut errors) ;
178234
179235 prefill_stream. extend ( quote ! {
180236 #value,
181237 } ) ;
182238 symbols_stream. extend ( quote ! {
183- pub const #name: Symbol = Symbol :: new( #counter ) ;
239+ pub const #name: Symbol = Symbol :: new( #idx ) ;
184240 } ) ;
185- counter += 1 ;
186241 }
187242
188243 // Generate symbols for the strings "0", "1", ..., "9".
189- let digits_base = counter;
190- counter += 10 ;
191244 for n in 0 ..10 {
192245 let n = n. to_string ( ) ;
193- check_dup ( Span :: call_site ( ) , & n, & mut errors) ;
246+ entries . insert ( Span :: call_site ( ) , & n, & mut errors) ;
194247 prefill_stream. extend ( quote ! {
195248 #n,
196249 } ) ;
197250 }
198251
252+ // Symbols whose value comes from an environment variable. It's allowed for
253+ // these to have the same value as another symbol.
254+ 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 value = match proc_macro:: tracked_env:: var ( env_var. value ( ) ) {
269+ Ok ( value) => value,
270+ Err ( err) => {
271+ errors. list . push ( syn:: Error :: new_spanned ( expr, err) ) ;
272+ continue ;
273+ }
274+ } ;
275+
276+ let idx = if let Some ( prev) = entries. map . get ( & value) {
277+ prev. idx
278+ } else {
279+ prefill_stream. extend ( quote ! {
280+ #value,
281+ } ) ;
282+ entries. insert ( symbol. name . span ( ) , & value, & mut errors)
283+ } ;
284+
285+ let name = & symbol. name ;
286+ symbols_stream. extend ( quote ! {
287+ pub const #name: Symbol = Symbol :: new( #idx) ;
288+ } ) ;
289+ }
290+
291+ let symbol_digits_base = entries. map [ "0" ] . idx ;
292+ let preinterned_symbols_count = entries. len ( ) ;
199293 let output = quote ! {
200- const SYMBOL_DIGITS_BASE : u32 = #digits_base ;
201- const PREINTERNED_SYMBOLS_COUNT : u32 = #counter ;
294+ const SYMBOL_DIGITS_BASE : u32 = #symbol_digits_base ;
295+ const PREINTERNED_SYMBOLS_COUNT : u32 = #preinterned_symbols_count ;
202296
203297 #[ doc( hidden) ]
204298 #[ allow( non_upper_case_globals) ]
0 commit comments