use peg::error::{ExpectedSet, ParseError};
use peg::str::LineCol;
pub const MAX_NESTING: usize = 256;
pub fn check_nesting(input: &str, max: usize) -> Result<(), ParseError<LineCol>> {
#[derive(PartialEq)]
enum Lex {
Code,
Str,
StrEscape,
Comment,
}
let bytes = input.as_bytes();
let mut state = Lex::Code;
let mut depth: usize = 0;
for (offset, &b) in bytes.iter().enumerate() {
match state {
Lex::Code => match b {
b'(' | b'[' | b'{' => {
depth += 1;
if depth > max {
return Err(too_deep(input, offset, max));
}
}
b')' | b']' | b'}' => depth = depth.saturating_sub(1),
b'"' => state = Lex::Str,
b';' => state = Lex::Comment,
_ => {}
},
Lex::Str => match b {
b'\\' => state = Lex::StrEscape,
b'"' => state = Lex::Code,
_ => {}
},
Lex::StrEscape => state = Lex::Str,
Lex::Comment => {
if b == b'\n' || b == b'\r' {
state = Lex::Code;
}
}
}
}
Ok(())
}
fn too_deep(input: &str, offset: usize, max: usize) -> ParseError<LineCol> {
let before = &input[..offset];
let line = before.bytes().filter(|&b| b == b'\n').count() + 1;
let column = offset - before.rfind('\n').map_or(0, |i| i + 1) + 1;
ParseError {
location: LineCol {
line,
column,
offset,
},
expected: expected_set(max),
}
}
fn expected_set(max: usize) -> ExpectedSet {
peg::parser!(grammar limit() for str {
pub rule default() = expected!("nesting depth at most 256")
pub rule pg_value() = expected!("nesting depth at most 100")
pub rule other() = expected!("less nesting (input nests too deeply)")
});
let r = match max {
MAX_NESTING => limit::default(""),
100 => limit::pg_value(""),
_ => limit::other(""),
};
r.expect_err("expected! always fails").expected
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn counts_every_opener() {
assert!(check_nesting("[({#{}})]", 4).is_ok());
assert!(check_nesting("[({#{}})]", 3).is_err());
}
#[test]
fn strings_and_comments_are_opaque() {
assert!(check_nesting(r#"["[[[[" "\"[[[[" "\\" ]"#, 1).is_ok());
assert!(check_nesting("[ ; [[[[\n ]", 1).is_ok());
assert!(check_nesting("[ ; [[[[\r ]", 1).is_ok());
}
#[test]
fn escaped_backslash_ends_the_string() {
assert!(check_nesting(r#"["\\" [ ]]"#, 1).is_err());
assert!(check_nesting(r#"["\\" [ ]]"#, 2).is_ok());
}
#[test]
fn error_names_the_limit_and_position() {
let e = check_nesting("[\n [[", MAX_NESTING.min(2)).unwrap_err();
assert_eq!((e.location.line, e.location.column), (2, 3));
let e = check_nesting(&"[".repeat(MAX_NESTING + 1), MAX_NESTING).unwrap_err();
assert!(e.to_string().contains("nesting depth at most 256"), "{e}");
}
#[test]
fn unbalanced_closers_do_not_underflow() {
assert!(check_nesting("]]]][", 1).is_ok());
}
}