diff --git a/peg_macro/src/lib.rs b/peg_macro/src/lib.rs index 4b644a4..493386f 100644 --- a/peg_macro/src/lib.rs +++ b/peg_macro/src/lib.rs @@ -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), - Literal(LitStr), + Literal(String), Optional(Box), Plus(Box), Range(RangeInclusive), @@ -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::().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 { 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) => { diff --git a/src/peg/peg.rs b/src/peg/peg.rs index 723418f..1716457 100644 --- a/src/peg/peg.rs +++ b/src/peg/peg.rs @@ -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 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)")); + } }