use super::*;
use punctuated::Punctuated;
use std::iter;
use proc_macro2::{Delimiter, Spacing, TokenNode, TokenStream, TokenTree};
#[cfg(feature = "extra-traits")]
use std::hash::{Hash, Hasher};
#[cfg(feature = "extra-traits")]
use tt::TokenStreamHelper;
ast_struct! {
pub struct Attribute #manual_extra_traits {
pub pound_token: Token![#],
pub style: AttrStyle,
pub bracket_token: token::Bracket,
pub path: Path,
pub tts: TokenStream,
pub is_sugared_doc: bool,
}
}
#[cfg(feature = "extra-traits")]
impl Eq for Attribute {}
#[cfg(feature = "extra-traits")]
impl PartialEq for Attribute {
fn eq(&self, other: &Self) -> bool {
self.style == other.style && self.pound_token == other.pound_token
&& self.bracket_token == other.bracket_token && self.path == other.path
&& TokenStreamHelper(&self.tts) == TokenStreamHelper(&other.tts)
&& self.is_sugared_doc == other.is_sugared_doc
}
}
#[cfg(feature = "extra-traits")]
impl Hash for Attribute {
fn hash<H>(&self, state: &mut H)
where
H: Hasher,
{
self.style.hash(state);
self.pound_token.hash(state);
self.bracket_token.hash(state);
self.path.hash(state);
TokenStreamHelper(&self.tts).hash(state);
self.is_sugared_doc.hash(state);
}
}
impl Attribute {
pub fn interpret_meta(&self) -> Option<Meta> {
let name = if self.path.segments.len() == 1 {
&self.path.segments.first().unwrap().value().ident
} else {
return None;
};
if self.tts.is_empty() {
return Some(Meta::Word(*name));
}
let tts = self.tts.clone().into_iter().collect::<Vec<_>>();
if tts.len() == 1 {
if let TokenNode::Group(Delimiter::Parenthesis, ref ts) = tts[0].kind {
let tokens = ts.clone().into_iter().collect::<Vec<_>>();
if let Some(nested_meta_items) = list_of_nested_meta_items_from_tokens(&tokens) {
return Some(Meta::List(MetaList {
paren_token: token::Paren(tts[0].span),
ident: *name,
nested: nested_meta_items,
}));
}
}
}
if tts.len() == 2 {
if let TokenNode::Op('=', Spacing::Alone) = tts[0].kind {
if let TokenNode::Literal(ref lit) = tts[1].kind {
if !lit.to_string().starts_with('/') {
return Some(Meta::NameValue(MetaNameValue {
ident: *name,
eq_token: Token,
lit: Lit::new(lit.clone(), tts[1].span),
}));
}
} else if let TokenNode::Term(ref term) = tts[1].kind {
match term.as_str() {
v @ "true" | v @ "false" => {
return Some(Meta::NameValue(MetaNameValue {
ident: *name,
eq_token: Token,
lit: Lit::Bool(LitBool { value: v == "true", span: tts[1].span }),
}));
},
_ => {}
}
}
}
}
None
}
}
fn nested_meta_item_from_tokens(tts: &[TokenTree]) -> Option<(NestedMeta, &[TokenTree])> {
assert!(!tts.is_empty());
match tts[0].kind {
TokenNode::Literal(ref lit) => {
if lit.to_string().starts_with('/') {
None
} else {
let lit = Lit::new(lit.clone(), tts[0].span);
Some((NestedMeta::Literal(lit), &tts[1..]))
}
}
TokenNode::Term(sym) => {
let ident = Ident::new(sym.as_str(), tts[0].span);
if tts.len() >= 3 {
if let TokenNode::Op('=', Spacing::Alone) = tts[1].kind {
if let TokenNode::Literal(ref lit) = tts[2].kind {
if !lit.to_string().starts_with('/') {
let pair = MetaNameValue {
ident: Ident::new(sym.as_str(), tts[0].span),
eq_token: Token,
lit: Lit::new(lit.clone(), tts[2].span),
};
return Some((Meta::NameValue(pair).into(), &tts[3..]));
}
} else if let TokenNode::Term(ref term) = tts[2].kind {
match term.as_str() {
v @ "true" | v @ "false" => {
let pair = MetaNameValue {
ident: Ident::new(sym.as_str(), tts[0].span),
eq_token: Token,
lit: Lit::Bool(LitBool { value: v == "true", span: tts[2].span }),
};
return Some((Meta::NameValue(pair).into(), &tts[3..]));
},
_ => {}
}
}
}
}
if tts.len() >= 2 {
if let TokenNode::Group(Delimiter::Parenthesis, ref inner_tts) = tts[1].kind {
let inner_tts = inner_tts.clone().into_iter().collect::<Vec<_>>();
return match list_of_nested_meta_items_from_tokens(&inner_tts) {
Some(nested_meta_items) => {
let list = MetaList {
ident: ident,
paren_token: token::Paren(tts[1].span),
nested: nested_meta_items,
};
Some((Meta::List(list).into(), &tts[2..]))
}
None => None,
};
}
}
Some((Meta::Word(ident).into(), &tts[1..]))
}
_ => None,
}
}
fn list_of_nested_meta_items_from_tokens(
mut tts: &[TokenTree],
) -> Option<Punctuated<NestedMeta, Token![,]>> {
let mut nested_meta_items = Punctuated::new();
let mut first = true;
while !tts.is_empty() {
let prev_comma = if first {
first = false;
None
} else if let TokenNode::Op(',', Spacing::Alone) = tts[0].kind {
let tok = Token;
tts = &tts[1..];
if tts.is_empty() {
break;
}
Some(tok)
} else {
return None;
};
let (nested, rest) = match nested_meta_item_from_tokens(tts) {
Some(pair) => pair,
None => return None,
};
if let Some(comma) = prev_comma {
nested_meta_items.push_punct(comma);
}
nested_meta_items.push_value(nested);
tts = rest;
}
Some(nested_meta_items)
}
ast_enum! {
#[cfg_attr(feature = "clone-impls", derive(Copy))]
pub enum AttrStyle {
Outer,
Inner(Token![!]),
}
}
ast_enum_of_structs! {
pub enum Meta {
pub Word(Ident),
pub List(MetaList {
pub ident: Ident,
pub paren_token: token::Paren,
pub nested: Punctuated<NestedMeta, Token![,]>,
}),
pub NameValue(MetaNameValue {
pub ident: Ident,
pub eq_token: Token![=],
pub lit: Lit,
}),
}
}
impl Meta {
pub fn name(&self) -> Ident {
match *self {
Meta::Word(ref meta) => *meta,
Meta::List(ref meta) => meta.ident,
Meta::NameValue(ref meta) => meta.ident,
}
}
}
ast_enum_of_structs! {
pub enum NestedMeta {
pub Meta(Meta),
pub Literal(Lit),
}
}
pub trait FilterAttrs<'a> {
type Ret: Iterator<Item = &'a Attribute>;
fn outer(self) -> Self::Ret;
fn inner(self) -> Self::Ret;
}
impl<'a, T> FilterAttrs<'a> for T
where
T: IntoIterator<Item = &'a Attribute>,
{
type Ret = iter::Filter<T::IntoIter, fn(&&Attribute) -> bool>;
fn outer(self) -> Self::Ret {
fn is_outer(attr: &&Attribute) -> bool {
match attr.style {
AttrStyle::Outer => true,
_ => false,
}
}
self.into_iter().filter(is_outer)
}
fn inner(self) -> Self::Ret {
fn is_inner(attr: &&Attribute) -> bool {
match attr.style {
AttrStyle::Inner(_) => true,
_ => false,
}
}
self.into_iter().filter(is_inner)
}
}
#[cfg(feature = "parsing")]
pub mod parsing {
use super::*;
use buffer::Cursor;
use parse_error;
use synom::PResult;
use proc_macro2::{Literal, Spacing, Span, TokenNode, TokenTree};
fn eq(span: Span) -> TokenTree {
TokenTree {
span: span,
kind: TokenNode::Op('=', Spacing::Alone),
}
}
impl Attribute {
named!(pub parse_inner -> Self, alt!(
do_parse!(
pound: punct!(#) >>
bang: punct!(!) >>
path_and_tts: brackets!(tuple!(
call!(Path::parse_mod_style),
syn!(TokenStream)
)) >>
({
let (bracket, (path, tts)) = path_and_tts;
Attribute {
style: AttrStyle::Inner(bang),
path: path,
tts: tts,
is_sugared_doc: false,
pound_token: pound,
bracket_token: bracket,
}
})
)
|
map!(
call!(lit_doc_comment, Comment::Inner),
|lit| {
let span = lit.span;
Attribute {
style: AttrStyle::Inner(<Token![!]>::new(span)),
path: Ident::new("doc", span).into(),
tts: vec![
eq(span),
lit,
].into_iter().collect(),
is_sugared_doc: true,
pound_token: <Token![#]>::new(span),
bracket_token: token::Bracket(span),
}
}
)
));
named!(pub parse_outer -> Self, alt!(
do_parse!(
pound: punct!(#) >>
path_and_tts: brackets!(tuple!(
call!(Path::parse_mod_style),
syn!(TokenStream)
)) >>
({
let (bracket, (path, tts)) = path_and_tts;
Attribute {
style: AttrStyle::Outer,
path: path,
tts: tts,
is_sugared_doc: false,
pound_token: pound,
bracket_token: bracket,
}
})
)
|
map!(
call!(lit_doc_comment, Comment::Outer),
|lit| {
let span = lit.span;
Attribute {
style: AttrStyle::Outer,
path: Ident::new("doc", span).into(),
tts: vec![
eq(span),
lit,
].into_iter().collect(),
is_sugared_doc: true,
pound_token: <Token![#]>::new(span),
bracket_token: token::Bracket(span),
}
}
)
));
}
enum Comment {
Inner,
Outer,
}
fn lit_doc_comment(input: Cursor, style: Comment) -> PResult<TokenTree> {
match input.literal() {
Some((span, lit, rest)) => {
let string = lit.to_string();
let ok = match style {
Comment::Inner => string.starts_with("//!") || string.starts_with("/*!"),
Comment::Outer => string.starts_with("///") || string.starts_with("/**"),
};
if ok {
Ok((
TokenTree {
span: span,
kind: TokenNode::Literal(Literal::string(&string)),
},
rest,
))
} else {
parse_error()
}
}
_ => parse_error(),
}
}
}
#[cfg(feature = "printing")]
mod printing {
use super::*;
use quote::{ToTokens, Tokens};
use proc_macro2::Literal;
impl ToTokens for Attribute {
fn to_tokens(&self, tokens: &mut Tokens) {
if self.is_sugared_doc {
if let Some(Meta::NameValue(ref pair)) = self.interpret_meta() {
if pair.ident == "doc" {
if let Lit::Str(ref comment) = pair.lit {
tokens.append(TokenTree {
span: comment.span,
kind: TokenNode::Literal(Literal::doccomment(&comment.value())),
});
return;
}
}
}
}
self.pound_token.to_tokens(tokens);
if let AttrStyle::Inner(ref b) = self.style {
b.to_tokens(tokens);
}
self.bracket_token.surround(tokens, |tokens| {
self.path.to_tokens(tokens);
self.tts.to_tokens(tokens);
});
}
}
impl ToTokens for MetaList {
fn to_tokens(&self, tokens: &mut Tokens) {
self.ident.to_tokens(tokens);
self.paren_token.surround(tokens, |tokens| {
self.nested.to_tokens(tokens);
})
}
}
impl ToTokens for MetaNameValue {
fn to_tokens(&self, tokens: &mut Tokens) {
self.ident.to_tokens(tokens);
self.eq_token.to_tokens(tokens);
self.lit.to_tokens(tokens);
}
}
}