Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support parsing attributes with arbitrary tokens. #111

Merged
merged 12 commits into from
Apr 21, 2017
275 changes: 192 additions & 83 deletions src/attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,111 @@ use std::iter;
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct Attribute {
pub style: AttrStyle,
pub value: MetaItem,

/// The path of the attribute.
///
/// E.g. `derive` in `#[derive(Copy)]`
/// E.g. `crate::precondition` in `#[crate::precondition x < 5]`
pub path: Path,

/// Any tokens after the path.
///
/// E.g. `( Copy )` in `#[derive(Copy)]`
/// E.g. `x < 5` in `#[crate::precondition x < 5]`
pub tts: Vec<TokenTree>,

pub is_sugared_doc: bool,
}

impl Attribute {
pub fn name(&self) -> &str {
self.value.name()
/// Parses the tokens after the path as a [`MetaItem`](enum.MetaItem.html) if possible.
pub fn meta_item(&self) -> Option<MetaItem> {
let name = if self.path.segments.len() == 1 {
&self.path.segments[0].ident
} else {
return None;
};

if self.tts.is_empty() {
return Some(MetaItem::Word(name.clone()));
}

if self.tts.len() == 1 {
if let TokenTree::Delimited(Delimited { delim: DelimToken::Paren, ref tts }) = self.tts[0] {
fn nested_meta_item_from_tokens(tts: &[TokenTree]) -> Option<(NestedMetaItem, &[TokenTree])> {
assert!(!tts.is_empty());

match tts[0] {
TokenTree::Token(Token::Literal(ref lit)) => {
Some((NestedMetaItem::Literal(lit.clone()), &tts[1..]))
}

TokenTree::Token(Token::Ident(ref ident)) => {
if tts.len() >= 3 {
if let TokenTree::Token(Token::Eq) = tts[1] {
if let TokenTree::Token(Token::Literal(ref lit)) = tts[2] {
return Some((NestedMetaItem::MetaItem(MetaItem::NameValue(ident.clone(), lit.clone())), &tts[3..]));
}
}
}

if tts.len() >= 2 {
if let TokenTree::Delimited(Delimited { delim: DelimToken::Paren, tts: ref inner_tts }) = tts[1] {
return match list_of_nested_meta_items_from_tokens(vec![], inner_tts) {
Some(nested_meta_items) => {
Some((NestedMetaItem::MetaItem(MetaItem::List(ident.clone(), nested_meta_items)), &tts[2..]))
}

None => None
};
}
}

Some((NestedMetaItem::MetaItem(MetaItem::Word(ident.clone())), &tts[1..]))
}

_ => None
}
}

fn list_of_nested_meta_items_from_tokens(mut result: Vec<NestedMetaItem>, tts: &[TokenTree]) -> Option<Vec<NestedMetaItem>> {
if tts.is_empty() {
return Some(result);
}

match nested_meta_item_from_tokens(tts) {
Some((nested_meta_item, rest)) => {
result.push(nested_meta_item);
if rest.is_empty() {
list_of_nested_meta_items_from_tokens(result, rest)
}
else if let TokenTree::Token(Token::Comma) = rest[0] {
list_of_nested_meta_items_from_tokens(result, &rest[1..])
}
else {
None
}
}

None => None
}
}

if let Some(nested_meta_items) = list_of_nested_meta_items_from_tokens(vec![], tts) {
return Some(MetaItem::List(name.clone(), nested_meta_items));
}
}
}

if self.tts.len() == 2 {
if let TokenTree::Token(Token::Eq) = self.tts[0] {
if let TokenTree::Token(Token::Literal(ref lit)) = self.tts[1] {
return Some(MetaItem::NameValue(name.clone(), lit.clone()));
}
}
}

None
}
}

Expand Down Expand Up @@ -109,22 +207,31 @@ impl<'a, T> FilterAttrs<'a> for T
#[cfg(feature = "parsing")]
pub mod parsing {
use super::*;
use ident::parsing::ident;
use lit::parsing::lit;
use lit::{Lit, StrStyle};
use mac::{Token, TokenTree};
use mac::parsing::token_trees;
use synom::space::{block_comment, whitespace};
use ty::parsing::mod_style_path;

#[cfg(feature = "full")]
named!(pub inner_attr -> Attribute, alt!(
do_parse!(
punct!("#") >>
punct!("!") >>
punct!("[") >>
meta_item: meta_item >>
punct!("]") >>
(Attribute {
style: AttrStyle::Inner,
value: meta_item,
is_sugared_doc: false,
path_and_tts: delimited!(
punct!("["),
tuple!(mod_style_path, token_trees),
punct!("]")
) >>
({
let (path, tts) = path_and_tts;

Attribute {
style: AttrStyle::Inner,
path: path,
tts: tts,
is_sugared_doc: false,
}
})
)
|
Expand All @@ -133,10 +240,11 @@ pub mod parsing {
content: take_until!("\n") >>
(Attribute {
style: AttrStyle::Inner,
value: MetaItem::NameValue(
"doc".into(),
format!("//!{}", content).into(),
),
path: "doc".into(),
tts: vec![
TokenTree::Token(Token::Eq),
TokenTree::Token(Token::Literal(Lit::Str(format!("//!{}", content).into(), StrStyle::Cooked))),
],
is_sugared_doc: true,
})
)
Expand All @@ -147,10 +255,11 @@ pub mod parsing {
com: block_comment >>
(Attribute {
style: AttrStyle::Inner,
value: MetaItem::NameValue(
"doc".into(),
com.into(),
),
path: "doc".into(),
tts: vec![
TokenTree::Token(Token::Eq),
TokenTree::Token(Token::Literal(Lit::Str(com.into(), StrStyle::Cooked))),
],
is_sugared_doc: true,
})
)
Expand All @@ -159,13 +268,20 @@ pub mod parsing {
named!(pub outer_attr -> Attribute, alt!(
do_parse!(
punct!("#") >>
punct!("[") >>
meta_item: meta_item >>
punct!("]") >>
(Attribute {
style: AttrStyle::Outer,
value: meta_item,
is_sugared_doc: false,
path_and_tts: delimited!(
punct!("["),
tuple!(mod_style_path, token_trees),
punct!("]")
) >>
({
let (path, tts) = path_and_tts;

Attribute {
style: AttrStyle::Outer,
path: path,
tts: tts,
is_sugared_doc: false,
}
})
)
|
Expand All @@ -175,10 +291,11 @@ pub mod parsing {
content: take_until!("\n") >>
(Attribute {
style: AttrStyle::Outer,
value: MetaItem::NameValue(
"doc".into(),
format!("///{}", content).into(),
),
path: "doc".into(),
tts: vec![
TokenTree::Token(Token::Eq),
TokenTree::Token(Token::Literal(Lit::Str(format!("///{}", content).into(), StrStyle::Cooked))),
],
is_sugared_doc: true,
})
)
Expand All @@ -189,82 +306,74 @@ pub mod parsing {
com: block_comment >>
(Attribute {
style: AttrStyle::Outer,
value: MetaItem::NameValue(
"doc".into(),
com.into(),
),
path: "doc".into(),
tts: vec![
TokenTree::Token(Token::Eq),
TokenTree::Token(Token::Literal(Lit::Str(com.into(), StrStyle::Cooked))),
],
is_sugared_doc: true,
})
)
));

named!(meta_item -> MetaItem, alt!(
do_parse!(
id: ident >>
punct!("(") >>
inner: terminated_list!(punct!(","), nested_meta_item) >>
punct!(")") >>
(MetaItem::List(id, inner))
)
|
do_parse!(
name: ident >>
punct!("=") >>
value: lit >>
(MetaItem::NameValue(name, value))
)
|
map!(ident, MetaItem::Word)
));

named!(nested_meta_item -> NestedMetaItem, alt!(
meta_item => { NestedMetaItem::MetaItem }
|
lit => { NestedMetaItem::Literal }
));
}

#[cfg(feature = "printing")]
mod printing {
use super::*;
use lit::{Lit, StrStyle};
use mac::{Token, TokenTree};
use quote::{Tokens, ToTokens};
use ty::Path;

impl ToTokens for Attribute {
fn to_tokens(&self, tokens: &mut Tokens) {
if let Attribute { style,
value: MetaItem::NameValue(ref name,
Lit::Str(ref value, StrStyle::Cooked)),
is_sugared_doc: true } = *self {
if name == "doc" {
match style {
AttrStyle::Inner if value.starts_with("//!") => {
tokens.append(&format!("{}\n", value));
return;
}
AttrStyle::Inner if value.starts_with("/*!") => {
tokens.append(value);
return;
}
AttrStyle::Outer if value.starts_with("///") => {
tokens.append(&format!("{}\n", value));
return;
}
AttrStyle::Outer if value.starts_with("/**") => {
tokens.append(value);
return;
// If this was a sugared doc, emit it in its original form instead of `#[doc = "..."]`
match *self {
Attribute {
style,
path: Path { global: false, ref segments },
ref tts,
is_sugared_doc: true,
} if segments.len() == 1 &&
segments[0].ident == "doc" &&
segments[0].parameters.is_empty() &&
tts.len() == 2 =>
{
if let TokenTree::Token(Token::Eq) = self.tts[0] {
if let TokenTree::Token(Token::Literal(Lit::Str(ref value, StrStyle::Cooked))) = self.tts[1] {
match style {
AttrStyle::Inner if value.starts_with("//!") => {
tokens.append(&format!("{}\n", value));
return;
}
AttrStyle::Inner if value.starts_with("/*!") => {
tokens.append(value);
return;
}
AttrStyle::Outer if value.starts_with("///") => {
tokens.append(&format!("{}\n", value));
return;
}
AttrStyle::Outer if value.starts_with("/**") => {
tokens.append(value);
return;
}
_ => {}
}
}
_ => {}
}
}

_ => {}
}

tokens.append("#");
if let AttrStyle::Inner = self.style {
tokens.append("!");
}
tokens.append("[");
self.value.to_tokens(tokens);
self.path.to_tokens(tokens);
tokens.append_all(&self.tts);
tokens.append("]");
}
}
Expand Down
21 changes: 21 additions & 0 deletions src/ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,27 @@ pub mod parsing {
), Into::into)
));

named!(pub mod_style_path -> Path, do_parse!(
global: option!(punct!("::")) >>
segments: separated_nonempty_list!(punct!("::"), mod_style_path_segment) >>
(Path {
global: global.is_some(),
segments: segments,
})
));

named!(mod_style_path_segment -> PathSegment, alt!(
map!(ident, Into::into)
|
map!(alt!(
keyword!("super")
|
keyword!("self")
|
keyword!("Self")
), Into::into)
));

named!(type_binding -> TypeBinding, do_parse!(
id: ident >>
punct!("=") >>
Expand Down
3 changes: 2 additions & 1 deletion tests/test_generics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ fn test_split_for_impl() {
ty_params: vec![TyParam {
attrs: vec![Attribute {
style: AttrStyle::Outer,
value: MetaItem::Word("may_dangle".into()),
path: "may_dangle".into(),
tts: vec![],
is_sugared_doc: false,
}],
ident: Ident::new("T"),
Expand Down
Loading