codama_syn_helpers/extensions/
parse_buffer.rsuse proc_macro2::{TokenStream, TokenTree};
use syn::{parse::ParseBuffer, Token};
pub trait ParseBufferExtension<'a> {
fn get_self(&self) -> &ParseBuffer<'a>;
fn parse_arg(&self) -> syn::Result<TokenStream> {
self.get_self().step(|cursor| {
let mut tts = Vec::new();
let mut rest = *cursor;
while let Some((tt, next)) = rest.token_tree() {
match &tt {
TokenTree::Punct(punct) if punct.as_char() == ',' => {
return Ok((tts.into_iter().collect(), rest));
}
_ => {
tts.push(tt);
rest = next
}
}
}
Ok((tts.into_iter().collect(), rest))
})
}
fn fork_arg(&self) -> syn::Result<ParseBuffer<'a>> {
let this = self.get_self();
let fork = this.fork();
this.parse_arg()?;
Ok(fork)
}
fn is_end_of_arg(&self) -> bool {
let this = self.get_self();
this.is_empty() || this.peek(Token![,])
}
fn is_empty_group(&self) -> bool {
match self.get_self().fork().parse::<proc_macro2::Group>() {
Ok(group) => group.stream().is_empty(),
Err(_) => false,
}
}
}
impl<'a> ParseBufferExtension<'a> for ParseBuffer<'a> {
fn get_self(&self) -> &ParseBuffer<'a> {
self
}
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! test_buffer {
($ty:ty, $callback:expr) => {{
struct TestBuffer($ty);
impl syn::parse::Parse for TestBuffer {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let result = ($callback)(input)?; input.parse::<proc_macro2::TokenStream>()?; Ok(Self(result))
}
}
move |input: &str| -> syn::Result<$ty> {
syn::parse_str::<TestBuffer>(input).map(|x| x.0)
}
}};
}
#[test]
fn parse_arg() {
let test = test_buffer!(
(String, String),
|input: syn::parse::ParseStream| -> syn::Result<(String, String)> {
let arg = input.parse_arg()?;
Ok((arg.to_string(), input.to_string()))
}
);
assert_eq!(
test("foo , bar , baz").unwrap(),
("foo".into(), ", bar , baz".into())
);
assert_eq!(
test(", bar , baz").unwrap(),
("".into(), ", bar , baz".into())
);
assert_eq!(
test("(foo , bar), baz").unwrap(),
("(foo , bar)".into(), ", baz".into())
);
assert_eq!(
test("foo bar baz").unwrap(),
("foo bar baz".into(), "".into())
);
assert_eq!(test("").unwrap(), ("".into(), "".into()));
}
#[test]
fn fork_arg() {
let test = test_buffer!(
(String, String),
|input: syn::parse::ParseStream| -> syn::Result<(String, String)> {
let arg = input.fork_arg()?;
Ok((arg.to_string(), input.to_string()))
}
);
assert_eq!(
test("foo , bar , baz").unwrap(),
("foo , bar , baz".into(), ", bar , baz".into())
);
assert_eq!(
test("foo bar baz").unwrap(),
("foo bar baz".into(), "".into())
);
}
#[test]
fn is_end_of_arg() {
let test = test_buffer!(
bool,
|input: syn::parse::ParseStream| -> syn::Result<bool> { Ok(input.is_end_of_arg()) }
);
assert!(test("").unwrap());
assert!(test(", bar").unwrap());
assert!(!test("foo").unwrap());
}
#[test]
fn is_empty_group() {
let test = test_buffer!(
bool,
|input: syn::parse::ParseStream| -> syn::Result<bool> { Ok(input.is_empty_group()) }
);
assert!(test("()").unwrap());
assert!(test("[]").unwrap());
assert!(test("{}").unwrap());
assert!(!test("").unwrap());
assert!(!test("foo").unwrap());
assert!(!test("(foo)").unwrap());
}
}