Skip to main content

duchess_reflect/
parse.rs

1use std::iter::Peekable;
2
3use proc_macro2::{Spacing, Span, TokenStream, TokenTree};
4
5pub struct Parser {
6    tokens: Peekable<Box<dyn Iterator<Item = TokenTree>>>,
7    last_span: Option<Span>,
8}
9
10impl From<TokenStream> for Parser {
11    fn from(value: TokenStream) -> Self {
12        let tokens: Box<dyn Iterator<Item = TokenTree>> = Box::new(value.into_iter());
13        Parser {
14            tokens: tokens.peekable(),
15            last_span: None,
16        }
17    }
18}
19
20impl Parser {
21    /// Top-level parse function that parses the input to the proc macro.
22    pub fn parse<T: Parse>(mut self) -> syn::Result<T> {
23        match T::parse(&mut self) {
24            Ok(Some(t)) => {
25                if let Some(s) = self.peek_span() {
26                    return Err(syn::Error::new(
27                        s,
28                        format!("extra input after the end of what was expected"),
29                    ));
30                }
31                Ok(t)
32            }
33
34            Err(e) => Err(e),
35
36            Ok(None) => {
37                let span = Span::call_site();
38                return Err(syn::Error::new(
39                    span,
40                    format!("expected a {}", T::description()),
41                ));
42            }
43        }
44    }
45
46    /// Returns the next token without consuming it, or None if no tokens remain.
47    pub fn peek_token(&mut self) -> Option<&TokenTree> {
48        self.tokens.peek()
49    }
50
51    /// Returns the span of the next token, or None if no tokens remain.
52    pub fn peek_span(&mut self) -> Option<Span> {
53        Some(self.peek_token()?.span())
54    }
55
56    /// Returns the span of the most recently consumed token.
57    pub fn last_span(&self) -> Option<Span> {
58        self.last_span.clone()
59    }
60
61    /// Consumes the next token, returning it; returns None if no tokens remain.
62    pub fn eat_token(&mut self) -> Option<TokenTree> {
63        let t = self.tokens.next()?;
64        self.last_span = Some(t.span());
65        Some(t)
66    }
67
68    /// Tests whether `test` returns true for the next token and -- if so -- consumes and returns it;
69    /// returns `None` if no tokens remain.
70    pub fn eat_token_if(&mut self, test: impl Fn(&TokenTree) -> bool) -> Option<TokenTree> {
71        if test(self.peek_token()?) {
72            self.eat_token()
73        } else {
74            None
75        }
76    }
77
78    pub fn eat_map<R>(&mut self, op: impl FnOnce(&TokenTree) -> Option<R>) -> Option<R> {
79        let t = self.peek_token()?;
80        let r = op(t)?;
81        self.eat_token();
82        Some(r)
83    }
84
85    pub fn eat_keyword(&mut self, kw: &str) -> Option<()> {
86        assert!(KEYWORDS.contains(&kw));
87        self.eat_map(|t| match t {
88            TokenTree::Ident(i) => {
89                let s = i.to_string();
90                if s == kw {
91                    Some(())
92                } else {
93                    None
94                }
95            }
96            _ => None,
97        })
98    }
99
100    pub fn eat_ident(&mut self) -> Option<String> {
101        self.eat_map(|t| match t {
102            TokenTree::Ident(i) => {
103                let s = i.to_string();
104                if KEYWORDS.iter().any(|k| k == &s) {
105                    None
106                } else {
107                    Some(i.to_string())
108                }
109            }
110            _ => None,
111        })
112    }
113
114    pub fn eat_punct(&mut self, ch: char) -> Option<Span> {
115        self.eat_map(|t| match t {
116            TokenTree::Punct(punct) if punct.as_char() == ch => Some(punct.span()),
117            _ => None,
118        })
119    }
120}
121
122/// Utility class for accumulating tokens into a string, keeping a combined span
123/// that contains (in theory) all the tokens accumulated. This class is a hack
124/// used to bridge our LALRPOP parser, which operates on strings, with the user's
125/// code. What we should really do is modify the LALRPOP parser to operate on tokens
126/// directly, but that's for later.
127pub struct TextAccum<'p> {
128    text: String,
129    span: Span,
130    parser: &'p mut Parser,
131}
132
133impl<'p> TextAccum<'p> {
134    pub fn new(parser: &'p mut Parser, t0: TokenTree) -> Self {
135        let mut s = Self {
136            text: String::new(),
137            span: t0.span(),
138            parser,
139        };
140        s.accum_token(&t0);
141        s
142    }
143
144    /// Accumulate next token into the internal buffer and return it.
145    pub fn accum(&mut self) -> Option<TokenTree> {
146        self.accum_if(|_| true)
147    }
148
149    /// If the next token passes `test`, accumulates it into the internal buffer, and returns it.
150    pub fn accum_if(&mut self, test: impl Fn(&TokenTree) -> bool) -> Option<TokenTree> {
151        let t1 = self.parser.eat_token_if(test)?;
152        self.accum_token(&t1);
153        Some(t1)
154    }
155
156    fn accum_token(&mut self, token: &TokenTree) {
157        self.text.push_str(&token.to_string());
158
159        // insert whitespace if this is a token that needs to be separated from following tokens
160        match token {
161            TokenTree::Group(_) => (),
162            TokenTree::Ident(_) => self.text.push(' '),
163            TokenTree::Punct(p) => match p.spacing() {
164                Spacing::Alone => self.text.push(' '),
165                Spacing::Joint => (),
166            },
167            TokenTree::Literal(_) => (),
168        }
169
170        self.span = self.span.join(token.span()).unwrap_or(self.span);
171    }
172
173    /// Return the string we accumulated.
174    pub fn into_accumulated_result(self) -> (String, Span) {
175        (self.text, self.span)
176    }
177}
178
179/// A trait for things that can be parsed from a token stream.
180pub trait Parse: Sized {
181    /// We assume an LL(1) grammar, so no need for backtracking.
182    ///
183    /// # Return value
184    ///
185    /// Err -- parse error after recognizing the start of a `Self`, may have consumed tokens
186    /// Ok(None) -- didn't recognize `Self` at this location, didn't consume any tokens
187    /// Ok(Some(e)) -- successful parse of `Self`
188    fn parse(p: &mut Parser) -> syn::Result<Option<Self>>;
189
190    /// parse any number of instances of self.
191    fn parse_many(p: &mut Parser) -> syn::Result<Vec<Self>> {
192        let mut result = vec![];
193
194        while let Some(e) = Self::parse(p)? {
195            result.push(e);
196        }
197
198        Ok(result)
199    }
200
201    /// Describes the thing we are parsing, for use in error messages.
202    /// e.g. "java path".
203    fn description() -> String;
204}
205
206/// Keywords not considered valid identifiers; subset of java keywords.
207pub const KEYWORDS: &[&str] = &["package", "class", "extends", "implements"];