From ad6d8e182b26a13fd4a6816a1be6e2ac3a2664d1 Mon Sep 17 00:00:00 2001 From: Chris Mounce Date: Fri, 30 May 2025 00:17:24 -0700 Subject: [PATCH] Add case-insensitive matching --- peg_macro/src/lib.rs | 29 +++++++++++++++++++++++++---- src/peg/peg.rs | 31 +++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/peg_macro/src/lib.rs b/peg_macro/src/lib.rs index d2ea7fc..7a62083 100644 --- a/peg_macro/src/lib.rs +++ b/peg_macro/src/lib.rs @@ -25,11 +25,13 @@ enum Term { Choice(Vec), EOI, Literal(String), + LiteralI(String), NegLookahead(Box), Optional(Box), Plus(Box), PosLookahead(Box), Range(RangeInclusive), + RangeI(RangeInclusive), Rule(Ident), Sequence(Vec), Star(Box), @@ -60,10 +62,15 @@ impl Parse for Rule { impl Parse for Term { fn parse(input: ParseStream) -> syn::Result { fn parse_range(input: ParseStream) -> syn::Result { - let start = input.parse::()?.value(); + let start_lit = input.parse::()?; input.parse::()?; - let end = input.parse::()?.value(); - Ok(Term::Range(start..=end)) + let end_lit = input.parse::()?; + let range = start_lit.value()..=end_lit.value(); + if end_lit.suffix() == "i" { + Ok(Term::RangeI(range)) + } else { + Ok(Term::Range(range)) + } } fn parse_atom(input: ParseStream) -> syn::Result { @@ -77,7 +84,12 @@ impl Parse for Term { input.parse().map(Term::Rule) } } else if look.peek(LitStr) { - input.parse::().map(|x| Term::Literal(x.value())) + let lit = input.parse::()?; + if lit.suffix() == "i" { + Ok(Term::LiteralI(lit.value())) + } else { + Ok(Term::Literal(lit.value())) + } } else if look.peek(LitChar) { parse_range(input) } else if look.peek(syn::token::Paren) { @@ -159,6 +171,9 @@ impl Term { Term::Literal(lit_str) => quote! { p.literal(#lit_str) }, + Term::LiteralI(lit_str) => quote! { + p.literal_i(#lit_str) + }, Term::Sequence(terms) => { let expr = terms .iter() @@ -218,6 +233,12 @@ impl Term { p.range(#lo..=#hi) } } + Term::RangeI(range) => { + let (lo, hi) = (range.start(), range.end()); + quote! { + p.range_i(#lo..=#hi) + } + } Term::NegLookahead(term) => { let code = term.generate_code(); quote! { diff --git a/src/peg/peg.rs b/src/peg/peg.rs index ce8be35..bd24e63 100644 --- a/src/peg/peg.rs +++ b/src/peg/peg.rs @@ -36,6 +36,15 @@ impl ParseState { } } + pub fn literal_i(&mut self, s: &str) -> bool { + if self.input[self.offset..(self.offset + s.len())].eq_ignore_ascii_case(s) { + self.offset += s.len(); + true + } else { + false + } + } + pub fn range(&mut self, r: RangeInclusive) -> bool { if let Some(next) = self.input[self.offset..].chars().next() { if r.contains(&next) { @@ -46,6 +55,16 @@ impl ParseState { false } + pub fn range_i(&mut self, r: RangeInclusive) -> bool { + if let Some(next) = self.input[self.offset..].chars().next() { + if r.contains(&next.to_ascii_lowercase()) || r.contains(&next.to_ascii_uppercase()) { + self.offset += next.len_utf8(); + return true; + } + } + false + } + pub fn any(&mut self) -> bool { if let Some(c) = self.input[self.offset..].chars().next() { self.offset += c.len_utf8(); @@ -85,6 +104,8 @@ mod tests { dq = "\""; quoted = dq ("\\" ANY / !dq ANY)* dq; + + case_sensitivity = "Strict " ('a'..'z')+ ", loose "i ('a'..'z'i)+; } fn parse bool>(rule: T, s: &str) -> bool { @@ -163,4 +184,14 @@ mod tests { assert!(!parse(quoted, r#""foo " bar""#)); assert!(!parse(quoted, r#""false end \""#)); } + + #[test] + fn test_i_suffix() { + assert!(parse(case_sensitivity, "Strict abc, loose xyz")); + assert!(!parse(case_sensitivity, "STRICT abc, loose xyz")); + assert!(!parse(case_sensitivity, "Strict ABC, loose xyz")); + assert!(parse(case_sensitivity, "Strict abc, LOOSE xyz")); + assert!(parse(case_sensitivity, "Strict abc, loose XYZ")); + assert!(parse(case_sensitivity, "Strict abc, LoOsE XyZ")); + } }