Add support for compound groups to grammar! macro
This commit is contained in:
+20
-10
@@ -3,7 +3,7 @@ use std::ops::RangeInclusive;
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{
|
||||
Ident, LitChar, LitStr, Token,
|
||||
Ident, LitChar, LitStr, Token, parenthesized,
|
||||
parse::{Parse, ParseStream},
|
||||
parse_macro_input,
|
||||
punctuated::Punctuated,
|
||||
@@ -18,9 +18,10 @@ struct Rule {
|
||||
definition: Term,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Term {
|
||||
Choice(Vec<Term>),
|
||||
Literal(LitStr),
|
||||
Literal(String),
|
||||
Optional(Box<Term>),
|
||||
Plus(Box<Term>),
|
||||
Range(RangeInclusive<char>),
|
||||
@@ -60,9 +61,13 @@ impl Parse for Term {
|
||||
if look.peek(Ident) {
|
||||
input.parse().map(Term::Rule)
|
||||
} else if look.peek(LitStr) {
|
||||
input.parse().map(Term::Literal)
|
||||
input.parse::<LitStr>().map(|x| Term::Literal(x.value()))
|
||||
} else if look.peek(LitChar) {
|
||||
parse_range(input)
|
||||
} else if look.peek(syn::token::Paren) {
|
||||
let content;
|
||||
parenthesized!(content in input);
|
||||
parse_choice(&content)
|
||||
} else {
|
||||
Err(look.error())
|
||||
}
|
||||
@@ -86,7 +91,7 @@ impl Parse for Term {
|
||||
|
||||
fn parse_sequence(input: ParseStream) -> syn::Result<Term> {
|
||||
let mut terms = vec![parse_repeat(input)?];
|
||||
while input.peek(Ident) || input.peek(LitStr) || input.peek(LitChar) {
|
||||
while !input.is_empty() && !input.peek(Token![/]) && !input.peek(Token![;]) {
|
||||
terms.push(parse_repeat(input)?);
|
||||
}
|
||||
if terms.len() == 1 {
|
||||
@@ -140,15 +145,20 @@ impl Term {
|
||||
}
|
||||
}
|
||||
}
|
||||
Term::Choice(terms) => terms
|
||||
.iter()
|
||||
.map(|t| t.generate_code())
|
||||
.reduce(|x, y| quote! { #x || #y })
|
||||
.unwrap(),
|
||||
Term::Choice(terms) => {
|
||||
let code = terms
|
||||
.iter()
|
||||
.map(|t| t.generate_code())
|
||||
.reduce(|x, y| quote! { #x || #y })
|
||||
.unwrap();
|
||||
quote! {
|
||||
( #code )
|
||||
}
|
||||
}
|
||||
Term::Optional(term) => {
|
||||
let expr = term.generate_code();
|
||||
quote! {
|
||||
(#expr || true)
|
||||
( #expr || true )
|
||||
}
|
||||
}
|
||||
Term::Star(term) => {
|
||||
|
||||
+27
-1
@@ -64,7 +64,11 @@ mod tests {
|
||||
|
||||
word_head = "_" / 'A'..'Z' / 'a'..'z';
|
||||
word_tail = word_head / '0'..'9';
|
||||
word = "(" word_head word_tail* ")";
|
||||
plain_word = word_head word_tail*;
|
||||
word = "(" plain_word ")";
|
||||
|
||||
words = "(" (plain_word ("," plain_word)*)? ")";
|
||||
nested_choice = "(" ("a" / "b") ("c" / "d") ")";
|
||||
}
|
||||
|
||||
fn parse<T: Fn(&mut ParseState) -> bool>(rule: T, s: &str) -> bool {
|
||||
@@ -105,4 +109,26 @@ mod tests {
|
||||
assert!(!parse(word, "(123abc)"));
|
||||
assert!(!parse(word, "(foo-bar)"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_groups() {
|
||||
assert!(parse(words, "()"));
|
||||
assert!(parse(words, "(foo)"));
|
||||
assert!(parse(words, "(foo,bar,baz)"));
|
||||
assert!(!parse(words, "(foo,)"));
|
||||
assert!(!parse(words, "(,bar)"));
|
||||
assert!(!parse(words, "(3baz)"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nested_choice() {
|
||||
assert!(parse(nested_choice, "(ac)"));
|
||||
assert!(parse(nested_choice, "(ad)"));
|
||||
assert!(parse(nested_choice, "(bc)"));
|
||||
assert!(parse(nested_choice, "(bd)"));
|
||||
assert!(!parse(nested_choice, "(a)"));
|
||||
assert!(!parse(nested_choice, "(b)"));
|
||||
assert!(!parse(nested_choice, "(c)"));
|
||||
assert!(!parse(nested_choice, "(d)"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user