use proc_macro2::{Delimiter, TokenStream, TokenTree};
use quote::TokenStreamExt;
use syn::{
braced, bracketed, parenthesized,
parse::{discouraged::Speculative, Parse, ParseStream, Parser},
parse_quote,
spanned::Spanned,
token, Ident, Path, Token, Type,
};
use crate::{
ast::{
capture::{Binder, Capture, CollectTrailer, EnumVariant, Matcher, MatcherKind, Quantity},
keyword::Keyword,
node::{Pattern, PatternKind},
},
scope_context::next_inline_index,
syntax::context::ParseContext,
};
fn parse_collect_matcher(
input: ParseStream,
start_span: proc_macro2::Span,
ctx: &mut ParseContext,
) -> syn::Result<Matcher> {
input.parse::<Token![..]>()?;
let trailer = if input.peek(token::Bracket) {
let stopper_tokens;
let _br = bracketed!(stopper_tokens in input);
if stopper_tokens.is_empty() {
return Err(stopper_tokens.error("expected stopper token inside []"));
}
let stopper = Keyword::parse(&stopper_tokens, ctx)?;
CollectTrailer::Keyword(stopper)
} else if input.peek(Token![|]) {
input.parse::<Token![|]>()?;
if !input.peek(Token![#]) || !input.peek2(token::Paren) {
return Err(input.error("expected capture after '|' in collected token capture"));
}
let capture = Capture::parse(input, ctx)?;
CollectTrailer::Capture(Box::new(capture))
} else {
CollectTrailer::End
};
Ok(Matcher {
kind: MatcherKind::TokenStreamCollect { trailer },
span: start_span,
})
}
impl Capture {
pub fn parse(input: syn::parse::ParseStream, ctx: &mut ParseContext) -> syn::Result<Self> {
let _hash_tag: Token![#] = input.parse()?;
let start_span = _hash_tag.span;
let content;
let _paren = parenthesized!(content in input);
let lookahead = content.lookahead1();
let fork = content.fork();
if fork.parse::<Type>().is_ok() && fork.is_empty() {
let ty: Type = content.parse()?;
let end_span = ty.span();
let matcher = Matcher {
kind: MatcherKind::SynType(ty),
span: start_span.join(end_span).unwrap_or(start_span),
};
let quantity = Quantity::One;
let binder = Binder::Anonymous;
Ok(Capture {
_hash_tag,
_paren,
matcher,
quantity,
binder,
edge: None,
span: start_span.join(end_span).unwrap_or(start_span),
})
} else if lookahead.peek(Ident) || lookahead.peek(Token![@]) {
let binder = if lookahead.peek(Ident) {
let ident: Ident = content.parse()?;
Binder::Named(ident)
} else {
let _at = content.parse::<Token![@]>()?;
let i = next_inline_index();
Binder::Inline(i)
};
let mut quantity = Quantity::One;
if content.peek(Token![?]) {
quantity = Quantity::Optional;
content.parse::<Token![?]>()?;
} else if content.peek(Token![*]) {
quantity = parse_many_quantity(&content, ctx)?;
}
if content.peek(Token![..]) {
if matches!(quantity, Quantity::Many(_)) {
return Err(content.error(
"collected token capture cannot be combined with iterative capture",
));
}
let matcher = parse_collect_matcher(&content, start_span, ctx)?;
let end_span = matcher.span;
if !content.is_empty() {
return Err(content.error("unexpected tokens after collected token capture"));
}
Ok(Capture {
_hash_tag,
_paren,
binder,
matcher,
quantity,
edge: None,
span: start_span.join(end_span).unwrap_or(start_span),
})
} else if content.peek(Token![:]) {
let _colon = content.parse::<Token![:]>()?;
let matcher = Matcher::parse(&content, ctx)?;
let end_span = matcher.span;
Ok(Capture {
_hash_tag,
_paren,
binder,
matcher,
quantity,
edge: None,
span: start_span.join(end_span).unwrap_or(start_span),
})
} else {
Err(content.error("expected ':' after capture name"))
}
} else {
let mut quantity = Quantity::One;
if content.peek(Token![?]) {
quantity = Quantity::Optional;
content.parse::<Token![?]>()?;
} else if content.peek(Token![*]) {
quantity = parse_many_quantity(&content, ctx)?;
}
if content.peek(Token![..]) {
return Err(
content.error("collected token capture requires a named or inline binder")
);
}
let _colon = content.parse::<Token![:]>()?;
let matcher = Matcher::parse(&content, ctx)?;
let end_span = matcher.span;
Ok(Capture {
_hash_tag,
_paren,
quantity,
matcher,
binder: Binder::Anonymous,
edge: None,
span: start_span.join(end_span).unwrap_or(start_span),
})
}
}
}
fn parse_many_quantity(input: ParseStream, ctx: &mut ParseContext) -> syn::Result<Quantity> {
input.parse::<Token![*]>()?;
if input.peek(token::Bracket) {
let separator_tokens;
let _br = bracketed!(separator_tokens in input);
if separator_tokens.is_empty() {
Ok(Quantity::Many(None))
} else {
let separator = Keyword::parse(&separator_tokens, ctx)?;
Ok(Quantity::Many(Some(separator)))
}
} else {
Ok(Quantity::Many(None))
}
}
impl Matcher {
pub fn parse(input: syn::parse::ParseStream, ctx: &mut ParseContext) -> syn::Result<Self> {
let cap = if input.peek(Token![#]) {
if input.peek2(token::Brace) {
let _hash: Token![#] = input.parse()?;
let start_span = _hash.span;
let content;
let _brace = braced!(content in input);
let inner = Pattern::parse_raw(&content, ctx)?;
let end_span = inner.span;
let children = if let PatternKind::Group { children, .. } = inner.kind {
children
} else {
vec![inner]
};
Matcher {
kind: MatcherKind::Nested(children),
span: start_span.join(end_span).unwrap_or(start_span),
}
} else if !input.peek2(token::Paren) {
let _hash_tag = input.parse::<Token![#]>()?;
let start_span = _hash_tag.span;
let pattern: Pattern = Pattern::parse(input)?;
let end_span = pattern.span;
let hash_tag_pattern = Pattern {
kind: PatternKind::Literal(Keyword::Rust(String::from("#"))),
span: _hash_tag.span,
meta: None,
};
Matcher {
kind: MatcherKind::Nested(vec![hash_tag_pattern, pattern]),
span: start_span.join(end_span).unwrap_or(start_span),
}
} else {
let capture = Capture::parse(input, ctx)?;
let span = capture.span;
if let MatcherKind::Enum { .. } = &capture.matcher.kind {
Matcher {
kind: capture.matcher.kind,
span,
}
} else {
let pattern = Pattern {
kind: PatternKind::Capture(Box::new(capture)),
span,
meta: None,
};
Matcher {
kind: MatcherKind::Nested(vec![pattern]),
span,
}
}
}
} else if input.peek(Ident) {
if input.peek2(token::Brace) {
let enum_name: Type = input.parse()?;
let start_span = enum_name.span();
let inner;
let _brace = braced!(inner in input);
let variants = inner.parse_terminated(EnumVariant::parse, Token![,])?;
let span = if let Some(v) = variants.last() {
start_span.join(v.span()).unwrap_or(start_span)
} else {
start_span
};
let variants = variants
.iter()
.map(|v| {
(
v.clone(),
Matcher {
span: v.span(),
kind: match &v {
EnumVariant::Type { ty, .. } => {
MatcherKind::SynType(ty.clone())
}
EnumVariant::Capture { pattern, .. } => {
MatcherKind::Nested(vec![*pattern.clone()])
}
},
},
)
})
.collect();
return Ok(Matcher {
kind: MatcherKind::Enum {
enum_name,
variants,
},
span,
});
}
let ty: Type = input.parse()?;
let span = ty.span();
Matcher {
kind: MatcherKind::SynType(ty),
span,
}
} else {
let pattern: Pattern = Pattern::parse(input)?;
let span = pattern.span;
if let PatternKind::Group {
delimiter: Delimiter::None,
children,
} = pattern.kind
{
Matcher {
kind: MatcherKind::Nested(children),
span,
}
} else {
Matcher {
kind: MatcherKind::Nested(vec![pattern]),
span,
}
}
};
if !input.is_empty() {
let start_span = cap.span;
match cap.kind {
MatcherKind::SynType(_) | MatcherKind::Enum { .. } => Err(syn::Error::new(
input.span(),
format!("Unexpected '{}'", input),
)),
MatcherKind::TokenStreamCollect { .. } => Err(syn::Error::new(
input.span(),
format!("Unexpected '{}'", input),
)),
MatcherKind::Nested(mut pattern_list) => {
let pattern: Pattern = Pattern::parse(input)?;
let end_span = pattern.span;
pattern_list.push(pattern);
let matcher = Matcher {
kind: MatcherKind::Nested(pattern_list),
span: start_span.join(end_span).unwrap_or(start_span),
};
Ok(matcher)
}
}
} else {
Ok(cap)
}
}
}
impl Parse for EnumVariant {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let fork = input.fork();
if let Ok(ty) = fork.parse::<Type>() {
if fork.is_empty() || fork.peek(Token![,]) {
input.advance_to(&fork);
let path: Path = parse_quote!(#ty);
let ident = path.segments.last().unwrap();
let ident = parse_quote!(#ident);
return Ok(EnumVariant::Type { ident, ty });
}
}
let ident: Ident = input.parse()?;
let ident: Type = parse_quote!(#ident);
let _colon: Token![:] = input.parse()?;
let fork = input.fork();
if let Ok(ty) = fork.parse::<Type>() {
if fork.is_empty() || fork.peek(Token![,]) {
input.advance_to(&fork);
return Ok(EnumVariant::Type { ident, ty });
}
}
{
let fork = input.fork();
let mut tokens = TokenStream::new();
while !fork.peek(Token![,]) && !fork.is_empty() {
tokens.append(fork.parse::<TokenTree>()?);
}
let parser = |input: ParseStream| -> syn::Result<Pattern> { Pattern::parse(input) };
let pattern = parser.parse2(tokens)?;
input.advance_to(&fork);
let captures = pattern.collect_captures();
let named = if let Some(cap) = captures.first() {
!cap.is_inline
} else {
false
};
Ok(EnumVariant::Capture {
ident,
named,
fields: captures,
pattern: Box::new(pattern),
})
}
}
}
impl EnumVariant {
fn span(&self) -> proc_macro2::Span {
match self {
EnumVariant::Capture { ident, pattern, .. } => {
ident.span().join(pattern.span).unwrap_or(ident.span())
}
EnumVariant::Type { ident, ty } => ident.span().join(ty.span()).unwrap_or(ident.span()),
}
}
}