Add special matchers, lookahead operators
This commit is contained in:
+63
-3
@@ -1,5 +1,6 @@
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
use kw::{ANY, EOI};
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{
|
||||
@@ -20,16 +21,25 @@ struct Rule {
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Term {
|
||||
AnyChar,
|
||||
Choice(Vec<Term>),
|
||||
EOI,
|
||||
Literal(String),
|
||||
NegLookahead(Box<Term>),
|
||||
Optional(Box<Term>),
|
||||
Plus(Box<Term>),
|
||||
PosLookahead(Box<Term>),
|
||||
Range(RangeInclusive<char>),
|
||||
Rule(Ident),
|
||||
Sequence(Vec<Term>),
|
||||
Star(Box<Term>),
|
||||
}
|
||||
|
||||
mod kw {
|
||||
syn::custom_keyword!(ANY);
|
||||
syn::custom_keyword!(EOI);
|
||||
}
|
||||
|
||||
impl Parse for Grammar {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
Ok(Grammar {
|
||||
@@ -59,7 +69,13 @@ impl Parse for Term {
|
||||
fn parse_atom(input: ParseStream) -> syn::Result<Term> {
|
||||
let look = input.lookahead1();
|
||||
if look.peek(Ident) {
|
||||
input.parse().map(Term::Rule)
|
||||
if input.parse::<ANY>().is_ok() {
|
||||
Ok(Term::AnyChar)
|
||||
} else if input.parse::<EOI>().is_ok() {
|
||||
Ok(Term::EOI)
|
||||
} else {
|
||||
input.parse().map(Term::Rule)
|
||||
}
|
||||
} else if look.peek(LitStr) {
|
||||
input.parse::<LitStr>().map(|x| Term::Literal(x.value()))
|
||||
} else if look.peek(LitChar) {
|
||||
@@ -89,10 +105,20 @@ impl Parse for Term {
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn parse_lookahead(input: ParseStream) -> syn::Result<Term> {
|
||||
if input.parse::<Token![!]>().is_ok() {
|
||||
parse_repeat(input).map(|x| Term::NegLookahead(x.into()))
|
||||
} else if input.parse::<Token![&]>().is_ok() {
|
||||
parse_repeat(input).map(|x| Term::PosLookahead(x.into()))
|
||||
} else {
|
||||
parse_repeat(input)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_sequence(input: ParseStream) -> syn::Result<Term> {
|
||||
let mut terms = vec![parse_repeat(input)?];
|
||||
let mut terms = vec![parse_lookahead(input)?];
|
||||
while !input.is_empty() && !input.peek(Token![/]) && !input.peek(Token![;]) {
|
||||
terms.push(parse_repeat(input)?);
|
||||
terms.push(parse_lookahead(input)?);
|
||||
}
|
||||
if terms.len() == 1 {
|
||||
Ok(terms.pop().unwrap())
|
||||
@@ -121,6 +147,12 @@ impl Parse for Term {
|
||||
impl Term {
|
||||
fn generate_code(&self) -> proc_macro2::TokenStream {
|
||||
match self {
|
||||
Term::AnyChar => quote! {
|
||||
p.any()
|
||||
},
|
||||
Term::EOI => quote! {
|
||||
p.eoi()
|
||||
},
|
||||
Term::Rule(ident) => quote! {
|
||||
#ident(p)
|
||||
},
|
||||
@@ -186,6 +218,34 @@ impl Term {
|
||||
p.range(#lo..=#hi)
|
||||
}
|
||||
}
|
||||
Term::NegLookahead(term) => {
|
||||
let code = term.generate_code();
|
||||
quote! {
|
||||
{
|
||||
let save = p.save();
|
||||
if #code {
|
||||
p.restore(save);
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Term::PosLookahead(term) => {
|
||||
let code = term.generate_code();
|
||||
quote! {
|
||||
{
|
||||
let save = p.save();
|
||||
if #code {
|
||||
p.restore(save);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+34
-2
@@ -45,6 +45,19 @@ impl ParseState {
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn any(&mut self) -> bool {
|
||||
if let Some(c) = self.input[self.offset..].chars().next() {
|
||||
self.offset += c.len_utf8();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eoi(&self) -> bool {
|
||||
self.offset >= self.input.len()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -54,7 +67,7 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
grammar! {
|
||||
fake_csv = line "\n" line;
|
||||
fake_csv = line "\n" line EOI;
|
||||
line = item "," item;
|
||||
item = "foo" / "bar";
|
||||
|
||||
@@ -69,11 +82,15 @@ mod tests {
|
||||
|
||||
words = "(" (plain_word ("," plain_word)*)? ")";
|
||||
nested_choice = "(" ("a" / "b") ("c" / "d") ")";
|
||||
|
||||
dq = "\"";
|
||||
quoted = dq ("\\" ANY / !dq ANY)* dq;
|
||||
}
|
||||
|
||||
fn parse<T: Fn(&mut ParseState) -> bool>(rule: T, s: &str) -> bool {
|
||||
println!("About to parse: {}", s);
|
||||
let mut p = ParseState::new(s);
|
||||
rule(&mut p)
|
||||
rule(&mut p) && p.eoi()
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -82,6 +99,8 @@ mod tests {
|
||||
assert!(fake_csv(&mut p));
|
||||
let mut p = ParseState::new("foo,foo\nfoo;foo");
|
||||
assert!(!fake_csv(&mut p));
|
||||
let mut p = ParseState::new("foo,foo\nfoo,foo\njunk at end");
|
||||
assert!(!fake_csv(&mut p));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -131,4 +150,17 @@ mod tests {
|
||||
assert!(!parse(nested_choice, "(c)"));
|
||||
assert!(!parse(nested_choice, "(d)"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_quoted() {
|
||||
assert!(parse(quoted, r#""""#));
|
||||
assert!(parse(quoted, r#""foo""#));
|
||||
assert!(parse(quoted, r#""\"""#));
|
||||
assert!(parse(quoted, r#""foo \"bar\" baz""#));
|
||||
assert!(parse(quoted, r#""C:\\>""#));
|
||||
|
||||
assert!(!parse(quoted, r#""no end"#));
|
||||
assert!(!parse(quoted, r#""foo " bar""#));
|
||||
assert!(!parse(quoted, r#""false end \""#));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user