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 proc_macro::TokenStream;
use quote::quote; use quote::quote;
use syn::{ use syn::{
Ident, LitChar, LitStr, Token, Ident, LitChar, LitStr, Token, parenthesized,
parse::{Parse, ParseStream}, parse::{Parse, ParseStream},
parse_macro_input, parse_macro_input,
punctuated::Punctuated, punctuated::Punctuated,
@@ -18,9 +18,10 @@ struct Rule {
definition: Term, definition: Term,
} }
#[derive(Debug)]
enum Term { enum Term {
Choice(Vec<Term>), Choice(Vec<Term>),
Literal(LitStr), Literal(String),
Optional(Box<Term>), Optional(Box<Term>),
Plus(Box<Term>), Plus(Box<Term>),
Range(RangeInclusive<char>), Range(RangeInclusive<char>),
@@ -60,9 +61,13 @@ impl Parse for Term {
if look.peek(Ident) { if look.peek(Ident) {
input.parse().map(Term::Rule) input.parse().map(Term::Rule)
} else if look.peek(LitStr) { } else if look.peek(LitStr) {
input.parse().map(Term::Literal) input.parse::<LitStr>().map(|x| Term::Literal(x.value()))
} else if look.peek(LitChar) { } else if look.peek(LitChar) {
parse_range(input) parse_range(input)
} else if look.peek(syn::token::Paren) {
let content;
parenthesized!(content in input);
parse_choice(&content)
} else { } else {
Err(look.error()) Err(look.error())
} }
@@ -86,7 +91,7 @@ impl Parse for Term {
fn parse_sequence(input: ParseStream) -> syn::Result<Term> { fn parse_sequence(input: ParseStream) -> syn::Result<Term> {
let mut terms = vec![parse_repeat(input)?]; 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)?); terms.push(parse_repeat(input)?);
} }
if terms.len() == 1 { if terms.len() == 1 {
@@ -140,15 +145,20 @@ impl Term {
} }
} }
} }
Term::Choice(terms) => terms Term::Choice(terms) => {
let code = terms
.iter() .iter()
.map(|t| t.generate_code()) .map(|t| t.generate_code())
.reduce(|x, y| quote! { #x || #y }) .reduce(|x, y| quote! { #x || #y })
.unwrap(), .unwrap();
quote! {
( #code )
}
}
Term::Optional(term) => { Term::Optional(term) => {
let expr = term.generate_code(); let expr = term.generate_code();
quote! { quote! {
(#expr || true) ( #expr || true )
} }
} }
Term::Star(term) => { Term::Star(term) => {
+27 -1
View File
@@ -64,7 +64,11 @@ mod tests {
word_head = "_" / 'A'..'Z' / 'a'..'z'; word_head = "_" / 'A'..'Z' / 'a'..'z';
word_tail = word_head / '0'..'9'; 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 { 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, "(123abc)"));
assert!(!parse(word, "(foo-bar)")); 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)"));
}
} }