Add special matchers, lookahead operators

This commit is contained in:
2025-05-28 00:13:03 -07:00
parent ee5451e2b5
commit df99d78a64
2 changed files with 97 additions and 5 deletions
+62 -2
View File
@@ -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) {
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
View File
@@ -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 \""#));
}
}