#[allow(clippy::wildcard_imports)]
use super::*;
pub(super) fn crlf(input: &[u8]) -> IResult<&[u8], &[u8]> {
tag(b"\r\n")(input)
}
pub(super) fn sp(input: &[u8]) -> IResult<&[u8], u8> {
nom::character::complete::char(' ')(input).map(|(i, _)| (i, b' '))
}
pub(super) fn skip_parenthesized_block(input: &[u8]) -> IResult<&[u8], ()> {
let (input, _) = char('(')(input)?;
let (input, ()) = skip_balanced_parens(input)?;
let (input, _) = char(')')(input)?;
Ok((input, ()))
}
pub(super) fn skip_balanced_parens(mut input: &[u8]) -> IResult<&[u8], ()> {
let mut depth: u32 = 0;
loop {
if input.is_empty() {
return if depth == 0 {
Ok((input, ()))
} else {
Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Char,
)))
};
}
if depth == 0 && input[0] == b')' {
return Ok((input, ()));
}
match input[0] {
b'(' => {
depth += 1;
input = &input[1..];
}
b')' if depth > 0 => {
depth -= 1;
input = &input[1..];
}
b'"' => {
input = &input[1..];
while !input.is_empty() && input[0] != b'"' {
if input[0] == b'\\' && input.len() > 1 {
if input[1] == b'\r' || input[1] == b'\n' {
break;
}
input = &input[2..]; } else {
input = &input[1..];
}
}
if !input.is_empty() {
input = &input[1..]; }
}
b'~' if input.len() > 1 && input[1] == b'{' => {
input = &input[1..]; }
b'{' => {
input = &input[1..]; let start = 0;
let mut end = start;
while end < input.len() && input[end].is_ascii_digit() {
end += 1;
}
if end > start && end < input.len() {
let count_end = end;
if input[end] == b'+' {
end += 1;
}
if end < input.len() && input[end] == b'}' {
end += 1; if end + 1 < input.len() && input[end] == b'\r' && input[end + 1] == b'\n' {
end += 2;
if let Ok(s) = std::str::from_utf8(&input[start..count_end]) {
if let Ok(count) = s.parse::<usize>() {
match end.checked_add(count) {
Some(new_end) if new_end <= input.len() => {
input = &input[new_end..];
}
_ => {
return Ok((input, ()));
}
}
}
}
}
}
}
}
_ => {
input = &input[1..];
}
}
}
}
pub(super) fn atom(input: &[u8]) -> IResult<&[u8], &[u8]> {
take_while1(is_atom_char)(input)
}
pub(super) fn objectid(input: &[u8]) -> IResult<&[u8], &[u8]> {
atom(input)
}
pub(super) fn fetch_attr_atom(input: &[u8]) -> IResult<&[u8], &[u8]> {
take_while1(is_atom_char_no_bracket)(input)
}
pub(super) fn tag_str(input: &[u8]) -> IResult<&[u8], &[u8]> {
take_while1(is_tag_char)(input)
}
fn is_tag_char(b: u8) -> bool {
(is_atom_char(b) || b == b']') && b != b'+'
}
pub(super) fn is_atom_char(b: u8) -> bool {
b > 0x1F
&& b != 0x7F
&& b != b' '
&& b != b'('
&& b != b')'
&& b != b'{'
&& b != b'%'
&& b != b'*'
&& b != b'"'
&& b != b'\\'
&& b != b']'
}
pub(super) fn is_atom_char_no_bracket(b: u8) -> bool {
is_atom_char(b) && b != b'['
}
pub(super) fn quoted_string(input: &[u8]) -> IResult<&[u8], Vec<u8>> {
let (input, _) = char('"')(input)?;
let mut result = Vec::new();
let mut i = input;
loop {
if i.is_empty() {
return Err(nom::Err::Error(nom::error::Error::new(
i,
nom::error::ErrorKind::Char,
)));
}
match i[0] {
b'"' => {
return Ok((&i[1..], result));
}
b'\\' => {
if i.len() < 2 {
return Err(nom::Err::Error(nom::error::Error::new(
i,
nom::error::ErrorKind::Char,
)));
}
let escaped = i[1];
if escaped == 0 || escaped == b'\r' || escaped == b'\n' {
return Err(nom::Err::Error(nom::error::Error::new(
i,
nom::error::ErrorKind::Char,
)));
}
if escaped != b'"' && escaped != b'\\' {
tracing::debug!(
escaped_byte = escaped,
"non-standard quoted-string escape: preserving backslash as literal data"
);
result.push(b'\\');
}
result.push(escaped);
i = &i[2..];
}
0 => {
return Err(nom::Err::Error(nom::error::Error::new(
i,
nom::error::ErrorKind::Char,
)));
}
b => {
if b == b'\r' || b == b'\n' {
return Err(nom::Err::Error(nom::error::Error::new(
i,
nom::error::ErrorKind::Char,
)));
}
result.push(b);
i = &i[1..];
}
}
}
}
pub(super) fn literal(input: &[u8]) -> IResult<&[u8], Vec<u8>> {
let (input, _is_literal8) = opt(char('~'))(input)?;
let (input, _) = char('{')(input)?;
let (input, count_bytes) = digit1(input)?;
let count_str = std::str::from_utf8(count_bytes).map_err(|_| {
nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::Digit))
})?;
let count_u64: u64 = count_str.parse().map_err(|_| {
nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::Digit))
})?;
if count_u64 > i64::MAX as u64 {
return Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Verify,
)));
}
let count: usize = usize::try_from(count_u64).map_err(|_| {
nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::Verify))
})?;
let (input, _has_plus) = opt(char('+'))(input)?;
let (input, _) = char('}')(input)?;
let (input, _) = crlf(input)?;
let (input, data) = take(count)(input)?;
Ok((input, data.to_vec()))
}
pub(super) fn string(input: &[u8]) -> IResult<&[u8], Vec<u8>> {
alt((quoted_string, literal))(input)
}
pub(super) fn astring(input: &[u8]) -> IResult<&[u8], Vec<u8>> {
alt((
map(astring_chars, |s: &[u8]| s.to_vec()),
string,
))(input)
}
fn astring_chars(input: &[u8]) -> IResult<&[u8], &[u8]> {
take_while1(|b: u8| is_atom_char(b) || b == b']')(input)
}
pub(super) fn nil_token(input: &[u8]) -> IResult<&[u8], &[u8]> {
terminated(
tag_no_case(b"NIL"),
peek(alt((
value((), verify(take(1u8), |b: &[u8]| !is_atom_char(b[0]))),
value((), eof),
))),
)(input)
}
pub(super) fn nstring(input: &[u8]) -> IResult<&[u8], Option<Vec<u8>>> {
alt((value(None, nil_token), map(string, Some)))(input)
}
pub(super) fn number(input: &[u8]) -> IResult<&[u8], u32> {
let (input, digits) = digit1(input)?;
let s = std::str::from_utf8(digits).map_err(|_| {
nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::Digit))
})?;
let n: u32 = s.parse().map_err(|_| {
nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::Digit))
})?;
Ok((input, n))
}
pub(super) fn nz_number(input: &[u8]) -> IResult<&[u8], u32> {
if input.first() == Some(&b'0') {
return Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Verify,
)));
}
let (rest, n) = number(input)?;
if n == 0 {
return Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Verify,
)));
}
Ok((rest, n))
}
pub(super) fn number64(input: &[u8]) -> IResult<&[u8], u64> {
let (input, digits) = digit1(input)?;
let s = std::str::from_utf8(digits).map_err(|_| {
nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::Digit))
})?;
let n: u64 = s.parse().map_err(|_| {
nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::Digit))
})?;
if n > i64::MAX as u64 {
return Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Verify,
)));
}
Ok((input, n))
}
pub(super) fn nz_number64(input: &[u8]) -> IResult<&[u8], u64> {
if input.first() == Some(&b'0') {
return Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Verify,
)));
}
let (rest, n) = number64(input)?;
if n == 0 {
return Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Verify,
)));
}
Ok((rest, n))
}
pub(super) fn number_tolerant(input: &[u8]) -> IResult<&[u8], Option<u32>> {
let (rest, digits) = digit1(input)?;
let val = std::str::from_utf8(digits)
.ok()
.and_then(|s| s.parse::<u32>().ok());
Ok((rest, val))
}
pub(super) fn number64_tolerant(input: &[u8]) -> IResult<&[u8], Option<u64>> {
let (rest, digits) = digit1(input)?;
let val = std::str::from_utf8(digits)
.ok()
.and_then(|s| s.parse::<u64>().ok())
.filter(|&n| i64::try_from(n).is_ok());
Ok((rest, val))
}
pub(super) fn nstring_utf8(input: &[u8]) -> IResult<&[u8], Option<String>> {
let (input, val) = nstring(input)?;
Ok((input, val.map(|v| String::from_utf8_lossy(&v).into_owned())))
}
pub(super) fn string_utf8(input: &[u8]) -> IResult<&[u8], String> {
let (input, val) = string(input)?;
Ok((input, String::from_utf8_lossy(&val).into_owned()))
}
pub(super) fn astring_utf8(input: &[u8]) -> IResult<&[u8], String> {
let (input, val) = astring(input)?;
Ok((input, String::from_utf8_lossy(&val).into_owned()))
}