use pom::combinator::*; use pom::{Error, Parser}; use proc_macro::{Delimiter, Group, Ident, Literal, Punct, TokenStream, TokenTree}; pub fn unit<'a, I: 'a, A: Clone>(value: A) -> Combinator> { comb(move |_, start| Ok((value.clone(), start))) } pub fn punct<'a>(punct: char) -> Combinator> { comb(move |input: &[TokenTree], start| match input.get(start) { Some(TokenTree::Punct(p)) if p.as_char() == punct => Ok((p.clone(), start + 1)), _ => Err(Error::Mismatch { message: format!("expected {:?}", punct), position: start, }), }) } pub fn ident<'a>() -> Combinator> { comb(|input: &[TokenTree], start| match input.get(start) { Some(TokenTree::Ident(i)) => Ok((i.clone(), start + 1)), _ => Err(Error::Mismatch { message: "expected identifier".to_string(), position: start, }), }) } pub fn ident_match<'a>(name: String) -> Combinator> { comb(move |input: &[TokenTree], start| match input.get(start) { Some(TokenTree::Ident(i)) => { if i.to_string() == name { Ok(((), start + 1)) } else { Err(Error::Mismatch { message: format!("expected '', found ''", name, i.to_string()), position: start, }) } } _ => Err(Error::Mismatch { message: "expected identifier".to_string(), position: start, }), }) } pub fn literal<'a>() -> Combinator> { comb(|input: &[TokenTree], start| match input.get(start) { Some(TokenTree::Literal(l)) => Ok((l.clone(), start + 1)), _ => Err(Error::Mismatch { message: "expected literal".to_string(), position: start, }), }) } pub fn group<'a>() -> Combinator> { comb(|input: &[TokenTree], start| match input.get(start) { Some(TokenTree::Group(g)) => Ok((g.clone(), start + 1)), _ => Err(Error::Mismatch { message: "expected group".to_string(), position: start, }), }) } fn to_stream<'a, I: IntoIterator>(tokens: I) -> TokenStream { let mut stream = TokenStream::new(); stream.extend(tokens.into_iter().cloned()); stream } pub fn type_spec<'a>() -> Combinator> { let valid = ident().map(TokenTree::Ident) | punct(':').map(TokenTree::Punct) | punct('<').map(TokenTree::Punct) | punct('>').map(TokenTree::Punct) | punct('&').map(TokenTree::Punct) | punct('\'').map(TokenTree::Punct); valid.repeat(1..).collect().map(to_stream) } pub fn dotted_ident<'a>() -> Combinator> { (ident() + ((punct('.') + ident()).discard() | (punct(':').repeat(2) + ident()).discard()) .repeat(0..)) .collect() .map(|tokens| { if tokens.len() == 1 { tokens[0].clone() } else { Group::new(Delimiter::Brace, to_stream(tokens)).into() } }) } /// Read a sequence of idents and dashes, and merge them into a single ident /// with the dashes replaced by underscores. pub fn html_ident<'a>() -> Combinator> { let start = ident(); let next = punct('-') * ident(); (start * next.repeat(0..)).collect().map(|stream| { let (span, name) = stream .into_iter() .fold((None, String::new()), |(span, name), token| { ( match span { None => Some(token.span()), Some(span) => span.join(token.span()), }, match token { TokenTree::Ident(ident) => name + &ident.to_string(), TokenTree::Punct(_) => name + "_", _ => unreachable!(), }, ) }); Ident::new(&name, span.unwrap()) }) } fn error_location(input: &[TokenTree], position: usize) -> String { format!("{:?}", input[position].span()) } pub fn parse_error(input: &[TokenTree], error: &pom::Error) -> String { match error { pom::Error::Incomplete => "Incomplete token stream".to_string(), pom::Error::Mismatch { message, position } => { format!("{}: {}", error_location(input, *position), message) } pom::Error::Conversion { message, position } => { format!("{}: {}", error_location(input, *position), message) } pom::Error::Expect { message, position, inner, } => format!( "{}: {}\n{}", error_location(input, *position), message, parse_error(input, &inner) ), pom::Error::Custom { message, position, inner, } => { let mut out = format!("{}: {}", error_location(input, *position), message); if let Some(error) = inner { out += &format!("\n{}", parse_error(input, error)); } out } } }