blob: 5b80a14d2ae6f75b9851374d230f483601e5b7dc [file] [log] [blame]
//! Tokens representing Rust punctuation, keywords, and delimiters.
use std::ops::{Deref, DerefMut};
use proc_macro2::{Spacing, Span};
use super::error::Error;
use super::lookahead;
use super::parse::{Lookahead1, Parse, ParseStream, Result};
use super::span::{FromSpans, IntoSpans};
/// Marker trait for types that represent single tokens.
///
/// This trait is sealed and cannot be implemented for types outside of Syn.
pub trait Token: private::Sealed {
// Not public API.
#[doc(hidden)]
fn peek(lookahead: &Lookahead1) -> bool;
// Not public API.
#[doc(hidden)]
fn display() -> String;
}
mod private {
pub trait Sealed {}
}
macro_rules! impl_token {
($token:tt $name:ident #[$doc:meta]) => {
impl Token for $name {
fn peek(lookahead: &Lookahead1) -> bool {
lookahead::is_token(lookahead, $token)
}
fn display() -> String {
concat!("`", $token, "`").to_owned()
}
}
impl private::Sealed for $name {}
};
}
macro_rules! define_keywords {
($($token:tt pub struct $name:ident #[$doc:meta])*) => {
$(
#[$doc]
#[derive(Debug)]
pub struct $name {
pub span: Span,
}
#[doc(hidden)]
#[allow(non_snake_case)]
pub fn $name<T: IntoSpans<[Span; 1]>>(span: T) -> $name {
$name {
span: span.into_spans()[0],
}
}
impl_token!($token $name #[$doc]);
impl Parse for $name {
fn parse(input: ParseStream) -> Result<Self> {
parse_keyword(input, $token).map($name)
}
}
)*
};
}
macro_rules! define_punctuation {
($($token:tt pub struct $name:ident/$len:tt #[$doc:meta])*) => {
$(
#[$doc]
#[derive(Debug)]
pub struct $name {
pub spans: [Span; $len],
}
impl Deref for $name {
type Target = [Span; $len];
fn deref(&self) -> &Self::Target {
&self.spans
}
}
impl DerefMut for $name {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.spans
}
}
#[doc(hidden)]
#[allow(non_snake_case)]
pub fn $name<T: IntoSpans<[Span; $len]>>(spans: T) -> $name {
$name {
spans: spans.into_spans(),
}
}
impl_token!($token $name #[$doc]);
impl Parse for $name {
fn parse(input: ParseStream) -> Result<Self> {
parse_punctuation(input, $token).map($name::<[Span; $len]>)
}
}
)*
};
}
define_keywords! {
"struct" pub struct Struct /// `struct`
"enum" pub struct Enum /// `enum`
}
define_punctuation! {
":" pub struct Colon/1 /// `:`
"," pub struct Comma/1 /// `,`
".." pub struct Dot2/2 /// `..`
}
/// `{...}`
#[derive(Debug)]
pub struct Brace(pub Span);
fn parse_keyword(input: ParseStream, token: &str) -> Result<Span> {
input.step_cursor(|cursor| {
if let Some((ident, rest)) = cursor.ident() {
if ident == token {
return Ok((ident.span(), rest));
}
}
Err(cursor.error(format!("expected `{}`", token)))
})
}
fn parse_punctuation<S: FromSpans>(input: ParseStream, token: &str) -> Result<S> {
input.step_cursor(|cursor| {
let mut cursor = *cursor;
let mut spans = [cursor.span(); 3];
assert!(token.len() <= spans.len());
for (i, ch) in token.chars().enumerate() {
match cursor.punct() {
Some((punct, rest)) => {
spans[i] = punct.span();
if punct.as_char() != ch {
break;
} else if i == token.len() - 1 {
return Ok((S::from_spans(&spans), rest));
} else if punct.spacing() != Spacing::Joint {
break;
}
cursor = rest;
}
None => break,
}
}
Err(Error::new(spans[0], format!("expected `{}`", token)))
})
}