//! [![github]](https://github.com/dtolnay/seq-macro) [![crates-io]](https://crates.io/crates/seq-macro) [![docs-rs]](https://docs.rs/seq-macro) //! //! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github //! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust //! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs //! //!
//! //! # Imagine for-loops in a macro //! //! This crate provides a `seq!` macro to repeat a fragment of source code and //! substitute into each repetition a sequential numeric counter. //! //! ``` //! use seq_macro::seq; //! //! fn main() { //! let tuple = (1000, 100, 10); //! let mut sum = 0; //! //! // Expands to: //! // //! // sum += tuple.0; //! // sum += tuple.1; //! // sum += tuple.2; //! // //! // This cannot be written using an ordinary for-loop because elements of //! // a tuple can only be accessed by their integer literal index, not by a //! // variable. //! seq!(N in 0..=2 { //! sum += tuple.N; //! }); //! //! assert_eq!(sum, 1110); //! } //! ``` //! //! - If the input tokens contain a section surrounded by `#(` ... `)*` then //! only that part is repeated. //! //! - The numeric counter can be pasted onto the end of some prefix to form //! sequential identifiers. //! //! ``` //! use seq_macro::seq; //! //! seq!(N in 64..=127 { //! #[derive(Debug)] //! enum Demo { //! // Expands to Variant64, Variant65, ... //! ##( //! Variant~N, //! )* //! } //! }); //! //! fn main() { //! assert_eq!("Variant99", format!("{:?}", Demo::Variant99)); //! } //! ``` //! //! - Byte and character ranges are supported: `b'a'..=b'z'`, `'a'..='z'`. //! //! - If the range bounds are written in binary, octal, hex, or with zero //! padding, those features are preserved in any generated tokens. //! //! ``` //! use seq_macro::seq; //! //! seq!(P in 0x000..=0x00F { //! // expands to structs Pin000, ..., Pin009, Pin00A, ..., Pin00F //! struct Pin~P; //! }); //! ``` #![doc(html_root_url = "https://docs.rs/seq-macro/0.3.5")] #![allow( clippy::cast_lossless, clippy::cast_possible_truncation, clippy::derive_partial_eq_without_eq, clippy::let_underscore_untyped, clippy::needless_doctest_main, clippy::single_match_else, clippy::wildcard_imports )] mod parse; use crate::parse::*; use proc_macro::{Delimiter, Group, Ident, Literal, Span, TokenStream, TokenTree}; use std::char; use std::iter::{self, FromIterator}; #[proc_macro] pub fn seq(input: TokenStream) -> TokenStream { match seq_impl(input) { Ok(expanded) => expanded, Err(error) => error.into_compile_error(), } } struct Range { begin: u64, end: u64, inclusive: bool, kind: Kind, suffix: String, width: usize, radix: Radix, } struct Value { int: u64, kind: Kind, suffix: String, width: usize, radix: Radix, span: Span, } struct Splice<'a> { int: u64, kind: Kind, suffix: &'a str, width: usize, radix: Radix, } #[derive(Copy, Clone, PartialEq)] enum Kind { Int, Byte, Char, } #[derive(Copy, Clone, PartialEq)] enum Radix { Binary, Octal, Decimal, LowerHex, UpperHex, } impl<'a> IntoIterator for &'a Range { type Item = Splice<'a>; type IntoIter = Box> + 'a>; fn into_iter(self) -> Self::IntoIter { let splice = move |int| Splice { int, kind: self.kind, suffix: &self.suffix, width: self.width, radix: self.radix, }; match self.kind { Kind::Int | Kind::Byte => { if self.inclusive { Box::new((self.begin..=self.end).map(splice)) } else { Box::new((self.begin..self.end).map(splice)) } } Kind::Char => { let begin = char::from_u32(self.begin as u32).unwrap(); let end = char::from_u32(self.end as u32).unwrap(); let int = |ch| u64::from(u32::from(ch)); if self.inclusive { Box::new((begin..=end).map(int).map(splice)) } else { Box::new((begin..end).map(int).map(splice)) } } } } } fn seq_impl(input: TokenStream) -> Result { let mut iter = input.into_iter(); let var = require_ident(&mut iter)?; require_keyword(&mut iter, "in")?; let begin = require_value(&mut iter)?; require_punct(&mut iter, '.')?; require_punct(&mut iter, '.')?; let inclusive = require_if_punct(&mut iter, '=')?; let end = require_value(&mut iter)?; let body = require_braces(&mut iter)?; require_end(&mut iter)?; let range = validate_range(begin, end, inclusive)?; let mut found_repetition = false; let expanded = expand_repetitions(&var, &range, body.clone(), &mut found_repetition); if found_repetition { Ok(expanded) } else { // If no `#(...)*`, repeat the entire body. Ok(repeat(&var, &range, &body)) } } fn repeat(var: &Ident, range: &Range, body: &TokenStream) -> TokenStream { let mut repeated = TokenStream::new(); for value in range { repeated.extend(substitute_value(var, &value, body.clone())); } repeated } fn substitute_value(var: &Ident, splice: &Splice, body: TokenStream) -> TokenStream { let mut tokens = Vec::from_iter(body); let mut i = 0; while i < tokens.len() { // Substitute our variable by itself, e.g. `N`. let replace = match &tokens[i] { TokenTree::Ident(ident) => ident.to_string() == var.to_string(), _ => false, }; if replace { let original_span = tokens[i].span(); let mut literal = splice.literal(); literal.set_span(original_span); tokens[i] = TokenTree::Literal(literal); i += 1; continue; } // Substitute our variable concatenated onto some prefix, `Prefix~N`. if i + 3 <= tokens.len() { let prefix = match &tokens[i..i + 3] { [first, TokenTree::Punct(tilde), TokenTree::Ident(ident)] if tilde.as_char() == '~' && ident.to_string() == var.to_string() => { match first { TokenTree::Ident(ident) => Some(ident.clone()), TokenTree::Group(group) => { let mut iter = group.stream().into_iter().fuse(); match (iter.next(), iter.next()) { (Some(TokenTree::Ident(ident)), None) => Some(ident), _ => None, } } _ => None, } } _ => None, }; if let Some(prefix) = prefix { let number = match splice.kind { Kind::Int => match splice.radix { Radix::Binary => format!("{0:01$b}", splice.int, splice.width), Radix::Octal => format!("{0:01$o}", splice.int, splice.width), Radix::Decimal => format!("{0:01$}", splice.int, splice.width), Radix::LowerHex => format!("{0:01$x}", splice.int, splice.width), Radix::UpperHex => format!("{0:01$X}", splice.int, splice.width), }, Kind::Byte | Kind::Char => { char::from_u32(splice.int as u32).unwrap().to_string() } }; let concat = format!("{}{}", prefix, number); let ident = Ident::new(&concat, prefix.span()); tokens.splice(i..i + 3, iter::once(TokenTree::Ident(ident))); i += 1; continue; } } // Recursively substitute content nested in a group. if let TokenTree::Group(group) = &mut tokens[i] { let original_span = group.span(); let content = substitute_value(var, splice, group.stream()); *group = Group::new(group.delimiter(), content); group.set_span(original_span); } i += 1; } TokenStream::from_iter(tokens) } fn enter_repetition(tokens: &[TokenTree]) -> Option { assert!(tokens.len() == 3); match &tokens[0] { TokenTree::Punct(punct) if punct.as_char() == '#' => {} _ => return None, } match &tokens[2] { TokenTree::Punct(punct) if punct.as_char() == '*' => {} _ => return None, } match &tokens[1] { TokenTree::Group(group) if group.delimiter() == Delimiter::Parenthesis => { Some(group.stream()) } _ => None, } } fn expand_repetitions( var: &Ident, range: &Range, body: TokenStream, found_repetition: &mut bool, ) -> TokenStream { let mut tokens = Vec::from_iter(body); // Look for `#(...)*`. let mut i = 0; while i < tokens.len() { if let TokenTree::Group(group) = &mut tokens[i] { let content = expand_repetitions(var, range, group.stream(), found_repetition); let original_span = group.span(); *group = Group::new(group.delimiter(), content); group.set_span(original_span); i += 1; continue; } if i + 3 > tokens.len() { i += 1; continue; } let template = match enter_repetition(&tokens[i..i + 3]) { Some(template) => template, None => { i += 1; continue; } }; *found_repetition = true; let mut repeated = Vec::new(); for value in range { repeated.extend(substitute_value(var, &value, template.clone())); } let repeated_len = repeated.len(); tokens.splice(i..i + 3, repeated); i += repeated_len; } TokenStream::from_iter(tokens) } impl Splice<'_> { fn literal(&self) -> Literal { match self.kind { Kind::Int | Kind::Byte => { let repr = match self.radix { Radix::Binary => format!("0b{0:02$b}{1}", self.int, self.suffix, self.width), Radix::Octal => format!("0o{0:02$o}{1}", self.int, self.suffix, self.width), Radix::Decimal => format!("{0:02$}{1}", self.int, self.suffix, self.width), Radix::LowerHex => format!("0x{0:02$x}{1}", self.int, self.suffix, self.width), Radix::UpperHex => format!("0x{0:02$X}{1}", self.int, self.suffix, self.width), }; let tokens = repr.parse::().unwrap(); let mut iter = tokens.into_iter(); let literal = match iter.next() { Some(TokenTree::Literal(literal)) => literal, _ => unreachable!(), }; assert!(iter.next().is_none()); literal } Kind::Char => { let ch = char::from_u32(self.int as u32).unwrap(); Literal::character(ch) } } } }