1use proc_macro2::{
2 Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree,
3 token_stream::IntoIter,
4};
5use quote::{TokenStreamExt, quote as oquote, quote_spanned as oquote_spanned};
6
7#[proc_macro]
48pub fn chunk(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
49 chunk_impl(TokenStream::from(input), true, false).into()
50}
51
52#[proc_macro]
56pub fn chunk_spanned(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
57 chunk_impl(TokenStream::from(input), true, true).into()
58}
59
60#[proc_macro]
62pub fn quote(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
63 chunk_impl(TokenStream::from(input), false, false).into()
64}
65
66#[proc_macro]
70pub fn quote_spanned(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
71 chunk_impl(TokenStream::from(input), false, true).into()
72}
73
74struct Cursor {
76 iter: IntoIter,
77 cur_token: Option<TokenTree>,
79 res: TokenStream,
81 end_span: Span,
83}
84
85impl Cursor {
86 fn new(iter: TokenStream, end_span: Span) -> Self {
88 Self {
89 iter: iter.into_iter(),
90 res: TokenStream::new(),
91 cur_token: None,
92 end_span,
93 }
94 }
95 fn next(&mut self) -> Option<TokenTree> {
97 self.cur_token.take().or_else(|| self.iter.next())
98 }
99 fn peek(&mut self) -> Option<&TokenTree> {
101 if self.cur_token.is_none() {
102 self.cur_token = self.iter.next();
103 }
104 self.cur_token.as_ref()
105 }
106 fn peek_kw(&mut self, kw: &str) -> bool {
108 match self.peek() {
109 Some(TokenTree::Ident(ident)) => ident == kw,
110 _ => false,
111 }
112 }
113 fn add<T>(&mut self, tokens: impl IntoIterator<Item = T>)
115 where
116 TokenStream: Extend<T>,
117 {
118 self.res.extend(tokens);
119 }
120 fn expected(&mut self, expected: &str, span: Option<Span>) {
122 let span = span.unwrap_or(self.end_span);
123 let msg = &format!("expected {expected}");
124 self.res.extend(oquote_spanned! { span => ::core::compile_error!(#msg); });
125 }
126 fn eat_ident(&mut self) -> Option<Ident> {
128 match self.next() {
129 Some(TokenTree::Ident(ident)) => return Some(ident),
130 t => self.expected("identifier", t.map(|t| t.span())),
131 }
132 None
133 }
134 fn eat_punct(&mut self, char: char) -> bool {
136 match self.next() {
137 Some(TokenTree::Punct(punct)) if punct.as_char() == char => return true,
138 t => self.expected(&format!("`{char}`"), t.map(|t| t.span())),
139 }
140 false
141 }
142 fn eat_brace(&mut self) -> Option<Group> {
144 match self.next() {
145 Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Brace => {
146 return Some(g);
147 }
148 t => self.expected("`(`", t.map(|t| t.span())),
149 }
150 None
151 }
152 fn eat_until(
154 &mut self, expected: &str, pred: impl Fn(&TokenTree) -> bool,
155 ) -> Option<TokenStream> {
156 let mut tokens = TokenStream::new();
157 while let Some(token) = self.peek() {
158 if pred(token) {
159 break;
160 }
161 tokens.append(self.next().unwrap());
162 }
163 if tokens.is_empty() {
164 let span = self.peek().map(|t| t.span());
165 self.expected(expected, span);
166 return None;
167 }
168 Some(tokens)
169 }
170}
171
172fn is_punct(token: &TokenTree, char: char) -> bool {
174 matches!(token, TokenTree::Punct(punct) if punct.as_char() == char)
175}
176
177fn chunk_impl(input: TokenStream, chunked: bool, spanned: bool) -> TokenStream {
185 let mut cur = Cursor::new(input, Span::call_site());
186
187 cur.add(oquote! { #[allow(unused)] use ::chunked_quote::__private::{
188 TokenStream as __TS, TokenStreamExt as _, Delimiter as __Del, ToTokens as __TT,
189 }; });
190
191 let stream_ident = if chunked {
192 let Some(stream_ident) = cur.eat_ident() else { return cur.res };
193 if !cur.eat_punct(',') {
194 return cur.res;
195 };
196 stream_ident
197 } else {
198 cur.add(oquote! { let mut __stream = __TS::new(); });
199 Ident::new("__stream", Span::call_site())
200 };
201
202 if spanned {
203 let Some(span) = cur.eat_until("an expression", |t| is_punct(t, ',')) else {
204 return cur.res;
205 };
206 if !cur.eat_punct(',') {
207 return cur.res;
208 }
209 cur.add(oquote! { let __span = #span; });
210 } else {
211 cur.add(oquote! { let __span = ::chunked_quote::__private::Span::call_site(); })
212 }
213
214 quote_stream(&mut cur, &stream_ident);
215 if !chunked {
217 cur.add(Some(stream_ident));
218 }
219 let res = cur.res;
220 oquote! { { #res } }
221}
222
223fn quote_punct(cur: &mut Cursor, punct: &Punct, stream_ident: &Ident) {
225 let char = punct.as_char();
226 match punct.spacing() {
227 Spacing::Joint => {
228 cur.add(oquote! { #stream_ident.__push_punct_joint(#char, __span); })
229 }
230 Spacing::Alone => {
231 cur.add(oquote! { #stream_ident.__push_punct_alone(#char, __span); })
232 }
233 }
234}
235
236fn quote_ident(cur: &mut Cursor, ident: &Ident, stream_ident: &Ident) {
238 let name = ident.to_string();
239 match name.strip_prefix("r#") {
240 Some(name) => cur.add(oquote! { #stream_ident.__push_ident_raw(#name, __span); }),
241 None => cur.add(oquote! { #stream_ident.__push_ident(#name, __span); }),
242 }
243}
244
245fn quote_literal(cur: &mut Cursor, lit: &Literal, stream_ident: &Ident) {
247 let text = lit.to_string();
248 cur.add(oquote! { #stream_ident.__push_literal(#text, __span); });
249}
250
251fn quote_group(cur: &mut Cursor, group: &Group, stream_ident: &Ident) {
253 let delimiter = match group.delimiter() {
254 Delimiter::Parenthesis => oquote! { __Del::Parenthesis },
255 Delimiter::Brace => oquote! { __Del::Brace },
256 Delimiter::Bracket => oquote! { __Del::Bracket },
257 Delimiter::None => oquote! { __Del::None },
258 };
259
260 let mut inner_cur = Cursor::new(group.stream(), group.span_close());
261 quote_stream(&mut inner_cur, stream_ident);
262 let inner = inner_cur.res;
263
264 cur.add(oquote! { #stream_ident.__push_group(#delimiter, __span, {
265 let mut #stream_ident = __TS::new();
266 #inner
267 #stream_ident
268 }); });
269}
270
271fn quote_stream(cur: &mut Cursor, stream_ident: &Ident) {
273 while let Some(token) = cur.next() {
274 match token {
275 TokenTree::Ident(ident) => quote_ident(cur, &ident, stream_ident),
276 TokenTree::Literal(lit) => quote_literal(cur, &lit, stream_ident),
277 TokenTree::Punct(p) if p.as_char() == '#' => {
278 handle_directive(cur, stream_ident);
279 }
280 TokenTree::Punct(punct) => quote_punct(cur, &punct, stream_ident),
281 TokenTree::Group(group) => quote_group(cur, &group, stream_ident),
282 }
283 }
284}
285
286fn kw_expr_body(cur: &mut Cursor, ident: Ident, stream_ident: &Ident) -> Option<()> {
288 cur.add(Some(ident));
289
290 let expr = cur.eat_until("an expression", |t| is_punct(t, '#'))?;
292 cur.add(expr);
293
294 body(cur, true, stream_ident)
295}
296
297fn body(cur: &mut Cursor, eat_hash: bool, stream_ident: &Ident) -> Option<()> {
299 if eat_hash {
300 cur.eat_punct('#').then_some(())?;
301 }
302 let body = cur.eat_brace()?;
303
304 let mut inner_cur = Cursor::new(body.stream(), body.span_close());
305 quote_stream(&mut inner_cur, stream_ident);
306
307 cur.add(Some(Group::new(Delimiter::Brace, inner_cur.res)));
308 Some(())
309}
310
311fn is_brace(token: &TokenTree) -> bool {
313 matches!(token, TokenTree::Group(group) if group.delimiter() == Delimiter::Brace)
314}
315
316fn handle_match(cur: &mut Cursor, ident: Ident, stream_ident: &Ident) -> Option<()> {
318 cur.add(Some(ident));
319 let expr = cur.eat_until("an expression", is_brace)?;
320 cur.add(expr);
321
322 let arms = cur.eat_brace()?;
323 let mut arms_cur = Cursor::new(arms.stream(), arms.span_close());
324
325 while let Some(token) = arms_cur.next() {
326 if is_punct(&token, '#') {
327 body(&mut arms_cur, false, stream_ident)?;
328 } else {
329 arms_cur.add(Some(token));
330 }
331 }
332
333 cur.add(Some(Group::new(Delimiter::Brace, arms_cur.res)));
334 Some(())
335}
336
337fn handle_directive(cur: &mut Cursor, stream_ident: &Ident) -> Option<()> {
339 match cur.next() {
340 Some(TokenTree::Punct(p)) if p.as_char() == '#' => {
342 quote_punct(cur, &p, stream_ident)
343 }
344 Some(TokenTree::Group(group)) if group.delimiter() == Delimiter::Brace => {
346 cur.add(oquote! { __TT::to_tokens(&#group, &mut #stream_ident); })
347 }
348 Some(TokenTree::Ident(ident)) => match &*ident.to_string() {
349 "if" | "for" | "while" => kw_expr_body(cur, ident, stream_ident)?,
351 "else" => {
352 if cur.peek_kw("if") {
353 kw_expr_body(cur, ident, stream_ident)?
354 } else {
355 cur.add(Some(ident));
356 body(cur, true, stream_ident)?
357 }
358 }
359 "match" => handle_match(cur, ident, stream_ident)?,
360 "do" => {
362 let block = cur.eat_brace()?;
363 cur.add(oquote! {{#[allow(unused_braces)] #block }})
364 }
365 _ => cur.add(oquote! { __TT::to_tokens(&#ident, &mut #stream_ident); }),
367 },
368 t => cur.expected("an identifier, `#` or `{`", t.map(|t| t.span())),
369 };
370 Some(())
371}