Skip to content

Commit ab43277

Browse files
committed
Support peeking End
1 parent 58762c4 commit ab43277

File tree

2 files changed

+176
-4
lines changed

2 files changed

+176
-4
lines changed

src/lookahead.rs

Lines changed: 170 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ use crate::buffer::Cursor;
22
use crate::error::{self, Error};
33
use crate::sealed::lookahead::Sealed;
44
use crate::span::IntoSpans;
5-
use crate::token::Token;
6-
use proc_macro2::Span;
5+
use crate::token::{CustomToken, Token};
6+
use proc_macro2::{Delimiter, Span};
77
use std::cell::RefCell;
88

99
/// Support for checking the next token in a stream to decide how to parse.
@@ -110,7 +110,18 @@ impl<'a> Lookahead1<'a> {
110110
/// The error message will identify all of the expected token types that
111111
/// have been peeked against this lookahead instance.
112112
pub fn error(self) -> Error {
113-
let comparisons = self.comparisons.into_inner();
113+
let mut comparisons = self.comparisons.into_inner();
114+
comparisons.retain_mut(|display| {
115+
if *display == "`)`" {
116+
*display = match self.cursor.scope_delimiter() {
117+
Delimiter::Parenthesis => "`)`",
118+
Delimiter::Brace => "`}`",
119+
Delimiter::Bracket => "`]`",
120+
Delimiter::None => return false,
121+
}
122+
}
123+
true
124+
});
114125
match comparisons.len() {
115126
0 => {
116127
if self.cursor.eof() {
@@ -150,6 +161,160 @@ pub trait Peek: Sealed {
150161
type Token: Token;
151162
}
152163

164+
/// Pseudo-token used for peeking the end of a parse stream.
165+
///
166+
/// This type is only useful as an argument to one of the following functions:
167+
///
168+
/// - [`ParseStream::peek`][crate::parse::ParseBuffer::peek]
169+
/// - [`ParseStream::peek2`][crate::parse::ParseBuffer::peek2]
170+
/// - [`ParseStream::peek3`][crate::parse::ParseBuffer::peek3]
171+
/// - [`Lookahead1::peek`]
172+
///
173+
/// The peek will return `true` if there are no remaining tokens after that
174+
/// point in the parse stream.
175+
///
176+
/// # Example
177+
///
178+
/// Suppose we are parsing attributes containing core::fmt inspired formatting
179+
/// arguments:
180+
///
181+
/// - `#[fmt("simple example")]`
182+
/// - `#[fmt("interpolation e{}ample", self.x)]`
183+
/// - `#[fmt("interpolation e{x}ample")]`
184+
///
185+
/// and we want to recognize the cases where no interpolation occurs so that
186+
/// more efficient code can be generated.
187+
///
188+
/// The following implementation uses `input.peek(Token![,]) &&
189+
/// input.peek2(End)` to recognize the case of a trailing comma without
190+
/// consuming the comma from the parse stream, because if it isn't a trailing
191+
/// comma, that same comma needs to be parsed as part of `args`.
192+
///
193+
/// ```
194+
/// use proc_macro2::TokenStream;
195+
/// use quote::quote;
196+
/// use syn::parse::{End, Parse, ParseStream, Result};
197+
/// use syn::{parse_quote, Attribute, LitStr, Token};
198+
///
199+
/// struct FormatArgs {
200+
/// template: LitStr, // "...{}..."
201+
/// args: TokenStream, // , self.x
202+
/// }
203+
///
204+
/// impl Parse for FormatArgs {
205+
/// fn parse(input: ParseStream) -> Result<Self> {
206+
/// let template: LitStr = input.parse()?;
207+
///
208+
/// let args = if input.is_empty()
209+
/// || input.peek(Token![,]) && input.peek2(End)
210+
/// {
211+
/// input.parse::<Option<Token![,]>>()?;
212+
/// TokenStream::new()
213+
/// } else {
214+
/// input.parse()?
215+
/// };
216+
///
217+
/// Ok(FormatArgs {
218+
/// template,
219+
/// args,
220+
/// })
221+
/// }
222+
/// }
223+
///
224+
/// fn main() -> Result<()> {
225+
/// let attrs: Vec<Attribute> = parse_quote! {
226+
/// #[fmt("simple example")]
227+
/// #[fmt("interpolation e{}ample", self.x)]
228+
/// #[fmt("interpolation e{x}ample")]
229+
/// };
230+
///
231+
/// for attr in &attrs {
232+
/// let FormatArgs { template, args } = attr.parse_args()?;
233+
/// let requires_fmt_machinery =
234+
/// !args.is_empty() || template.value().contains(['{', '}']);
235+
/// let out = if requires_fmt_machinery {
236+
/// quote! {
237+
/// ::core::write!(__formatter, #template #args)
238+
/// }
239+
/// } else {
240+
/// quote! {
241+
/// __formatter.write_str(#template)
242+
/// }
243+
/// };
244+
/// println!("{}", out);
245+
/// }
246+
/// Ok(())
247+
/// }
248+
/// ```
249+
///
250+
/// Implementing this parsing logic without `peek2(End)` is more clumsy because
251+
/// we'd need a parse stream actually advanced past the comma before being able
252+
/// to find out whether there is anything after it. It would look something
253+
/// like:
254+
///
255+
/// ```
256+
/// # use proc_macro2::TokenStream;
257+
/// # use syn::parse::{ParseStream, Result};
258+
/// # use syn::Token;
259+
/// #
260+
/// # fn parse(input: ParseStream) -> Result<()> {
261+
/// use syn::parse::discouraged::Speculative as _;
262+
///
263+
/// let ahead = input.fork();
264+
/// ahead.parse::<Option<Token![,]>>()?;
265+
/// let args = if ahead.is_empty() {
266+
/// input.advance_to(&ahead);
267+
/// TokenStream::new()
268+
/// } else {
269+
/// input.parse()?
270+
/// };
271+
/// # Ok(())
272+
/// # }
273+
/// ```
274+
///
275+
/// or:
276+
///
277+
/// ```
278+
/// # use proc_macro2::TokenStream;
279+
/// # use syn::parse::{ParseStream, Result};
280+
/// # use syn::Token;
281+
/// #
282+
/// # fn parse(input: ParseStream) -> Result<()> {
283+
/// use quote::ToTokens as _;
284+
///
285+
/// let comma: Option<Token![,]> = input.parse()?;
286+
/// let mut args = TokenStream::new();
287+
/// if !input.is_empty() {
288+
/// comma.to_tokens(&mut args);
289+
/// input.parse::<TokenStream>()?.to_tokens(&mut args);
290+
/// }
291+
/// # Ok(())
292+
/// # }
293+
/// ```
294+
pub struct End;
295+
296+
impl Copy for End {}
297+
298+
impl Clone for End {
299+
fn clone(&self) -> Self {
300+
*self
301+
}
302+
}
303+
304+
impl Peek for End {
305+
type Token = Self;
306+
}
307+
308+
impl CustomToken for End {
309+
fn peek(cursor: Cursor) -> bool {
310+
cursor.eof()
311+
}
312+
313+
fn display() -> &'static str {
314+
"`)`" // Lookahead1 error message will fill in the expected close delimiter
315+
}
316+
}
317+
153318
impl<F: Copy + FnOnce(TokenMarker) -> T, T: Token> Peek for F {
154319
type Token = T;
155320
}
@@ -163,3 +328,5 @@ impl<S> IntoSpans<S> for TokenMarker {
163328
}
164329

165330
impl<F: Copy + FnOnce(TokenMarker) -> T, T: Token> Sealed for F {}
331+
332+
impl Sealed for End {}

src/parse.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ use std::rc::Rc;
202202
use std::str::FromStr;
203203

204204
pub use crate::error::{Error, Result};
205-
pub use crate::lookahead::{Lookahead1, Peek};
205+
pub use crate::lookahead::{End, Lookahead1, Peek};
206206

207207
/// Parsing interface implemented by all types that can be parsed in a default
208208
/// way from a token stream.
@@ -751,6 +751,11 @@ impl<'a> ParseBuffer<'a> {
751751
/// set of delimiters, as well as at the end of the tokens provided to the
752752
/// outermost parsing entry point.
753753
///
754+
/// This is equivalent to
755+
/// <code>.<a href="#method.peek">peek</a>(<a href="struct.End.html">syn::parse::End</a>)</code>.
756+
/// Use `.peek2(End)` or `.peek3(End)` to look for the end of a parse stream
757+
/// further ahead than the current position.
758+
///
754759
/// # Example
755760
///
756761
/// ```

0 commit comments

Comments
 (0)