#[allow(unused_imports)]
use std::iter::FromIterator;
#[allow(unused_imports)]
use proc_macro::TokenStream;
use proc_macro2::{Delimiter, Group, TokenStream as TokenStream2, TokenTree};
use proc_macro_error::emit_error;
use quote::quote;
use syn::Attribute;
use crate::{
params::MacroParameters,
utils::{set_error_and_return, unwrap_or_set_error_and_return},
visit_ext::{VisitMutExt, Visitor},
DEFAULT_CRATE_NAME,
};
pub struct ContentVisitor {
pub params: MacroParameters,
}
impl ContentVisitor {
pub fn new() -> Self {
Self {
params: MacroParameters::new(),
}
}
fn process_attribute_maybe(&mut self, node: &mut Attribute) -> syn::Result<()> {
let mut params = MacroParameters::from_tokens_in_parens(node.tokens.clone().into())?;
MacroParameters::apply_parent(&mut params, &self.params)?;
let tokens = params.to_tokens();
node.tokens = quote!((#tokens));
Ok(())
}
fn process_file(&mut self, node: &mut syn::File) -> syn::Result<()> {
node.attrs.retain(|attr| {
if let Some(prefix) = is_default_attr(attr) {
self.params = unwrap_or_set_error_and_return!(
MacroParameters::from_tokens_in_parens(attr.tokens.clone().into()),
false
);
self.params.prefix_set(prefix);
false
} else {
true
}
});
Ok(())
}
fn process_attribute(&mut self, node: &mut syn::Attribute) -> syn::Result<()> {
if let Some(name) = self.params.is_our_attr(node) {
match name.as_str() {
"maybe" => self.process_attribute_maybe(node)?,
_ => {}
}
}
Ok(())
}
fn extend_with_group(parent: &mut TokenStream2, child: TokenStream2, delim: Delimiter) {
let g = TokenTree::Group(Group::new(delim, child));
parent.extend(vec![g]);
}
fn search_maybe_in_tokenstream(&self, ts: TokenStream2, initial_state: u8) -> TokenStream2 {
let mut state: u8 = initial_state;
let mut result = TokenStream2::new();
if state == 7 {
let mut params = match MacroParameters::from_tokens(ts.clone().into()) {
Ok(p) => p,
Err(_) => return ts,
};
match MacroParameters::apply_parent(&mut params, &self.params) {
Ok(p) => p,
Err(_) => return ts,
};
return params.to_tokens().into();
}
let mut iter = ts.into_iter();
while let Some(tt) = iter.next() {
match state {
0 => {
match &tt {
TokenTree::Punct(p) if p.as_char() == '#' => {
result.extend(vec![tt]);
state = 1;
continue;
}
TokenTree::Group(g) => {
Self::extend_with_group(
&mut result,
self.search_maybe_in_tokenstream(g.stream(), 0),
g.delimiter(),
);
state = 0;
continue;
}
_ => {}
};
}
1 => {
if let TokenTree::Group(g) = &tt {
if g.delimiter() == Delimiter::Bracket {
Self::extend_with_group(
&mut result,
self.search_maybe_in_tokenstream(g.stream(), 2),
g.delimiter(),
);
state = 0;
continue;
}
}
}
2 => {
if let TokenTree::Ident(ident) = &tt {
if ident.to_string() == DEFAULT_CRATE_NAME {
result.extend(vec![tt]);
state = 3;
continue;
}
}
}
3 => {
if let TokenTree::Punct(p) = &tt {
if p.as_char() == ':' {
result.extend(vec![tt]);
state = 4;
continue;
}
};
}
4 => {
if let TokenTree::Punct(p) = &tt {
if p.as_char() == ':' {
result.extend(vec![tt]);
state = 5;
continue;
}
};
}
5 => {
if let TokenTree::Ident(ident) = &tt {
if ident.to_string() == "maybe" {
result.extend(vec![tt]);
state = 6;
continue;
}
}
}
6 => {
if let TokenTree::Group(g) = &tt {
if g.delimiter() == Delimiter::Parenthesis {
Self::extend_with_group(
&mut result,
self.search_maybe_in_tokenstream(g.stream(), 7),
g.delimiter(),
);
state = 0;
continue;
}
}
}
_ => unreachable!(),
};
result.extend(vec![tt]);
state = 0;
}
result
}
}
impl VisitMutExt for Visitor<ContentVisitor> {
fn process_attribute(&mut self, node: &mut syn::Attribute) -> syn::Result<()> {
self.inner.process_attribute(node)
}
fn process_file(&mut self, node: &mut syn::File) -> syn::Result<()> {
self.inner.process_file(node)
}
fn process_macro(&mut self, node: &mut syn::Macro) -> syn::Result<()> {
if node.path.is_ident("macro_rules") {
node.tokens = self
.inner
.search_maybe_in_tokenstream(node.tokens.clone(), 0);
}
Ok(())
}
}
fn is_default_attr(attr: &syn::Attribute) -> Option<String> {
if let syn::AttrStyle::Inner(_) = attr.style {
if attr.path.leading_colon.is_none() && attr.path.segments.len() == 2 {
let first_segment = &attr.path.segments[0];
let last_segment = &attr.path.segments[1];
if first_segment.arguments == syn::PathArguments::None
&& last_segment.arguments == syn::PathArguments::None
{
let first = first_segment.ident.to_string();
let last = last_segment.ident.to_string();
if last == "default" {
return Some(first);
}
}
}
};
None
}