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