Add grammar! matching on character ranges

This commit is contained in:
2025-05-26 22:59:17 -07:00
parent 0994054291
commit 02a3e9b267
2 changed files with 45 additions and 3 deletions
+21 -3
View File
@@ -1,7 +1,9 @@
use std::ops::RangeInclusive;
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::quote; use quote::quote;
use syn::{ use syn::{
Ident, LitStr, Token, Ident, LitChar, LitStr, Token,
parse::{Parse, ParseStream}, parse::{Parse, ParseStream},
parse_macro_input, parse_macro_input,
punctuated::Punctuated, punctuated::Punctuated,
@@ -20,10 +22,11 @@ enum Term {
Choice(Vec<Term>), Choice(Vec<Term>),
Literal(LitStr), Literal(LitStr),
Optional(Box<Term>), Optional(Box<Term>),
Plus(Box<Term>),
Range(RangeInclusive<char>),
Rule(Ident), Rule(Ident),
Sequence(Vec<Term>), Sequence(Vec<Term>),
Star(Box<Term>), Star(Box<Term>),
Plus(Box<Term>),
} }
impl Parse for Grammar { impl Parse for Grammar {
@@ -45,12 +48,21 @@ 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> {
fn parse_range(input: ParseStream) -> syn::Result<Term> {
let start = input.parse::<LitChar>()?.value();
input.parse::<Token![..]>()?;
let end = input.parse::<LitChar>()?.value();
Ok(Term::Range(start..=end))
}
fn parse_atom(input: ParseStream) -> syn::Result<Term> { fn parse_atom(input: ParseStream) -> syn::Result<Term> {
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)
} else if look.peek(LitStr) { } else if look.peek(LitStr) {
input.parse().map(Term::Literal) input.parse().map(Term::Literal)
} else if look.peek(LitChar) {
parse_range(input)
} else { } else {
Err(look.error()) Err(look.error())
} }
@@ -74,7 +86,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) { while input.peek(Ident) || input.peek(LitStr) || input.peek(LitChar) {
terms.push(parse_repeat(input)?); terms.push(parse_repeat(input)?);
} }
if terms.len() == 1 { if terms.len() == 1 {
@@ -158,6 +170,12 @@ impl Term {
} }
} }
} }
Term::Range(range) => {
let (lo, hi) = (range.start(), range.end());
quote! {
p.range(#lo..=#hi)
}
}
} }
} }
} }
+24
View File
@@ -1,3 +1,5 @@
use std::ops::RangeInclusive;
pub struct ParseState { pub struct ParseState {
pub input: String, pub input: String,
pub offset: usize, pub offset: usize,
@@ -33,6 +35,16 @@ impl ParseState {
false false
} }
} }
pub fn range(&mut self, r: RangeInclusive<char>) -> bool {
if let Some(next) = self.input[self.offset..].chars().next() {
if r.contains(&next) {
self.offset += next.len_utf8();
return true;
}
}
false
}
} }
#[cfg(test)] #[cfg(test)]
@@ -49,6 +61,10 @@ mod tests {
option = "(" "x"? ")"; option = "(" "x"? ")";
plus = "(" "x"+ ")"; plus = "(" "x"+ ")";
star = "(" "x"* ")"; star = "(" "x"* ")";
word_head = "_" / 'A'..'Z' / 'a'..'z';
word_tail = word_head / '0'..'9';
word = "(" word_head word_tail* ")";
} }
fn parse<T: Fn(&mut ParseState) -> bool>(rule: T, s: &str) -> bool { fn parse<T: Fn(&mut ParseState) -> bool>(rule: T, s: &str) -> bool {
@@ -81,4 +97,12 @@ mod tests {
assert!(parse(star, many)); assert!(parse(star, many));
assert!(parse(plus, many)); assert!(parse(plus, many));
} }
#[test]
fn test_ranges() {
assert!(parse(word, "(_FooBar)"));
assert!(parse(word, "(abc123)"));
assert!(!parse(word, "(123abc)"));
assert!(!parse(word, "(foo-bar)"));
}
} }