1#![doc(html_root_url = "https://docs.rs/seq-macro/0.3.5")]
102#![allow(
103 clippy::cast_lossless,
104 clippy::cast_possible_truncation,
105 clippy::derive_partial_eq_without_eq,
106 clippy::into_iter_without_iter,
107 clippy::let_underscore_untyped,
108 clippy::needless_doctest_main,
109 clippy::single_match_else,
110 clippy::wildcard_imports
111)]
112
113use proc_macro2::{Delimiter, Group, Ident, Spacing, Span, TokenStream, TokenTree};
114use std::iter::{self, FromIterator};
115use std::mem;
116use syn::{
117 parse::{Parse, ParseStream},
118 parse_macro_input, Error, Result, Token,
119};
120
121#[derive(Debug)]
122struct SeqInput {
123 ident: Ident,
124 script: TokenStream,
125 block: TokenStream,
126}
127
128impl Parse for SeqInput {
129 fn parse(input: ParseStream) -> Result<Self> {
130 let ident: Ident = input.parse()?;
131 input.parse::<Token![in]>()?;
132 let (script, block) = input.step(|cursor| {
133 let mut cur = *cursor;
134 let mut script = TokenStream::new();
135 let mut block: Option<TokenTree> = None;
136
137 while let Some((tt, next)) = cur.token_tree() {
138 if let Some(ref mut block) = block {
139 let old_block = mem::replace(block, tt.clone());
140 script.extend(std::iter::once(old_block));
141 } else {
142 block = Some(tt.clone());
143 }
144 cur = next;
145 }
146 Ok(((script, block), cur))
147 })?;
148
149 let Some(block) = block else {
150 return Err(Error::new(Span::call_site(), "Expected block"));
151 };
152 let TokenTree::Group(block) = block else {
153 return Err(Error::new(block.span(), "Expected block"));
154 };
155
156 Ok(SeqInput {
157 ident,
158 script,
159 block: block.stream(),
160 })
161 }
162}
163
164#[proc_macro]
165pub fn seq(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
166 let input = parse_macro_input!(input as SeqInput);
167
168 let output = seq_impl(input).unwrap_or_else(Error::into_compile_error);
169 proc_macro::TokenStream::from(output)
170}
171
172fn seq_impl(
173 SeqInput {
174 ident,
175 script,
176 block,
177 }: SeqInput,
178) -> Result<TokenStream> {
179 let script = rewrite_script(script);
181 let script_span = Span::call_site(); let mut engine = rhai::Engine::new();
184
185 fn rhai_collect<T: IntoIterator<Item: Into<rhai::Dynamic>>>(inp: T) -> rhai::Array {
186 inp.into_iter().map(|x| x.into()).collect()
187 }
188 engine.register_fn("collect", rhai_collect::<std::ops::Range<i64>>);
189 engine.register_fn("collect", rhai_collect::<std::ops::RangeInclusive<i64>>);
190
191 let output: rhai::Dynamic = engine
192 .eval(&script)
193 .map_err(|e| Error::new(script_span, e.to_string()))?;
194
195 let list: Vec<_> = if let Some(r) = output.clone().try_cast::<std::ops::Range<i64>>() {
197 r.map(|x| x.to_string()).collect()
198 } else if let Some(r) = output.clone().try_cast::<std::ops::RangeInclusive<i64>>() {
199 r.map(|x| x.to_string()).collect()
200 } else if let Some(a) = output.clone().try_cast::<Vec<rhai::Dynamic>>() {
201 a.into_iter().map(|d| d.to_string()).collect()
202 } else {
203 return Err(Error::new(script_span, "Bad expression type"));
204 };
205
206 let mut found_repetition = false;
213 let expanded = expand_repetitions(&ident, &list, block.clone(), &mut found_repetition);
214 if found_repetition {
215 Ok(expanded)
216 } else {
217 Ok(repeat(&ident, &list, &block))
219 }
220}
221
222fn rewrite_script(script: TokenStream) -> String {
223 fn dollar_str(tokens: &[TokenTree]) -> Option<String> {
224 assert!(tokens.len() == 3);
225 match &tokens[0] {
226 TokenTree::Punct(punct) if punct.as_char() == '$' => {}
227 _ => return None,
228 }
229 match &tokens[2] {
230 TokenTree::Punct(punct) if punct.as_char() == '$' => {}
231 _ => return None,
232 }
233 match &tokens[1] {
234 TokenTree::Literal(lit) => {
235 let content = lit.to_string();
236 let mut chars = content.chars();
237 match chars.next() {
238 Some('"') => {}
239 _ => return None,
240 }
241 match chars.next_back() {
242 Some('"') => {}
243 _ => return None,
244 }
245 return Some(format!("`{}`", chars.as_str()));
246 }
247 _ => return None,
248 }
249 }
250
251 let tokens = Vec::from_iter(script);
253 let mut output = String::new();
254 let mut i = 0;
255 while i < tokens.len() {
256 if let TokenTree::Group(group) = &tokens[i] {
257 match group.delimiter() {
258 Delimiter::Parenthesis => {
259 output.push('(');
260 }
261 Delimiter::Brace => {
262 output.push('{');
263 }
264 Delimiter::Bracket => {
265 output.push('[');
266 }
267 Delimiter::None => {}
268 }
269 output.push(' ');
270 output.push_str(&rewrite_script(group.stream()));
271 match group.delimiter() {
272 Delimiter::Parenthesis => {
273 output.push(')');
274 }
275 Delimiter::Brace => {
276 output.push('}');
277 }
278 Delimiter::Bracket => {
279 output.push(']');
280 }
281 Delimiter::None => {}
282 }
283 output.push(' ');
284 i += 1;
285 continue;
286 }
287 if i + 3 <= tokens.len() {
288 if let Some(backtick_str) = dollar_str(&tokens[i..i + 3]) {
289 output.push_str(&backtick_str);
290 output.push(' ');
291 i += 3;
292 continue;
293 }
294 }
295
296 match &tokens[i] {
297 TokenTree::Group(_) => {
298 unreachable!();
299 }
300 TokenTree::Ident(i) => {
301 output.push_str(&i.to_string());
302 output.push(' ');
303 }
304 TokenTree::Punct(p) => {
305 output.push_str(&p.to_string());
306 match p.spacing() {
307 Spacing::Alone => {
308 output.push(' ');
309 }
310 Spacing::Joint => {}
311 }
312 }
313 TokenTree::Literal(l) => {
314 output.push_str(&l.to_string());
315 output.push(' ');
316 }
317 }
318 i += 1;
319 }
335
336 output
339}
340
341fn repeat(var: &Ident, list: &[String], body: &TokenStream) -> TokenStream {
342 let mut repeated = TokenStream::new();
343 for value in list {
344 repeated.extend(substitute_value(var, value, body.clone()));
345 }
346 repeated
347}
348
349fn substitute_value(var: &Ident, value: &str, body: TokenStream) -> TokenStream {
350 let mut tokens = Vec::from_iter(body);
351
352 let mut i = 0;
353 while i < tokens.len() {
354 let replace = match &tokens[i] {
356 TokenTree::Ident(ident) => ident.to_string() == var.to_string(),
357 _ => false,
358 };
359 if replace {
360 let original_span = tokens[i].span();
361
362 let new_tokens = value.parse::<TokenStream>().unwrap();
363
364 tokens.splice(
365 i..i + 1,
366 new_tokens.into_iter().map(|mut t| {
367 t.set_span(original_span);
368 t
369 }),
370 );
371
372 continue;
383 }
384
385 if i + 3 <= tokens.len() {
387 let prefix = match &tokens[i..i + 3] {
388 [first, TokenTree::Punct(tilde), TokenTree::Ident(ident)]
389 if tilde.as_char() == '~' && ident.to_string() == var.to_string() =>
390 {
391 match first {
392 TokenTree::Ident(ident) => Some(ident.clone()),
393 TokenTree::Group(group) => {
394 let mut iter = group.stream().into_iter().fuse();
395 match (iter.next(), iter.next()) {
396 (Some(TokenTree::Ident(ident)), None) => Some(ident),
397 _ => None,
398 }
399 }
400 _ => None,
401 }
402 }
403 _ => None,
404 };
405 if let Some(prefix) = prefix {
406 let concat = format!("{}{}", prefix, value);
407 let ident = Ident::new(&concat, prefix.span());
408 tokens.splice(i..i + 3, iter::once(TokenTree::Ident(ident)));
409 i += 1;
410 continue;
411 }
412 }
413
414 if let TokenTree::Group(group) = &mut tokens[i] {
416 let original_span = group.span();
417 let content = substitute_value(var, value, group.stream());
418 *group = Group::new(group.delimiter(), content);
419 group.set_span(original_span);
420 }
421
422 i += 1;
423 }
424
425 TokenStream::from_iter(tokens)
426}
427
428fn enter_repetition(tokens: &[TokenTree]) -> Option<TokenStream> {
429 assert!(tokens.len() == 3);
430 match &tokens[0] {
431 TokenTree::Punct(punct) if punct.as_char() == '#' => {}
432 _ => return None,
433 }
434 match &tokens[2] {
435 TokenTree::Punct(punct) if punct.as_char() == '*' => {}
436 _ => return None,
437 }
438 match &tokens[1] {
439 TokenTree::Group(group) if group.delimiter() == Delimiter::Parenthesis => {
440 Some(group.stream())
441 }
442 _ => None,
443 }
444}
445
446fn enter_single(tokens: &[TokenTree]) -> Option<TokenStream> {
447 assert!(tokens.len() == 3);
448 match &tokens[0] {
449 TokenTree::Punct(punct) if punct.as_char() == '#' => {}
450 _ => return None,
451 }
452 match &tokens[2] {
453 TokenTree::Punct(punct) if punct.as_char() == '#' => {}
454 _ => return None,
455 }
456 match &tokens[1] {
457 TokenTree::Group(group) if group.delimiter() == Delimiter::Parenthesis => {
458 Some(group.stream())
459 }
460 _ => None,
461 }
462}
463
464fn expand_repetitions(
465 var: &Ident,
466 range: &[String],
467 body: TokenStream,
468 found_repetition: &mut bool,
469) -> TokenStream {
470 let mut tokens = Vec::from_iter(body);
471
472 let mut i = 0;
474 while i < tokens.len() {
475 if let TokenTree::Group(group) = &mut tokens[i] {
476 let content = expand_repetitions(var, range, group.stream(), found_repetition);
477 let original_span = group.span();
478 *group = Group::new(group.delimiter(), content);
479 group.set_span(original_span);
480 i += 1;
481 continue;
482 }
483 if i + 3 > tokens.len() {
484 i += 1;
485 continue;
486 }
487 if let Some(template) = enter_repetition(&tokens[i..i + 3]) {
488 *found_repetition = true;
489 let mut repeated = Vec::new();
490 for value in range {
491 repeated.extend(substitute_value(var, &value, template.clone()));
492 }
493 let repeated_len = repeated.len();
494 tokens.splice(i..i + 3, repeated);
495 i += repeated_len;
496 continue;
497 }
498 if let Some(template) = enter_single(&tokens[i..i + 3]) {
499 tokens.splice(i..i + 3, template);
500 *found_repetition = true;
501 i += 1;
502 continue;
503 }
504 i += 1;
506 continue;
507 }
508
509 TokenStream::from_iter(tokens)
510}
511
512#[cfg(test)]
513mod test {
514 use crate::{rewrite_script, seq_impl};
515 use quote::quote;
516
517 #[test]
518 fn test_string_rewrite() {
519 let inp = quote! {
520 let a = (
521 $"${x}"$
522 );
523 };
524
525 let result = rewrite_script(inp);
526 assert_eq!(result, "let a = ( `${x}` ) ; ");
527 }
528
529 #[test]
530 fn test_range() {
531 let inp = quote! {
532 A in 0..3 {
533 println("{}", A);
534 }
535 };
536
537 let result = seq_impl(syn::parse2(inp).unwrap()).unwrap();
538 assert_eq!(
539 result.to_string(),
540 "println (\"{}\" , 0) ; println (\"{}\" , 1) ; println (\"{}\" , 2) ;"
541 );
542 }
543
544 #[test]
545 fn test_int_array() {
546 let inp = quote! {
547 A in [0,1,2] {
548 println("{}", A);
549 }
550 };
551
552 let result = seq_impl(syn::parse2(inp).unwrap()).unwrap();
553 assert_eq!(
554 result.to_string(),
555 "println (\"{}\" , 0) ; println (\"{}\" , 1) ; println (\"{}\" , 2) ;"
556 );
557 }
558
559 #[test]
560 fn test_str_array() {
561 let inp = quote! {
562 A in (0..3).collect().map(|x| $"${x}"$) {
563 println("{}", A);
564 }
565 };
566
567 let result = seq_impl(syn::parse2(inp).unwrap()).unwrap();
568 assert_eq!(
569 result.to_string(),
570 "println (\"{}\" , 0) ; println (\"{}\" , 1) ; println (\"{}\" , 2) ;"
571 );
572 }
573}