#[macro_use]
extern crate lazy_static;
use proc_macro::TokenStream;
use proc_macro2::Span;
use regex::Regex;
use syn::{
braced, bracketed,
parse::{Parse, ParseStream},
parse_macro_input, Ident, LitInt, LitStr, Result, Token,
};
struct RepeatDefinition {
replacement_id: Ident,
range: RepeatRange,
repeat_block: proc_macro2::TokenStream,
separator: LitStr,
}
struct RepeatRange {
low: LitInt,
high: LitInt,
step: Option<LitInt>,
}
impl Parse for RepeatRange {
fn parse(input: ParseStream) -> Result<Self> {
let range;
let _ = bracketed!(range in input);
let mut range: Vec<_> =
syn::punctuated::Punctuated::<syn::LitInt, Token![;]>::parse_terminated(&range)?
.into_iter()
.collect();
range.reverse();
Ok(RepeatRange {
low: range.pop().unwrap(),
high: range.pop().unwrap(),
step: range.pop(),
})
}
}
impl Parse for RepeatDefinition {
fn parse(input: ParseStream) -> Result<Self> {
let _: Token![for] = input.parse()?;
let replacement_id: Ident = input.parse()?;
let _: Token![in] = input.parse()?;
let range: RepeatRange = input.parse()?;
let repeat_block;
let _ = braced!(repeat_block in input);
let repeat_block = repeat_block.parse()?;
let mut separator = LitStr::new("\n", Span::call_site());
let expecting_user_separator = input.parse::<Token![,]>();
if let Ok(_) = expecting_user_separator {
separator = input.parse()?
}
Ok(RepeatDefinition {
replacement_id,
range,
repeat_block,
separator,
})
}
}
lazy_static! {
static ref PRELUDE_IDENT_RE: Regex =
Regex::new(r"^\s*?%\s*?%\s*?(?P<prelude_ident>\w*?)\s*?prelude\s").unwrap();
static ref POSTLUDE_IDENT_RE: Regex =
Regex::new(r"\spostlude\s*?(?P<postlude_ident>\w*?)\s*?%\s*?%\s*?$").unwrap();
static ref SIMPLE_BODY_RE: Regex = Regex::new(r"(?s)(?P<body>.*)").unwrap();
}
#[proc_macro]
pub fn repeated(repeated_tokens: TokenStream) -> TokenStream {
let tokens_string = format!("{}", repeated_tokens);
let mut prelude_ident = None;
let mut prelude_string = String::new();
let prelude_ident_captures = PRELUDE_IDENT_RE.captures(&tokens_string);
if let Some(prelude_ident_captures) = prelude_ident_captures {
prelude_ident = Some(prelude_ident_captures["prelude_ident"].to_string());
let prelude_re = Regex::new(&format!(
r"(?s)^\s*?%\s*?%\s*?{}\s*?prelude\s?(?P<prelude>.*)\s?prelude\s*?{}\s*?%\s*?%",
prelude_ident.as_ref().unwrap(),
prelude_ident.as_ref().unwrap()
))
.unwrap();
let prelude_body_captures = prelude_re.captures(&tokens_string);
let prelude_body = &prelude_body_captures.unwrap()["prelude"];
prelude_string = prelude_body.into();
}
let mut postlude_ident = None;
let mut postlude_string = String::new();
let postlude_ident_captures = POSTLUDE_IDENT_RE.captures(&tokens_string);
if let Some(postlude_ident_captures) = postlude_ident_captures {
postlude_ident = Some(postlude_ident_captures["postlude_ident"].to_string());
let postlude_re = Regex::new(&format!(
r"(?s)%\s*?%\s*?{}\s*?postlude\s?(?P<postlude>.*)\s?postlude\s*?{}\s*?%\s*?%\s*?$",
postlude_ident.as_ref().unwrap(),
postlude_ident.as_ref().unwrap()
))
.unwrap();
let postlude_body_captures = postlude_re.captures(&tokens_string);
let postlude_body = &postlude_body_captures.unwrap()["postlude"];
postlude_string = postlude_body.into();
}
let mut rep_body_string = None;
match (prelude_ident, postlude_ident) {
(None, None) => {}
(Some(prelude_ident), Some(postlude_ident)) => {
let body_re = Regex::new(&format!(r"(?xs)
^\s*?%\s*?%\s*?{}\s*?prelude\s?(?P<prelude>.*)\s?prelude\s*?{}\s*?%\s*?% # Prelude
(?P<body>.*?) # Repitition Body
%\s*?%\s*?{}\s*?postlude\s?(?P<postlude>.*)\s?postlude\s*?{}\s*?%\s*?%\s*?$ # Postlude",
prelude_ident, prelude_ident, postlude_ident, postlude_ident)).unwrap();
let body_captures = body_re.captures(&tokens_string).unwrap();
rep_body_string = Some((&body_captures["body"]).to_string());
}
(Some(prelude_ident), None) => {
let body_re = Regex::new(&format!(r"(?xs)
^\s*?%\s*?%\s*?{}\s*?prelude\s?(?P<prelude>.*)\s?prelude\s*?{}\s*?%\s*?% # Prelude
(?P<body>.*?) # Repitition Body",
prelude_ident, prelude_ident)).unwrap();
let body_captures = body_re.captures(&tokens_string).unwrap();
rep_body_string = Some((&body_captures["body"]).to_string());
}
(None, Some(postlude_ident)) => {
let body_re = Regex::new(&format!(r"(?xs)
(?P<body>.*?) # Repitition Body
%\s*?%\s*?{}\s*?postlude\s?(?P<postlude>.*)\s?postlude\s*?{}\s*?%\s*?%\s*?$ # Postlude",
postlude_ident, postlude_ident)).unwrap();
let body_captures = body_re.captures(&tokens_string).unwrap();
rep_body_string = Some((&body_captures["body"]).to_string());
}
}
let rep_body_tokenstream = if let Some(rep_body_string) = rep_body_string {
TokenStream::from(
rep_body_string
.parse::<proc_macro2::TokenStream>()
.unwrap_or_else(|e| {
let e: syn::Error = e.into();
e.to_compile_error()
}),
)
} else {
repeated_tokens
};
let repitition_definition: RepeatDefinition = parse_macro_input!(rep_body_tokenstream);
generate_repititions(prelude_string, repitition_definition, postlude_string)
.unwrap_or_else(|e| TokenStream::from(e.to_compile_error()))
}
fn generate_repititions(
prelude_string: String,
repitition_definition: RepeatDefinition,
postlude_string: String,
) -> Result<TokenStream> {
let start = repitition_definition.range.low.base10_parse::<u32>()?;
let end = repitition_definition.range.high.base10_parse::<u32>()?;
let step = repitition_definition
.range
.step
.map_or(Ok(1), |s| s.base10_parse::<usize>())?;
let replacement_id = repitition_definition.replacement_id.to_string();
let repeated_block: String = repitition_definition.repeat_block.to_string();
let mut repeated_blocks = if prelude_string.is_empty() { Vec::new() } else { vec![prelude_string] };
for i in (start..=end).step_by(step) {
let new_block = repeated_block
.replace(&format!(" % % {} % % ", replacement_id), &format!("{}", i))
.replace(&format!("% % {} % %", replacement_id), &format!("{}", i))
.replace(&format!("%%{}%%", replacement_id), &format!("{}", i));
repeated_blocks.push(new_block);
}
if !postlude_string.is_empty() {
repeated_blocks.push(postlude_string);
}
let separator = repitition_definition.separator.value();
let repeated_tokenstream: proc_macro2::TokenStream =
repeated_blocks.join(&separator).parse()?;
Ok(TokenStream::from(repeated_tokenstream))
}