From 2a935840fd68aca97475e9c6d45db0bd44285c32 Mon Sep 17 00:00:00 2001 From: Chris Mounce Date: Mon, 26 May 2025 00:18:21 -0700 Subject: [PATCH] Add grammar! support for choice operator --- peg_macro/src/lib.rs | 45 +++++++++++++++++++++++++++++++++----------- src/peg/peg.rs | 6 +++--- 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/peg_macro/src/lib.rs b/peg_macro/src/lib.rs index aa49fdb..296e608 100644 --- a/peg_macro/src/lib.rs +++ b/peg_macro/src/lib.rs @@ -18,6 +18,7 @@ struct Rule { } enum Term { + Choice(Vec), Literal(LitStr), Rule(Ident), Sequence(Vec), @@ -42,8 +43,7 @@ impl Parse for Rule { impl Parse for Term { fn parse(input: ParseStream) -> syn::Result { - let mut terms = vec![]; - let parse_one = || { + fn parse_atom(input: ParseStream) -> syn::Result { let look = input.lookahead1(); if look.peek(Ident) { input.parse().map(Term::Rule) @@ -52,16 +52,34 @@ impl Parse for Term { } else { Err(look.error()) } - }; - terms.push(parse_one()?); - while !input.is_empty() && !input.peek(Token![;]) { - terms.push(parse_one()?); } - if terms.len() == 1 { - Ok(terms.pop().unwrap()) - } else { - Ok(Term::Sequence(terms)) + + fn parse_term(input: ParseStream) -> syn::Result { + let mut choices = vec![parse_atom(input)?]; + while input.peek(Token![/]) { + input.parse::()?; + 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 { + let mut terms = vec![parse_term(input)?]; + while !input.is_empty() && !input.peek(Token![;]) { + terms.push(parse_term(input)?); + } + if terms.len() == 1 { + Ok(terms.pop().unwrap()) + } else { + Ok(Term::Sequence(terms)) + } + } + + parse_sequence(input) } } @@ -103,13 +121,18 @@ impl Term { #(#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 { let code = self.generate_code(); match self { - Term::Sequence(_) => quote! { + Term::Sequence(_) | Term::Choice(_) => quote! { (|| { #code })() diff --git a/src/peg/peg.rs b/src/peg/peg.rs index 3f3ee9f..163c647 100644 --- a/src/peg/peg.rs +++ b/src/peg/peg.rs @@ -70,8 +70,8 @@ mod tests { grammar! { fake_csv = line "\n" line; - line = foo "," foo; - foo = "foo"; + line = item "," item; + item = "foo" / "bar"; } #[test] @@ -84,7 +84,7 @@ mod tests { #[test] 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)); let mut p = ParseState::new("foo,foo\nfoo;foo"); assert!(!fake_csv(&mut p));