Add grammar! matching on character ranges
This commit is contained in:
+21
-3
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user