1use proc_macro::*;
2use std::mem;
3
4#[proc_macro]
32pub fn quote(input: TokenStream) -> TokenStream {
33 let mut input = input.into_iter();
34
35 let var = parse_arg(&mut input, "expected `ident`");
36
37 let input = match input.next() {
38 Some(TokenTree::Group(g)) => g.stream(),
39 _ => panic!("expected `{{`"),
40 };
41
42 let mut output = TokenStream::new();
43 expend(input, &mut output, None, var);
44 output
45}
46
47fn parse_arg(input: &mut token_stream::IntoIter, msg: &str) -> Ident {
48 let Some(TokenTree::Ident(var)) = input.next() else {
49 panic!("{msg}")
50 };
51 input.next().expect("expected `,`");
52 var
53}
54
55#[proc_macro]
71pub fn quote_spanned(input: TokenStream) -> TokenStream {
72 let mut input = input.into_iter();
73
74 let span = parse_arg(&mut input, "expected `span`");
75 let var = parse_arg(&mut input, "expected `ident`");
76
77 let input = match input.next() {
78 Some(TokenTree::Group(g)) => g.stream(),
79 _ => panic!("expected `{{`"),
80 };
81
82 let mut output = TokenStream::new();
83 expend(input, &mut output, Some(&span), var);
84 output
85}
86
87fn expend(input: TokenStream, o: &mut TokenStream, span: Option<&Ident>, var: Ident) {
88 let mut input = input.into_iter().peekable();
89 let mut items = TokenStream::new();
90
91 while let Some(tree) = input.next() {
92 match tree {
93 TokenTree::Punct(punct) => {
94 let ch = punct.as_char();
95 if ch == '#' && matches!(input.peek(), Some(TokenTree::Ident(_))) {
96 write_extender(&mut items, o, &var);
97 let v = input.next();
98
99 o.extend([
100 tt(var.clone()),
101 tt::punct('.'),
102 tt::ident("add_tokens"),
103 tt::group('(', |o| {
104 add(o, tt::punct('&'));
105 o.extend(v);
106 }),
107 tt::punct(';'),
108 ]);
109 } else {
110 let varient_ty = match (punct.spacing(), span.is_some()) {
111 (Spacing::Joint, true) => "punct_join_span",
112 (Spacing::Alone, true) => "punct_span",
113 (Spacing::Joint, false) => "punct_join",
114 (Spacing::Alone, false) => "punct",
115 };
116 varient(&mut items, varient_ty, |o| {
117 add_span(o, span);
118 add(o, tt::char(ch));
119 });
120 }
121 }
122 TokenTree::Group(group) => {
123 let varient_ty = if span.is_some() {
124 "group_span"
125 } else {
126 "group"
127 };
128 varient(&mut items, varient_ty, |o| {
129 add_span(o, span);
130
131 let var = Ident::new("__o", Span::call_site());
132 o.extend([
133 tt::char(match group.delimiter() {
134 Delimiter::None => '_',
135 Delimiter::Brace => '{',
136 Delimiter::Bracket => '[',
137 Delimiter::Parenthesis => '(',
138 }),
139 tt::punct(','),
140 tt::punct('|'),
141 tt(var.clone()),
142 tt::punct('|'),
143 tt::group('{', |o| {
144 expend(group.stream(), o, span, var);
145 }),
146 ]);
147 });
148 }
149 TokenTree::Ident(ident) => {
150 let varient_ty = if span.is_some() {
151 "ident_span"
152 } else {
153 "ident"
154 };
155 varient(&mut items, varient_ty, |o| {
156 add_span(o, span);
157 add(o, Literal::string(&ident.to_string()));
158 });
159 }
160 TokenTree::Literal(lit) => {
161 let varient_ty = if span.is_some() {
162 "parsed_lit_span"
163 } else {
164 "parsed_lit"
165 };
166 varient(&mut items, varient_ty, |o| {
167 add_span(o, span);
168 add(o, Literal::string(&lit.to_string()));
169 });
170 }
171 }
172 }
173 write_extender(&mut items, o, &var);
174}
175
176fn write_extender(items: &mut TokenStream, o: &mut TokenStream, var: &Ident) {
177 if !items.is_empty() {
178 let items = mem::take(items);
179 o.extend([
180 tt(var.clone()),
181 tt::punct('.'),
182 tt::ident("extend"),
183 tt::group('(', |o| {
184 add(o, Group::new(Delimiter::Bracket, items));
185 }),
186 tt::punct(';'),
187 ]);
188 }
189}
190
191fn add(o: &mut TokenStream, t: impl Into<TokenTree>) {
192 o.extend(Some(t.into()));
193}
194
195fn add_span(o: &mut TokenStream, span: Option<&Ident>) {
196 if let Some(spanned) = span {
197 o.extend([tt(spanned.clone()), tt::punct(',')]);
198 }
199}
200
201fn varient(t: &mut TokenStream, varient_ty: &str, f: impl FnOnce(&mut TokenStream)) {
202 t.extend([
203 tt::ident("quote2"),
204 tt::punct_joined(':'),
205 tt::punct(':'),
206 tt::ident("tt"),
207 tt::punct_joined(':'),
208 tt::punct(':'),
209 tt::ident(varient_ty),
210 tt::group('(', f),
211 tt::punct(','),
212 ]);
213}
214
215fn tt<T: Into<TokenTree>>(tt: T) -> TokenTree {
216 tt.into()
217}
218
219mod tt {
220 use super::*;
221 pub fn punct_joined(ch: char) -> TokenTree {
222 TokenTree::from(Punct::new(ch, Spacing::Joint))
223 }
224 pub fn punct(ch: char) -> TokenTree {
225 TokenTree::from(Punct::new(ch, Spacing::Alone))
226 }
227 pub fn ident(string: &str) -> TokenTree {
228 TokenTree::from(Ident::new(string, Span::call_site()))
229 }
230 pub fn char(ch: char) -> TokenTree {
231 TokenTree::from(Literal::character(ch))
232 }
233
234 pub fn group(delimiter: char, f: impl FnOnce(&mut TokenStream)) -> TokenTree {
235 let mut stream = TokenStream::new();
236 f(&mut stream);
237 let delimiter = match delimiter {
238 '{' => Delimiter::Brace,
239 '[' => Delimiter::Bracket,
240 '(' => Delimiter::Parenthesis,
241 _ => Delimiter::None,
242 };
243 Group::new(delimiter, stream).into()
244 }
245}