syn_error_experiment/
token.rs

1//! Tokens representing Rust punctuation, keywords, and delimiters.
2
3use proc_macro2::Span;
4
5use parse::{Lookahead1, Parse, ParseStream, Result};
6
7/// Marker trait for types that represent single tokens.
8///
9/// This trait is sealed and cannot be implemented for types outside of Syn.
10pub trait Token: private::Sealed {
11    // Not public API.
12    #[doc(hidden)]
13    fn peek(lookahead: &Lookahead1) -> bool;
14
15    // Not public API.
16    #[doc(hidden)]
17    fn display() -> String;
18}
19
20mod private {
21    pub trait Sealed {}
22}
23
24/// A type-macro that expands to the name of the Rust type representation of a
25/// given token.
26#[macro_export]
27#[cfg_attr(rustfmt, rustfmt_skip)]
28macro_rules! Token {
29    (struct) => { $crate::token::Struct };
30    (enum)   => { $crate::token::Enum };
31    (:)      => { $crate::token::Colon };
32    (,)      => { $crate::token::Comma };
33}
34
35macro_rules! define_token {
36    ($token:tt $name:ident #[$doc:meta]) => {
37        #[$doc]
38        #[derive(Debug)]
39        pub struct $name(pub Span);
40
41        impl Token for $name {
42            fn peek(lookahead: &Lookahead1) -> bool {
43                ::lookahead::is_token(lookahead, $token)
44            }
45
46            fn display() -> String {
47                concat!("`", $token, "`").to_owned()
48            }
49        }
50
51        impl private::Sealed for $name {}
52    };
53}
54
55macro_rules! define_keywords {
56    ($($token:tt $name:ident #[$doc:meta])*) => {
57        $(
58            define_token!($token $name #[$doc]);
59
60            impl Parse for $name {
61                fn parse(input: ParseStream) -> Result<Self> {
62                    parse_keyword(input, $token).map($name)
63                }
64            }
65        )*
66    };
67}
68
69macro_rules! define_punctuation {
70    ($($token:tt $name:ident #[$doc:meta])*) => {
71        $(
72            define_token!($token $name #[$doc]);
73
74            impl Parse for $name {
75                fn parse(input: ParseStream) -> Result<Self> {
76                    parse_punctuation(input, $token).map($name)
77                }
78            }
79        )*
80    };
81}
82
83define_keywords! {
84    "struct" Struct /// `struct`
85    "enum"   Enum   /// `enum`
86}
87
88define_punctuation! {
89    ":" Colon /// `:`
90    "," Comma /// `,`
91}
92
93/// `{...}`
94#[derive(Debug)]
95pub struct Brace(pub Span);
96
97fn parse_keyword(input: ParseStream, token: &str) -> Result<Span> {
98    input.step_cursor(|cursor| {
99        if let Some((ident, rest)) = cursor.ident() {
100            if ident == token {
101                return Ok((ident.span(), rest));
102            }
103        }
104        Err(cursor.error(format!("expected `{}`", token)))
105    })
106}
107
108fn parse_punctuation(input: ParseStream, token: &str) -> Result<Span> {
109    input.step_cursor(|cursor| {
110        // TODO: support multi-character punctuation
111        if let Some((punct, rest)) = cursor.punct() {
112            if punct.as_char() == token.chars().next().unwrap() {
113                return Ok((punct.span(), rest));
114            }
115        }
116        Err(cursor.error(format!("expected `{}`", token)))
117    })
118}