#![cfg_attr(nightly, feature(proc_macro_span))]
use proc_macro::*;
use proc_macro_error::{abort, abort_call_site, proc_macro_error};
#[cfg(feature = "proc_macro_span")]
use std::iter::once;
#[proc_macro]
#[proc_macro_error]
pub fn macro_wrap(input: TokenStream) -> TokenStream {
let mut output = Vec::<TokenTree>::new();
let mut prior_to_previous_token = None::<TokenTree>;
let mut previous_token = None::<TokenTree>;
for token in input.into_iter() {
match token {
TokenTree::Group(ref group) => {
match (prior_to_previous_token, previous_token.clone()) {
(Some(TokenTree::Ident(ident)), Some(TokenTree::Punct(punct)))
if ident.to_string() == "macro_for" && punct.as_char() == '!' =>
{
output.pop();
output.pop();
output.extend(macro_for(group.stream()).into_iter());
}
_ => {
let mut output_group =
Group::new(group.delimiter(), macro_wrap(group.stream()));
output_group.set_span(group.span());
output.push(output_group.into());
}
}
}
_ => output.push(token.clone()),
}
prior_to_previous_token = previous_token;
previous_token = Some(token);
}
return TokenStream::from_iter(output);
}
#[proc_macro]
#[proc_macro_error]
pub fn macro_for(input: TokenStream) -> TokenStream {
let mut tokens = input.clone().into_iter();
let (substitution_identifier, substitution_values) = parse_attr(&mut tokens);
let mut output = Vec::<TokenTree>::new();
match tokens.next() {
Some(TokenTree::Group(group)) => {
if group.delimiter() != Delimiter::Brace {
abort!(
group.span_open(), "expected '{' after subsituted values";
help = "try placing this code inside a block: `{{ {} }}`", group.to_string()
)
}
for substitution_value in substitution_values {
output.extend(substitute(
group.stream(),
&substitution_identifier,
&substitution_value,
));
}
}
Some(token) => {
#[cfg(feature = "proc_macro_span")]
if let Some(source) = once(token.clone())
.chain(tokens)
.map(|t| t.span().source_text())
.fold(Some("".to_string()), |accum, source| {
accum.map(|a| source.as_ref().map(|s| a + s)).flatten()
})
{
abort!(
token.span(), "expected '{' after subsituted values";
help = "try placing this code inside a block: `{{ {} }}`", source
)
}
abort!(token.span(), "expected '{' after subsituted values");
}
None => {
abort_call_site!("expected '{' after subsituted values")
}
}
return TokenStream::from_iter(output);
}
fn parse_attr<T: Iterator<Item = TokenTree>>(
tokens: &mut T,
) -> (Ident, Vec<Vec<TokenTree>>) {
let substitution_identifier: Ident;
match tokens.next() {
Some(TokenTree::Punct(dollar)) if dollar.as_char() == '$' => {
let token = tokens.next();
match token {
Some(TokenTree::Ident(ident)) => {
#[cfg(feature = "proc_macro_span")]
if ident.span().start().line == dollar.span().end().line
&& ident.span().start().column == dollar.span().end().column + 1
{
substitution_identifier = ident;
} else {
abort_call_site!(
"extraneus space between '$' and substitution identifier"
);
}
#[cfg(not(feature = "proc_macro_span"))]
{
substitution_identifier = ident;
}
}
Some(token) => {
abort!(token.span(), "missing identifier after '$'")
}
None => {
abort!(dollar.span(), "missing identifier after '$'")
}
}
}
Some(TokenTree::Ident(id)) => {
if id.to_string() == "in" {
abort!(
id.span(),
"missing substitution identifier starting with '$' before 'in'"
)
} else {
abort!(id.span(), "substitution identifier should start with '$'")
}
}
Some(token) => {
abort!(
token.span(),
"expected substitution identifier starting with '$'"
)
}
None => {
abort_call_site!("expected substitution identifier starting with '$'")
}
}
match tokens.next() {
Some(TokenTree::Ident(id)) if id.to_string() == "in" => {}
Some(token) => {
abort!(token.span(), "expected 'in'")
}
None => {
abort_call_site!("expected 'in'")
}
}
let mut substitution_values = Vec::<Vec<TokenTree>>::new();
match tokens.next() {
Some(TokenTree::Group(group)) => {
if group.delimiter() != Delimiter::Bracket {
abort!(
group.span(),
"expected substituted values to be enclosed in square brackets and separated with commas";
help = "try using [] instead of {:?}", group.delimiter();
help = "like this: `[{}]`", group.stream().to_string()
)
}
let mut value = Vec::<TokenTree>::new();
for t in group.stream().into_iter() {
match t {
TokenTree::Punct(p) if p.as_char() == ',' => {
substitution_values.push(value);
value = Vec::<_>::new();
}
_ => value.push(t),
}
}
substitution_values.push(value);
}
Some(token) => {
abort!(
token.span(),
"expected substituted values inside square brackets and separated with commas";
help = "like this: `[one, two, three]`"
)
}
None => {
abort_call_site!(
"expected substituted values inside square brackets and separated with commas";
help = "like this: `[one, two, three]`"
)
}
}
return (substitution_identifier, substitution_values);
}
fn substitute(
tokens: TokenStream,
substitution_id: &Ident,
substitution_value: &Vec<TokenTree>,
) -> Vec<TokenTree> {
let mut output = Vec::<TokenTree>::new();
let mut previous_token = None::<TokenTree>;
for token in tokens.into_iter() {
match token {
TokenTree::Ident(ref id)
if id.to_string() == substitution_id.to_string() =>
{
match previous_token {
Some(TokenTree::Punct(p)) if p.as_char() == '$' => {
output.pop();
output.extend_from_slice(&substitution_value);
}
_ => output.push(token.clone()),
}
}
TokenTree::Group(ref group) => {
let mut output_group = Group::new(
group.delimiter(),
TokenStream::from_iter(substitute(
group.stream(),
substitution_id,
substitution_value,
)),
);
output_group.set_span(group.span());
output.push(output_group.into());
}
_ => output.push(token.clone()),
}
previous_token = Some(token);
}
return output;
}
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
let result = 2 + 2;
assert_eq!(result, 4);
}
}