summaryrefslogtreecommitdiff
path: root/rust/syn/lookahead.rs
diff options
context:
space:
mode:
Diffstat (limited to 'rust/syn/lookahead.rs')
-rw-r--r--rust/syn/lookahead.rs334
1 files changed, 334 insertions, 0 deletions
diff --git a/rust/syn/lookahead.rs b/rust/syn/lookahead.rs
new file mode 100644
index 000000000000..a7994cb6ae88
--- /dev/null
+++ b/rust/syn/lookahead.rs
@@ -0,0 +1,334 @@
+// SPDX-License-Identifier: Apache-2.0 OR MIT
+
+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<T: Clone, 'a, 'b: 'a, const N: usize>() { ... }
+/// //
+/// // On invalid input, lookahead gives us a reasonable error message.
+/// //
+/// // error: expected one of: identifier, lifetime, `const`
+/// // |
+/// // 5 | fn f<!Sized>() {}
+/// // | ^
+/// enum GenericParam {
+/// Type(TypeParam),
+/// Lifetime(LifetimeParam),
+/// Const(ConstParam),
+/// }
+///
+/// impl Parse for GenericParam {
+/// fn parse(input: ParseStream) -> Result<Self> {
+/// 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<Vec<&'static str>>,
+}
+
+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)`&emsp;*(does not accept keywords)*
+ /// - `input.peek(Ident::peek_any)`
+ /// - `input.peek(Lifetime)`
+ /// - `input.peek(token::Brace)`
+ pub fn peek<T: 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<Self> {
+/// let template: LitStr = input.parse()?;
+///
+/// let args = if input.is_empty()
+/// || input.peek(Token![,]) && input.peek2(End)
+/// {
+/// input.parse::<Option<Token![,]>>()?;
+/// TokenStream::new()
+/// } else {
+/// input.parse()?
+/// };
+///
+/// Ok(FormatArgs {
+/// template,
+/// args,
+/// })
+/// }
+/// }
+///
+/// fn main() -> Result<()> {
+/// let attrs: Vec<Attribute> = 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::<Option<Token![,]>>()?;
+/// 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<Token![,]> = input.parse()?;
+/// let mut args = TokenStream::new();
+/// if !input.is_empty() {
+/// comma.to_tokens(&mut args);
+/// input.parse::<TokenStream>()?.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<F: Copy + FnOnce(TokenMarker) -> T, T: Token> Peek for F {
+ type Token = T;
+}
+
+pub enum TokenMarker {}
+
+impl<S> IntoSpans<S> for TokenMarker {
+ fn into_spans(self) -> S {
+ match self {}
+ }
+}
+
+impl<F: Copy + FnOnce(TokenMarker) -> T, T: Token> Sealed for F {}
+
+impl Sealed for End {}