1#![doc(html_root_url = "https://docs.rs/macro-string/0.1.4")]
71
72use proc_macro2::TokenStream;
73use quote::{quote, ToTokens};
74use std::env;
75use std::fs;
76use std::path::{Component, Path, PathBuf};
77use syn::parse::{Error, Parse, ParseBuffer, ParseStream, Parser, Result};
78use syn::punctuated::Punctuated;
79use syn::token::{Brace, Bracket, Paren};
80use syn::{
81 braced, bracketed, parenthesized, Ident, LitBool, LitChar, LitFloat, LitInt, LitStr, Token,
82};
83
84mod kw {
85 syn::custom_keyword!(concat);
86 syn::custom_keyword!(env);
87 syn::custom_keyword!(include);
88 syn::custom_keyword!(include_str);
89 syn::custom_keyword!(stringify);
90}
91
92pub struct MacroString(pub String);
93
94impl Parse for MacroString {
95 fn parse(input: ParseStream) -> Result<Self> {
96 let expr = input.call(Expr::parse_strict)?;
97 let value = expr.eval()?;
98 Ok(MacroString(value))
99 }
100}
101
102enum Expr {
103 LitStr(LitStr),
104 LitChar(LitChar),
105 LitInt(LitInt),
106 LitFloat(LitFloat),
107 LitBool(LitBool),
108 Concat(Concat),
109 Env(Env),
110 Include(Include),
111 IncludeStr(IncludeStr),
112 Stringify(Stringify),
113}
114
115impl Expr {
116 fn eval(&self) -> Result<String> {
117 match self {
118 Expr::LitStr(lit) => Ok(lit.value()),
119 Expr::LitChar(lit) => Ok(lit.value().to_string()),
120 Expr::LitInt(lit) => Ok(lit.base10_digits().to_owned()),
121 Expr::LitFloat(lit) => Ok(lit.base10_digits().to_owned()),
122 Expr::LitBool(lit) => Ok(lit.value.to_string()),
123 Expr::Concat(expr) => {
124 let mut concat = String::new();
125 for arg in &expr.args {
126 concat += &arg.eval()?;
127 }
128 Ok(concat)
129 }
130 Expr::Env(expr) => {
131 let key = expr.arg.eval()?;
132 match env::var(&key) {
133 Ok(value) => Ok(value),
134 Err(err) => Err(Error::new_spanned(expr, err)),
135 }
136 }
137 Expr::Include(expr) => {
138 let path = expr.arg.eval()?;
139 let content = fs_read(&expr, &path)?;
140 let inner = Expr::parse_strict.parse_str(&content)?;
141 inner.eval()
142 }
143 Expr::IncludeStr(expr) => {
144 let path = expr.arg.eval()?;
145 fs_read(&expr, &path)
146 }
147 Expr::Stringify(expr) => Ok(expr.tokens.to_string()),
148 }
149 }
150}
151
152fn fs_read(span: &dyn ToTokens, path: impl AsRef<Path>) -> Result<String> {
153 let mut path = path.as_ref();
154 if path.is_relative() {
155 let name = span.to_token_stream().into_iter().next().unwrap();
156 return Err(Error::new_spanned(
157 span,
158 format!("a relative path is not supported here; use `{name}!(concat!(env!(\"CARGO_MANIFEST_DIR\"), ...))`"),
159 ));
160 }
161
162 let path_buf: PathBuf;
165 if let Some(Component::Prefix(prefix)) = path.components().next() {
166 if prefix.kind().is_verbatim() {
167 path_buf = path.components().collect();
168 path = &path_buf;
169 }
170 }
171
172 match fs::read_to_string(path) {
173 Ok(content) => Ok(content),
174 Err(err) => Err(Error::new_spanned(
175 span,
176 format!("{} {}", err, path.display()),
177 )),
178 }
179}
180
181struct Concat {
182 name: kw::concat,
183 bang_token: Token![!],
184 delimiter: MacroDelimiter,
185 args: Punctuated<Expr, Token![,]>,
186}
187
188struct Env {
189 name: kw::env,
190 bang_token: Token![!],
191 delimiter: MacroDelimiter,
192 arg: Box<Expr>,
193 trailing_comma: Option<Token![,]>,
194}
195
196struct Include {
197 name: kw::include,
198 bang_token: Token![!],
199 delimiter: MacroDelimiter,
200 arg: Box<Expr>,
201 trailing_comma: Option<Token![,]>,
202}
203
204struct IncludeStr {
205 name: kw::include_str,
206 bang_token: Token![!],
207 delimiter: MacroDelimiter,
208 arg: Box<Expr>,
209 trailing_comma: Option<Token![,]>,
210}
211
212struct Stringify {
213 name: kw::stringify,
214 bang_token: Token![!],
215 delimiter: MacroDelimiter,
216 tokens: TokenStream,
217}
218
219enum MacroDelimiter {
220 Paren(Paren),
221 Brace(Brace),
222 Bracket(Bracket),
223}
224
225impl Expr {
226 fn parse_strict(input: ParseStream) -> Result<Self> {
227 Self::parse(input, false)
228 }
229
230 fn parse_any(input: ParseStream) -> Result<Self> {
231 Self::parse(input, true)
232 }
233
234 fn parse(input: ParseStream, allow_nonstring_literals: bool) -> Result<Self> {
235 let lookahead = input.lookahead1();
236 if lookahead.peek(LitStr) {
237 let lit: LitStr = input.parse()?;
238 if !lit.suffix().is_empty() {
239 return Err(Error::new(
240 lit.span(),
241 "unexpected suffix on string literal",
242 ));
243 }
244 Ok(Expr::LitStr(lit))
245 } else if allow_nonstring_literals && input.peek(LitChar) {
246 let lit: LitChar = input.parse()?;
247 if !lit.suffix().is_empty() {
248 return Err(Error::new(lit.span(), "unexpected suffix on char literal"));
249 }
250 Ok(Expr::LitChar(lit))
251 } else if allow_nonstring_literals && input.peek(LitInt) {
252 let lit: LitInt = input.parse()?;
253 match lit.suffix() {
254 "" | "i8" | "i16" | "i32" | "i64" | "i128" | "u8" | "u16" | "u32" | "u64"
255 | "u128" | "f16" | "f32" | "f64" | "f128" => {}
256 _ => {
257 return Err(Error::new(
258 lit.span(),
259 "unexpected suffix on integer literal",
260 ));
261 }
262 }
263 Ok(Expr::LitInt(lit))
264 } else if allow_nonstring_literals && input.peek(LitFloat) {
265 let lit: LitFloat = input.parse()?;
266 match lit.suffix() {
267 "" | "f16" | "f32" | "f64" | "f128" => {}
268 _ => return Err(Error::new(lit.span(), "unexpected suffix on float literal")),
269 }
270 Ok(Expr::LitFloat(lit))
271 } else if allow_nonstring_literals && input.peek(LitBool) {
272 input.parse().map(Expr::LitBool)
273 } else if lookahead.peek(kw::concat) {
274 input.parse().map(Expr::Concat)
275 } else if lookahead.peek(kw::env) {
276 input.parse().map(Expr::Env)
277 } else if lookahead.peek(kw::include) {
278 input.parse().map(Expr::Include)
279 } else if lookahead.peek(kw::include_str) {
280 input.parse().map(Expr::IncludeStr)
281 } else if lookahead.peek(kw::stringify) {
282 input.parse().map(Expr::Stringify)
283 } else if input.peek(Ident) && input.peek2(Token![!]) && input.peek3(Paren) {
284 let ident: Ident = input.parse()?;
285 let bang_token: Token![!] = input.parse()?;
286 let unsupported = quote!(#ident #bang_token);
287 Err(Error::new_spanned(
288 unsupported,
289 "unsupported macro, expected one of: `concat!`, `env!`, `include!`, `include_str!`, `stringify!`",
290 ))
291 } else {
292 Err(lookahead.error())
293 }
294 }
295}
296
297impl ToTokens for Expr {
298 fn to_tokens(&self, tokens: &mut TokenStream) {
299 match self {
300 Expr::LitStr(expr) => expr.to_tokens(tokens),
301 Expr::LitChar(expr) => expr.to_tokens(tokens),
302 Expr::LitInt(expr) => expr.to_tokens(tokens),
303 Expr::LitFloat(expr) => expr.to_tokens(tokens),
304 Expr::LitBool(expr) => expr.to_tokens(tokens),
305 Expr::Concat(expr) => expr.to_tokens(tokens),
306 Expr::Env(expr) => expr.to_tokens(tokens),
307 Expr::Include(expr) => expr.to_tokens(tokens),
308 Expr::IncludeStr(expr) => expr.to_tokens(tokens),
309 Expr::Stringify(expr) => expr.to_tokens(tokens),
310 }
311 }
312}
313
314macro_rules! macro_delimiter {
315 ($var:ident in $input:ident) => {{
316 let (delim, content) = $input.call(macro_delimiter)?;
317 $var = content;
318 delim
319 }};
320}
321
322fn macro_delimiter(input: ParseStream) -> Result<(MacroDelimiter, ParseBuffer)> {
323 let content;
324 let lookahead = input.lookahead1();
325 let delim = if input.peek(Paren) {
326 MacroDelimiter::Paren(parenthesized!(content in input))
327 } else if input.peek(Brace) {
328 MacroDelimiter::Brace(braced!(content in input))
329 } else if input.peek(Bracket) {
330 MacroDelimiter::Bracket(bracketed!(content in input))
331 } else {
332 return Err(lookahead.error());
333 };
334 Ok((delim, content))
335}
336
337impl MacroDelimiter {
338 fn surround<F>(&self, tokens: &mut TokenStream, f: F)
339 where
340 F: FnOnce(&mut TokenStream),
341 {
342 match self {
343 MacroDelimiter::Paren(delimiter) => delimiter.surround(tokens, f),
344 MacroDelimiter::Brace(delimiter) => delimiter.surround(tokens, f),
345 MacroDelimiter::Bracket(delimiter) => delimiter.surround(tokens, f),
346 }
347 }
348}
349
350impl Parse for Concat {
351 fn parse(input: ParseStream) -> Result<Self> {
352 let content;
353 Ok(Concat {
354 name: input.parse()?,
355 bang_token: input.parse()?,
356 delimiter: macro_delimiter!(content in input),
357 args: Punctuated::parse_terminated_with(&content, Expr::parse_any)?,
358 })
359 }
360}
361
362impl ToTokens for Concat {
363 fn to_tokens(&self, tokens: &mut TokenStream) {
364 self.name.to_tokens(tokens);
365 self.bang_token.to_tokens(tokens);
366 self.delimiter
367 .surround(tokens, |tokens| self.args.to_tokens(tokens));
368 }
369}
370
371impl Parse for Env {
372 fn parse(input: ParseStream) -> Result<Self> {
373 let content;
374 Ok(Env {
375 name: input.parse()?,
376 bang_token: input.parse()?,
377 delimiter: macro_delimiter!(content in input),
378 arg: Expr::parse_strict(&content).map(Box::new)?,
379 trailing_comma: content.parse()?,
380 })
381 }
382}
383
384impl ToTokens for Env {
385 fn to_tokens(&self, tokens: &mut TokenStream) {
386 self.name.to_tokens(tokens);
387 self.bang_token.to_tokens(tokens);
388 self.delimiter.surround(tokens, |tokens| {
389 self.arg.to_tokens(tokens);
390 self.trailing_comma.to_tokens(tokens);
391 });
392 }
393}
394
395impl Parse for Include {
396 fn parse(input: ParseStream) -> Result<Self> {
397 let content;
398 Ok(Include {
399 name: input.parse()?,
400 bang_token: input.parse()?,
401 delimiter: macro_delimiter!(content in input),
402 arg: Expr::parse_strict(&content).map(Box::new)?,
403 trailing_comma: content.parse()?,
404 })
405 }
406}
407
408impl ToTokens for Include {
409 fn to_tokens(&self, tokens: &mut TokenStream) {
410 self.name.to_tokens(tokens);
411 self.bang_token.to_tokens(tokens);
412 self.delimiter.surround(tokens, |tokens| {
413 self.arg.to_tokens(tokens);
414 self.trailing_comma.to_tokens(tokens);
415 });
416 }
417}
418
419impl Parse for IncludeStr {
420 fn parse(input: ParseStream) -> Result<Self> {
421 let content;
422 Ok(IncludeStr {
423 name: input.parse()?,
424 bang_token: input.parse()?,
425 delimiter: macro_delimiter!(content in input),
426 arg: Expr::parse_strict(&content).map(Box::new)?,
427 trailing_comma: content.parse()?,
428 })
429 }
430}
431
432impl ToTokens for IncludeStr {
433 fn to_tokens(&self, tokens: &mut TokenStream) {
434 self.name.to_tokens(tokens);
435 self.bang_token.to_tokens(tokens);
436 self.delimiter.surround(tokens, |tokens| {
437 self.arg.to_tokens(tokens);
438 self.trailing_comma.to_tokens(tokens);
439 });
440 }
441}
442
443impl Parse for Stringify {
444 fn parse(input: ParseStream) -> Result<Self> {
445 let content;
446 Ok(Stringify {
447 name: input.parse()?,
448 bang_token: input.parse()?,
449 delimiter: macro_delimiter!(content in input),
450 tokens: content.parse()?,
451 })
452 }
453}
454
455impl ToTokens for Stringify {
456 fn to_tokens(&self, tokens: &mut TokenStream) {
457 self.name.to_tokens(tokens);
458 self.bang_token.to_tokens(tokens);
459 self.delimiter
460 .surround(tokens, |tokens| self.tokens.to_tokens(tokens));
461 }
462}