Add grammar! support for choice operator

This commit is contained in:
2025-05-26 00:18:21 -07:00
parent 1e90a68ee8
commit 2a935840fd
2 changed files with 37 additions and 14 deletions
+29 -6
View File
@@ -18,6 +18,7 @@ struct Rule {
} }
enum Term { enum Term {
Choice(Vec<Term>),
Literal(LitStr), Literal(LitStr),
Rule(Ident), Rule(Ident),
Sequence(Vec<Term>), Sequence(Vec<Term>),
@@ -42,8 +43,7 @@ impl Parse for Rule {
impl Parse for Term { impl Parse for Term {
fn parse(input: ParseStream) -> syn::Result<Self> { fn parse(input: ParseStream) -> syn::Result<Self> {
let mut terms = vec![]; fn parse_atom(input: ParseStream) -> syn::Result<Term> {
let parse_one = || {
let look = input.lookahead1(); let look = input.lookahead1();
if look.peek(Ident) { if look.peek(Ident) {
input.parse().map(Term::Rule) input.parse().map(Term::Rule)
@@ -52,10 +52,25 @@ impl Parse for Term {
} else { } else {
Err(look.error()) Err(look.error())
} }
}; }
terms.push(parse_one()?);
fn parse_term(input: ParseStream) -> syn::Result<Term> {
let mut choices = vec![parse_atom(input)?];
while input.peek(Token![/]) {
input.parse::<Token![/]>()?;
choices.push(parse_atom(input)?);
}
if choices.len() == 1 {
Ok(choices.pop().unwrap())
} else {
Ok(Term::Choice(choices))
}
}
fn parse_sequence(input: ParseStream) -> syn::Result<Term> {
let mut terms = vec![parse_term(input)?];
while !input.is_empty() && !input.peek(Token![;]) { while !input.is_empty() && !input.peek(Token![;]) {
terms.push(parse_one()?); terms.push(parse_term(input)?);
} }
if terms.len() == 1 { if terms.len() == 1 {
Ok(terms.pop().unwrap()) Ok(terms.pop().unwrap())
@@ -63,6 +78,9 @@ impl Parse for Term {
Ok(Term::Sequence(terms)) Ok(Term::Sequence(terms))
} }
} }
parse_sequence(input)
}
} }
impl Term { impl Term {
@@ -103,13 +121,18 @@ impl Term {
#(#parts)* #(#parts)*
} }
} }
Term::Choice(terms) => terms
.iter()
.map(|t| t.generate_wrapped_code())
.reduce(|x, y| quote! { #x || #y })
.unwrap(),
} }
} }
fn generate_wrapped_code(&self) -> proc_macro2::TokenStream { fn generate_wrapped_code(&self) -> proc_macro2::TokenStream {
let code = self.generate_code(); let code = self.generate_code();
match self { match self {
Term::Sequence(_) => quote! { Term::Sequence(_) | Term::Choice(_) => quote! {
(|| { (|| {
#code #code
})() })()
+3 -3
View File
@@ -70,8 +70,8 @@ mod tests {
grammar! { grammar! {
fake_csv = line "\n" line; fake_csv = line "\n" line;
line = foo "," foo; line = item "," item;
foo = "foo"; item = "foo" / "bar";
} }
#[test] #[test]
@@ -84,7 +84,7 @@ mod tests {
#[test] #[test]
fn test_generated() { fn test_generated() {
let mut p = ParseState::new("foo,foo\nfoo,foo"); let mut p = ParseState::new("foo,bar\nbar,foo");
assert!(fake_csv(&mut p)); assert!(fake_csv(&mut p));
let mut p = ParseState::new("foo,foo\nfoo;foo"); let mut p = ParseState::new("foo,foo\nfoo;foo");
assert!(!fake_csv(&mut p)); assert!(!fake_csv(&mut p));