use crate::ast;
use crate::shared::Description;
use crate::{Parse, ParseError, Parser, Peek, Peeker, Spanned, ToTokens, TokenStream};
#[derive(Debug, Clone, PartialEq, Eq, ToTokens, Spanned)]
pub struct Attribute {
pub hash: T![#],
pub style: AttrStyle,
pub open: T!['['],
pub path: ast::Path,
#[rune(optional)]
pub input: TokenStream,
pub close: T![']'],
}
impl Parse for Attribute {
fn parse(p: &mut Parser<'_>) -> Result<Self, ParseError> {
let hash = p.parse()?;
let style = p.parse()?;
let open = p.parse()?;
let path = p.parse()?;
let close;
let mut level = 1;
let mut input = TokenStream::new();
loop {
let token = p.next()?;
match token.kind {
K!['['] => level += 1,
K![']'] => {
level -= 1;
}
_ => (),
}
if level == 0 {
close = ast::CloseBracket { token };
break;
}
input.push(token);
}
Ok(Attribute {
hash,
style,
open,
path,
input,
close,
})
}
}
impl Peek for Attribute {
fn peek(p: &mut Peeker<'_>) -> bool {
match (p.nth(0), p.nth(1)) {
(K![#], K![!]) => true,
(K![#], K!['[']) => true,
_ => false,
}
}
}
impl Description for &Attribute {
fn description(self) -> &'static str {
match &self.style {
AttrStyle::Inner => "inner attribute",
AttrStyle::Outer(_) => "outer attribute",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, ToTokens)]
pub enum AttrStyle {
Inner,
Outer(T![!]),
}
impl Parse for AttrStyle {
fn parse(p: &mut Parser) -> Result<Self, ParseError> {
Ok(if p.peek::<T![!]>()? {
Self::Outer(p.parse()?)
} else {
Self::Inner
})
}
}
pub(crate) struct InnerAttribute(pub(crate) Attribute);
impl Parse for InnerAttribute {
fn parse(p: &mut Parser) -> Result<Self, ParseError> {
let attribute: Attribute = p.parse()?;
match attribute.style {
AttrStyle::Inner => Ok(Self(attribute)),
_ => Err(ParseError::expected(
&attribute,
"inner attribute like `#![allow(unused)]`",
)),
}
}
}
pub struct OuterAttribute;
impl Peek for OuterAttribute {
fn peek(p: &mut Peeker<'_>) -> bool {
match (p.nth(0), p.nth(1)) {
(K![#], K![!]) => true,
_ => false,
}
}
}
#[test]
fn test_parse_attribute() {
const TEST_STRINGS: &[&'static str] = &[
"#[foo]",
"#[a::b::c]",
"#[foo = \"hello world\"]",
"#[foo = 1]",
"#[foo = 1.3]",
"#[foo = true]",
"#[foo = b\"bytes\"]",
"#[foo = (1, 2, \"string\")]",
"#[foo = #{\"a\": 1} ]",
r#"#[foo = Fred {"a": 1} ]"#,
r#"#[foo = a::Fred {"a": #{ "b": 2 } } ]"#,
"#[bar()]",
"#[bar(baz)]",
"#[derive(Debug, PartialEq, PartialOrd)]",
"#[tracing::instrument(skip(non_debug))]",
"#[zanzibar(a = \"z\", both = false, sasquatch::herring)]",
r#"#[doc = "multiline \
docs are neat"
]"#,
];
for s in TEST_STRINGS.iter() {
crate::parse_all::<ast::Attribute>(s).expect(s);
let withbang = s.replacen("#[", "#![", 1);
crate::parse_all::<ast::Attribute>(&withbang).expect(&withbang);
}
}