278 lines
7.2 KiB
Rust
278 lines
7.2 KiB
Rust
use std::{
|
|
num::NonZero,
|
|
ops::{Range, RangeInclusive},
|
|
};
|
|
|
|
pub struct ParseState<T: Clone> {
|
|
pub input: String,
|
|
pub offset: usize,
|
|
captures: Vec<RawCapture<T>>,
|
|
}
|
|
|
|
pub struct Savepoint {
|
|
offset: usize,
|
|
}
|
|
|
|
pub struct Captures<'a, T: Clone> {
|
|
input: &'a str,
|
|
raw: &'a [RawCapture<T>],
|
|
index: usize,
|
|
}
|
|
|
|
pub struct Capture<'a, T: Clone> {
|
|
input: &'a str,
|
|
raw: &'a [RawCapture<T>],
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct RawCapture<T: Clone> {
|
|
kind: T,
|
|
span: Range<usize>,
|
|
subtree_len: Option<NonZero<usize>>,
|
|
}
|
|
|
|
impl<T: Clone> ParseState<T> {
|
|
pub fn new(s: &str) -> Self {
|
|
Self {
|
|
input: s.into(),
|
|
offset: 0,
|
|
captures: vec![],
|
|
}
|
|
}
|
|
|
|
pub fn save(&self) -> Savepoint {
|
|
Savepoint {
|
|
offset: self.offset,
|
|
}
|
|
}
|
|
|
|
pub fn restore(&mut self, save: Savepoint) {
|
|
self.offset = save.offset;
|
|
}
|
|
|
|
pub fn literal(&mut self, s: &str) -> bool {
|
|
if self.input[self.offset..].starts_with(s) {
|
|
self.offset += s.len();
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
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<char>) -> bool {
|
|
if let Some(next) = self.input[self.offset..].chars().next() {
|
|
if r.contains(&next) {
|
|
self.offset += next.len_utf8();
|
|
return true;
|
|
}
|
|
}
|
|
false
|
|
}
|
|
|
|
pub fn range_i(&mut self, r: RangeInclusive<char>) -> 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();
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
pub fn eoi(&self) -> bool {
|
|
self.offset >= self.input.len()
|
|
}
|
|
|
|
pub fn captures<'a>(&'a self) -> Captures<'a, T> {
|
|
Captures {
|
|
input: &self.input,
|
|
raw: &self.captures,
|
|
index: 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a, T: Clone> Iterator for Captures<'a, T> {
|
|
type Item = Capture<'a, T>;
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
let head = self.raw.get(self.index)?;
|
|
let subtree_len = head.subtree_len.unwrap().get();
|
|
let subtree_slice = &self.raw[self.index..self.index + subtree_len];
|
|
self.index += subtree_len;
|
|
Some(Capture {
|
|
input: &self.input,
|
|
raw: subtree_slice,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl<'a, T: Clone> Capture<'a, T> {
|
|
pub fn children(&self) -> Captures<'a, T> {
|
|
Captures {
|
|
input: &self.input,
|
|
raw: &self.raw[1..],
|
|
index: 0,
|
|
}
|
|
}
|
|
|
|
pub fn kind(&self) -> T {
|
|
self.raw[0].kind.clone()
|
|
}
|
|
|
|
pub fn span(&self) -> Range<usize> {
|
|
self.raw[0].span.clone()
|
|
}
|
|
|
|
pub fn text(&self) -> &'a str {
|
|
&self.input[self.span()]
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use peg_macro::grammar;
|
|
|
|
use super::*;
|
|
|
|
grammar! {
|
|
fake_csv = line "\n" line EOI;
|
|
line = item "," item;
|
|
item = "foo" / "bar";
|
|
|
|
option = "(" "x"? ")";
|
|
plus = "(" "x"+ ")";
|
|
star = "(" "x"* ")";
|
|
|
|
word_head = "_" / 'A'..'Z' / 'a'..'z';
|
|
word_tail = word_head / '0'..'9';
|
|
plain_word = word_head word_tail*;
|
|
word = "(" plain_word ")";
|
|
|
|
words = "(" (plain_word ("," plain_word)*)? ")";
|
|
nested_choice = "(" ("a" / "b") ("c" / "d") ")";
|
|
|
|
dq = "\"";
|
|
quoted = dq ("\\" ANY / !dq ANY)* dq;
|
|
|
|
case_sensitivity = "Strict " ('a'..'z')+ ", loose "i ('a'..'z'i)+;
|
|
|
|
@icase
|
|
hex_config = "let " var_name " = 0x" ('a'..'f' / '0'..'9')+;
|
|
var_name = "foo" / "bar"; // case must match
|
|
}
|
|
|
|
fn parse<C: Clone, T: Fn(&mut ParseState<C>) -> bool>(rule: T, s: &str) -> bool {
|
|
println!("About to parse: {}", s);
|
|
let mut p = ParseState::new(s);
|
|
rule(&mut p) && p.eoi()
|
|
}
|
|
|
|
#[test]
|
|
fn test_generated() {
|
|
let mut p = ParseState::new("foo,bar\nbar,foo");
|
|
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]
|
|
fn test_repetition_suffixes() {
|
|
let zero = "()";
|
|
assert!(parse(option, zero));
|
|
assert!(parse(star, zero));
|
|
assert!(!parse(plus, zero));
|
|
|
|
let one = "(x)";
|
|
assert!(parse(option, one));
|
|
assert!(parse(star, one));
|
|
assert!(parse(plus, one));
|
|
|
|
let many = "(xxx)";
|
|
assert!(!parse(option, many));
|
|
assert!(parse(star, 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)"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_groups() {
|
|
assert!(parse(words, "()"));
|
|
assert!(parse(words, "(foo)"));
|
|
assert!(parse(words, "(foo,bar,baz)"));
|
|
assert!(!parse(words, "(foo,)"));
|
|
assert!(!parse(words, "(,bar)"));
|
|
assert!(!parse(words, "(3baz)"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_nested_choice() {
|
|
assert!(parse(nested_choice, "(ac)"));
|
|
assert!(parse(nested_choice, "(ad)"));
|
|
assert!(parse(nested_choice, "(bc)"));
|
|
assert!(parse(nested_choice, "(bd)"));
|
|
assert!(!parse(nested_choice, "(a)"));
|
|
assert!(!parse(nested_choice, "(b)"));
|
|
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 \""#));
|
|
}
|
|
|
|
#[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"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_icase_decorator() {
|
|
assert!(parse(hex_config, "let foo = 0xc0ffee"));
|
|
assert!(parse(hex_config, "LET bar = 0XCAFE"));
|
|
assert!(!parse(hex_config, "let Foo = 0xc0ffee"));
|
|
assert!(!parse(hex_config, "let BAR = 0xcafe"));
|
|
}
|
|
}
|