Add support for compound groups to grammar! macro

This commit is contained in:
2025-05-26 23:54:21 -07:00
parent 02a3e9b267
commit ee5451e2b5
2 changed files with 47 additions and 11 deletions
+17 -7
View File
@@ -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
Term::Choice(terms) => {
let code = terms
.iter()
.map(|t| t.generate_code())
.reduce(|x, y| quote! { #x || #y })
.unwrap(),
.unwrap();
quote! {
( #code )
}
}
Term::Optional(term) => {
let expr = term.generate_code();
quote! {
(#expr || true)
( #expr || true )
}
}
Term::Star(term) => {
+27 -1
View File
@@ -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)"));
}
}