1#![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#[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#[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}