use crate::buffer::Cursor; use crate::error::{self, Error}; use crate::sealed::lookahead::Sealed; use crate::span::IntoSpans; use crate::token::{CustomToken, Token}; use proc_macro2::{Delimiter, Span}; use std::cell::RefCell; /// Support for checking the next token in a stream to decide how to parse. /// /// An important advantage over [`ParseStream::peek`] is that here we /// automatically construct an appropriate error message based on the token /// alternatives that get peeked. If you are producing your own error message, /// go ahead and use `ParseStream::peek` instead. /// /// Use [`ParseStream::lookahead1`] to construct this object. /// /// [`ParseStream::peek`]: crate::parse::ParseBuffer::peek /// [`ParseStream::lookahead1`]: crate::parse::ParseBuffer::lookahead1 /// /// Consuming tokens from the source stream after constructing a lookahead /// object does not also advance the lookahead object. /// /// # Example /// /// ``` /// use syn::{ConstParam, Ident, Lifetime, LifetimeParam, Result, Token, TypeParam}; /// use syn::parse::{Parse, ParseStream}; /// /// // A generic parameter, a single one of the comma-separated elements inside /// // angle brackets in: /// // /// // fn f() { ... } /// // /// // On invalid input, lookahead gives us a reasonable error message. /// // /// // error: expected one of: identifier, lifetime, `const` /// // | /// // 5 | fn f() {} /// // | ^ /// enum GenericParam { /// Type(TypeParam), /// Lifetime(LifetimeParam), /// Const(ConstParam), /// } /// /// impl Parse for GenericParam { /// fn parse(input: ParseStream) -> Result { /// let lookahead = input.lookahead1(); /// if lookahead.peek(Ident) { /// input.parse().map(GenericParam::Type) /// } else if lookahead.peek(Lifetime) { /// input.parse().map(GenericParam::Lifetime) /// } else if lookahead.peek(Token![const]) { /// input.parse().map(GenericParam::Const) /// } else { /// Err(lookahead.error()) /// } /// } /// } /// ``` pub struct Lookahead1<'a> { scope: Span, cursor: Cursor<'a>, comparisons: RefCell>, } pub(crate) fn new(scope: Span, cursor: Cursor) -> Lookahead1 { Lookahead1 { scope, cursor, comparisons: RefCell::new(Vec::new()), } } fn peek_impl( lookahead: &Lookahead1, peek: fn(Cursor) -> bool, display: fn() -> &'static str, ) -> bool { if peek(lookahead.cursor) { return true; } lookahead.comparisons.borrow_mut().push(display()); false } impl<'a> Lookahead1<'a> { /// Looks at the next token in the parse stream to determine whether it /// matches the requested type of token. /// /// # Syntax /// /// Note that this method does not use turbofish syntax. Pass the peek type /// inside of parentheses. /// /// - `input.peek(Token![struct])` /// - `input.peek(Token![==])` /// - `input.peek(Ident)` *(does not accept keywords)* /// - `input.peek(Ident::peek_any)` /// - `input.peek(Lifetime)` /// - `input.peek(token::Brace)` pub fn peek(&self, token: T) -> bool { let _ = token; peek_impl(self, T::Token::peek, T::Token::display) } /// Triggers an error at the current position of the parse stream. /// /// The error message will identify all of the expected token types that /// have been peeked against this lookahead instance. pub fn error(self) -> Error { let mut comparisons = self.comparisons.into_inner(); comparisons.retain_mut(|display| { if *display == "`)`" { *display = match self.cursor.scope_delimiter() { Delimiter::Parenthesis => "`)`", Delimiter::Brace => "`}`", Delimiter::Bracket => "`]`", Delimiter::None => return false, } } true }); match comparisons.len() { 0 => { if self.cursor.eof() { Error::new(self.scope, "unexpected end of input") } else { Error::new(self.cursor.span(), "unexpected token") } } 1 => { let message = format!("expected {}", comparisons[0]); error::new_at(self.scope, self.cursor, message) } 2 => { let message = format!("expected {} or {}", comparisons[0], comparisons[1]); error::new_at(self.scope, self.cursor, message) } _ => { let join = comparisons.join(", "); let message = format!("expected one of: {}", join); error::new_at(self.scope, self.cursor, message) } } } } /// Types that can be parsed by looking at just one token. /// /// Use [`ParseStream::peek`] to peek one of these types in a parse stream /// without consuming it from the stream. /// /// This trait is sealed and cannot be implemented for types outside of Syn. /// /// [`ParseStream::peek`]: crate::parse::ParseBuffer::peek pub trait Peek: Sealed { // Not public API. #[doc(hidden)] type Token: Token; } /// Pseudo-token used for peeking the end of a parse stream. /// /// This type is only useful as an argument to one of the following functions: /// /// - [`ParseStream::peek`][crate::parse::ParseBuffer::peek] /// - [`ParseStream::peek2`][crate::parse::ParseBuffer::peek2] /// - [`ParseStream::peek3`][crate::parse::ParseBuffer::peek3] /// - [`Lookahead1::peek`] /// /// The peek will return `true` if there are no remaining tokens after that /// point in the parse stream. /// /// # Example /// /// Suppose we are parsing attributes containing core::fmt inspired formatting /// arguments: /// /// - `#[fmt("simple example")]` /// - `#[fmt("interpolation e{}ample", self.x)]` /// - `#[fmt("interpolation e{x}ample")]` /// /// and we want to recognize the cases where no interpolation occurs so that /// more efficient code can be generated. /// /// The following implementation uses `input.peek(Token![,]) && /// input.peek2(End)` to recognize the case of a trailing comma without /// consuming the comma from the parse stream, because if it isn't a trailing /// comma, that same comma needs to be parsed as part of `args`. /// /// ``` /// use proc_macro2::TokenStream; /// use quote::quote; /// use syn::parse::{End, Parse, ParseStream, Result}; /// use syn::{parse_quote, Attribute, LitStr, Token}; /// /// struct FormatArgs { /// template: LitStr, // "...{}..." /// args: TokenStream, // , self.x /// } /// /// impl Parse for FormatArgs { /// fn parse(input: ParseStream) -> Result { /// let template: LitStr = input.parse()?; /// /// let args = if input.is_empty() /// || input.peek(Token![,]) && input.peek2(End) /// { /// input.parse::>()?; /// TokenStream::new() /// } else { /// input.parse()? /// }; /// /// Ok(FormatArgs { /// template, /// args, /// }) /// } /// } /// /// fn main() -> Result<()> { /// let attrs: Vec = parse_quote! { /// #[fmt("simple example")] /// #[fmt("interpolation e{}ample", self.x)] /// #[fmt("interpolation e{x}ample")] /// }; /// /// for attr in &attrs { /// let FormatArgs { template, args } = attr.parse_args()?; /// let requires_fmt_machinery = /// !args.is_empty() || template.value().contains(['{', '}']); /// let out = if requires_fmt_machinery { /// quote! { /// ::core::write!(__formatter, #template #args) /// } /// } else { /// quote! { /// __formatter.write_str(#template) /// } /// }; /// println!("{}", out); /// } /// Ok(()) /// } /// ``` /// /// Implementing this parsing logic without `peek2(End)` is more clumsy because /// we'd need a parse stream actually advanced past the comma before being able /// to find out whether there is anything after it. It would look something /// like: /// /// ``` /// # use proc_macro2::TokenStream; /// # use syn::parse::{ParseStream, Result}; /// # use syn::Token; /// # /// # fn parse(input: ParseStream) -> Result<()> { /// use syn::parse::discouraged::Speculative as _; /// /// let ahead = input.fork(); /// ahead.parse::>()?; /// let args = if ahead.is_empty() { /// input.advance_to(&ahead); /// TokenStream::new() /// } else { /// input.parse()? /// }; /// # Ok(()) /// # } /// ``` /// /// or: /// /// ``` /// # use proc_macro2::TokenStream; /// # use syn::parse::{ParseStream, Result}; /// # use syn::Token; /// # /// # fn parse(input: ParseStream) -> Result<()> { /// use quote::ToTokens as _; /// /// let comma: Option = input.parse()?; /// let mut args = TokenStream::new(); /// if !input.is_empty() { /// comma.to_tokens(&mut args); /// input.parse::()?.to_tokens(&mut args); /// } /// # Ok(()) /// # } /// ``` pub struct End; impl Copy for End {} impl Clone for End { fn clone(&self) -> Self { *self } } impl Peek for End { type Token = Self; } impl CustomToken for End { fn peek(cursor: Cursor) -> bool { cursor.eof() } fn display() -> &'static str { "`)`" // Lookahead1 error message will fill in the expected close delimiter } } impl T, T: Token> Peek for F { type Token = T; } pub enum TokenMarker {} impl IntoSpans for TokenMarker { fn into_spans(self) -> S { match self {} } } impl T, T: Token> Sealed for F {} impl Sealed for End {}