stylish_macros/
lib.rs

1//! Internal implementation details of [`stylish-core`](https://docs.rs/stylish-core).
2//!
3//! Do not depend on this crate directly.
4
5#![allow(uncommon_codepoints)]
6#![cfg_attr(stylish_proc_macro_expand, feature(proc_macro_expand))]
7
8use std::collections::{HashMap, HashSet};
9
10use proc_macro2::Span;
11use quote::{quote, ToTokens};
12use syn::{
13    parse::{ParseStream, Result},
14    parse_macro_input, Expr, ExprAssign, ExprPath, Ident, Index, LitStr, Path, PathArguments,
15    Token,
16};
17
18use self::{
19    format::{Format, FormatArg, FormatArgRef, FormatSpec, Parse as _, Piece},
20    to_tokens::Scoped,
21};
22
23mod format;
24mod to_tokens;
25
26struct ArgsInput {
27    krate: Option<Path>,
28    format: LitStr,
29    positional_args: Vec<Expr>,
30    named_args: Vec<(Ident, Expr)>,
31}
32
33impl syn::parse::Parse for ArgsInput {
34    fn parse(input: ParseStream<'_>) -> Result<Self> {
35        let krate = if input.peek(Token![crate]) {
36            input.parse::<Token![crate]>()?;
37            input.parse::<Token![=]>()?;
38            let res = input.parse()?;
39            input.parse::<Token![,]>()?;
40            Some(res)
41        } else {
42            None
43        };
44        #[cfg(not(stylish_proc_macro_expand))]
45        let format = input.parse()?;
46        #[cfg(stylish_proc_macro_expand)]
47        let format = {
48            use syn::spanned::Spanned;
49            let expr = input.parse::<Expr>()?;
50            let span = expr.span();
51            let tokens = proc_macro::TokenStream::from(expr.into_token_stream());
52            let expanded = tokens.expand_expr().map_err(|e| {
53                syn::parse::Error::new(span, format!("failed to expand format string: {e}"))
54            })?;
55            #[cfg(stylish_proc_macro_expand_debug)]
56            if expanded.to_string() != tokens.to_string() {
57                eprintln!("{tokens} => {expanded}");
58            }
59            syn::parse(expanded)?
60        };
61        let mut positional_args = Vec::new();
62        let mut named_args = Vec::new();
63        let mut onto_named = false;
64        while input.peek(Token![,]) {
65            input.parse::<Token![,]>()?;
66            if input.is_empty() {
67                break;
68            }
69            let expr = input.parse::<Expr>()?;
70            match expr {
71                Expr::Assign(ExprAssign { left, right, .. }) if matches!(&*left, Expr::Path(ExprPath { path, .. }) if path.segments.len() == 1 && matches!(path.segments[0].arguments, PathArguments::None)) =>
72                {
73                    let ident = if let Expr::Path(ExprPath { mut path, .. }) = *left {
74                        path.segments.pop().unwrap().into_value().ident
75                    } else {
76                        panic!()
77                    };
78                    named_args.push((ident, *right));
79                    onto_named = true;
80                }
81                expr => {
82                    if onto_named {
83                        panic!("positional arg after named")
84                    }
85                    positional_args.push(expr);
86                }
87            }
88        }
89        Ok(Self {
90            krate,
91            format,
92            positional_args,
93            named_args,
94        })
95    }
96}
97
98fn format_args_impl(
99    ArgsInput {
100        krate,
101        format,
102        positional_args,
103        named_args,
104    }: ArgsInput,
105) -> impl ToTokens {
106    let krate = krate.expect("base crate not specified (are you using stylish-macros directly instead of through stylish-core?)");
107    let export: syn::Path = syn::parse_quote!(#krate::𓀄);
108
109    let span = format.span();
110    let format_string = &format;
111    let format = format.value();
112    let (leftover, format) = Format::parse(&format).unwrap();
113    assert!(leftover.is_empty());
114    let num_positional_args = positional_args.len();
115    let positional_args = positional_args.into_iter();
116    let positional_args = quote! { (#(&#positional_args,)*) };
117    let (named_args_names, named_args_values): (Vec<_>, Vec<_>) = named_args.into_iter().unzip();
118    let named_args_names: HashMap<String, usize> = named_args_names
119        .into_iter()
120        .map(|name| name.to_string())
121        .enumerate()
122        .map(|(i, s)| (s, i))
123        .collect();
124    let named_args_values = named_args_values.into_iter();
125    let named_args_values = quote! {
126        (#(&#named_args_values,)*)
127    };
128    let mut implicit_named_args_values = Vec::new();
129    let mut used_positional_args = HashSet::new();
130    let mut used_named_args = HashSet::new();
131    let mut next_arg_iter = 0..num_positional_args;
132    let statements: Vec<_> = format
133        .pieces
134        .into_iter()
135        .map(|piece| match piece {
136            Piece::Lit(lit) => {
137                let lit = LitStr::new(&lit.replace("{{", "{"), span);
138                quote!(#export::Formatter::write_str(__stylish_formatter, #lit)?)
139            }
140            Piece::Arg(FormatArg {
141                arg,
142                format_spec:
143                    FormatSpec {
144                        formatter_args,
145                        style,
146                        format_trait,
147                    },
148            }) => {
149                let formatter_args = Scoped::new(&export, &formatter_args);
150                let style = Scoped::new(&export, &style);
151                let arg = match arg {
152                    None => {
153                        let i = next_arg_iter.next().expect("missing argument");
154                        let index = Index::from(i);
155                        used_positional_args.insert(i);
156                        quote!(__stylish_positional_args.#index)
157                    }
158                    Some(FormatArgRef::Positional(i)) => {
159                        let index = Index::from(i);
160                        if i >= num_positional_args {
161                            panic!("missing positional argument {i}");
162                        }
163                        used_positional_args.insert(i);
164                        quote!(__stylish_positional_args.#index)
165                    }
166                    Some(FormatArgRef::Named(name)) => {
167                        if let Some(&i) = named_args_names.get(name) {
168                            let index = Index::from(i);
169                            used_named_args.insert(name.to_owned());
170                            quote!(__stylish_named_args.#index)
171                        } else {
172                            let i = implicit_named_args_values.len();
173                            implicit_named_args_values.push(ExprPath {
174                                attrs: Vec::new(),
175                                qself: None,
176                                path: Ident::new_raw(
177                                    name,
178                                    Span::call_site().resolved_at(format_string.span()),
179                                )
180                                .into(),
181                            });
182                            let index = Index::from(i);
183                            quote!(__stylish_implicit_named_args.#index)
184                        }
185                    }
186                };
187                let arg = (format_trait, arg);
188                let arg = Scoped::new(&export, &arg);
189                quote! {
190                    #export::Display::fmt(
191                        &#arg,
192                        &mut #export::Formatter::with_args(
193                            __stylish_formatter,
194                            #formatter_args,
195                            #style
196                        ),
197                    )?
198                }
199            }
200        })
201        .collect();
202    let unused_positional_args =
203        &HashSet::from_iter(0..num_positional_args) - &used_positional_args;
204    let unused_named_args =
205        &HashSet::from_iter(named_args_names.keys().cloned()) - &used_named_args;
206    if !unused_positional_args.is_empty() || !unused_named_args.is_empty() {
207        panic!("unused formatting arguments");
208    }
209    let implicit_named_args = quote! {
210        (#(&#implicit_named_args_values,)*)
211    };
212    quote! {
213        #export::Arguments {
214            f: &match (#positional_args, #named_args_values, #implicit_named_args) {
215                (__stylish_positional_args, __stylish_named_args, __stylish_implicit_named_args) => {
216                    #[inline]
217                    move |__stylish_formatter: &mut #export::Formatter| -> #export::fmt::Result {
218                        #(#statements;)*
219                        #export::fmt::Result::Ok(())
220                    }
221                }
222            }
223        }
224    }
225}
226
227/// Internal implementation details of
228/// [`stylish_core::format_args!`](https://docs.rs/stylish-core/latest/stylish_core/macro.format_args.html).
229#[proc_macro]
230pub fn format_args(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
231    format_args_impl(parse_macro_input!(input as ArgsInput))
232        .into_token_stream()
233        .into()
234}
235
236/// Internal implementation details of
237/// [`stylish_core::format_args!`](https://docs.rs/stylish-core/latest/stylish_core/macro.format_args.html).
238#[proc_macro]
239pub fn format_args_nl(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
240    let input = parse_macro_input!(input as ArgsInput);
241    let format = LitStr::new(&(input.format.value() + "\n"), input.format.span());
242    format_args_impl(ArgsInput { format, ..input })
243        .into_token_stream()
244        .into()
245}