use nom::{
branch::alt,
bytes::streaming::{escaped, tag, tag_no_case, take, take_while, take_while1},
character::streaming::{char, digit1, one_of, space0},
combinator::{map, map_res, opt, peek},
multi::{separated_list0, separated_list1},
sequence::{delimited, preceded},
IResult, Parser,
};
use std::borrow::Cow;
use std::str::{from_utf8, FromStr};
pub(crate) fn to_owned_cow<T: ?Sized + ToOwned>(c: Cow<'_, T>) -> Cow<'static, T> {
Cow::Owned(c.into_owned())
}
pub fn number(i: &[u8]) -> IResult<&[u8], u32> {
let (i, bytes) = digit1(i)?;
match from_utf8(bytes).ok().and_then(|s| u32::from_str(s).ok()) {
Some(v) => Ok((i, v)),
None => Err(nom::Err::Error(nom::error::make_error(
i,
nom::error::ErrorKind::MapRes,
))),
}
}
pub fn number_64(i: &[u8]) -> IResult<&[u8], u64> {
let (i, bytes) = digit1(i)?;
match from_utf8(bytes).ok().and_then(|s| u64::from_str(s).ok()) {
Some(v) => Ok((i, v)),
None => Err(nom::Err::Error(nom::error::make_error(
i,
nom::error::ErrorKind::MapRes,
))),
}
}
pub fn sequence_range(i: &[u8]) -> IResult<&[u8], std::ops::RangeInclusive<u32>> {
map((number, tag(":"), number), |(s, _, e)| s..=e).parse(i)
}
pub fn sequence_set(i: &[u8]) -> IResult<&[u8], Vec<std::ops::RangeInclusive<u32>>> {
separated_list1(tag(","), alt((sequence_range, map(number, |n| n..=n)))).parse(i)
}
pub fn string(i: &[u8]) -> IResult<&[u8], &[u8]> {
alt((quoted, literal)).parse(i)
}
#[inline]
fn lossy_str(bytes: &[u8]) -> Cow<'_, str> {
String::from_utf8_lossy(bytes)
}
pub fn string_utf8(i: &[u8]) -> IResult<&[u8], Cow<'_, str>> {
map(string, lossy_str).parse(i)
}
pub fn quoted(i: &[u8]) -> IResult<&[u8], &[u8]> {
delimited(
char('"'),
alt((
map(peek(char('"')), |_| &b""[..]),
escaped(
take_while1(|byte| is_text_char(byte) && !is_quoted_specials(byte)),
'\\',
one_of("\\\""),
),
)),
char('"'),
)
.parse(i)
}
pub fn quoted_utf8(i: &[u8]) -> IResult<&[u8], Cow<'_, str>> {
map(quoted, lossy_str).parse(i)
}
pub fn is_quoted_specials(c: u8) -> bool {
c == b'"' || c == b'\\'
}
pub fn literal(input: &[u8]) -> IResult<&[u8], &[u8]> {
let mut parser = (tag("{"), number, tag("}"), tag("\r\n"));
let (remaining, (_, count, _, _)) = parser.parse(input)?;
let (remaining, data) = take(count)(remaining)?;
Ok((remaining, data))
}
pub fn astring(i: &[u8]) -> IResult<&[u8], &[u8]> {
alt((take_while1(is_astring_char), string)).parse(i)
}
pub fn astring_utf8(i: &[u8]) -> IResult<&[u8], Cow<'_, str>> {
map(astring, lossy_str).parse(i)
}
pub fn is_astring_char(c: u8) -> bool {
is_atom_char(c) || is_resp_specials(c)
}
pub fn is_atom_char(c: u8) -> bool {
is_char(c) && !is_atom_specials(c)
}
pub fn is_atom_specials(c: u8) -> bool {
c == b'('
|| c == b')'
|| c == b'{'
|| c == b' '
|| c < 32
|| is_list_wildcards(c)
|| is_quoted_specials(c)
|| is_resp_specials(c)
}
pub fn is_resp_specials(c: u8) -> bool {
c == b']'
}
pub fn atom(i: &[u8]) -> IResult<&[u8], &str> {
map_res(take_while1(is_atom_char), from_utf8).parse(i)
}
pub fn nstring(i: &[u8]) -> IResult<&[u8], Option<&[u8]>> {
alt((map(nil, |_| None), map(string, Some))).parse(i)
}
pub fn nstring_utf8(i: &[u8]) -> IResult<&[u8], Option<Cow<'_, str>>> {
alt((map(nil, |_| None), map(string_utf8, Some))).parse(i)
}
pub fn nil(i: &[u8]) -> IResult<&[u8], &[u8]> {
tag_no_case("NIL")(i)
}
pub fn text(i: &[u8]) -> IResult<&[u8], Cow<'_, str>> {
map(take_while(is_text_char), lossy_str).parse(i)
}
pub fn is_text_char(c: u8) -> bool {
is_char(c) && c != b'\r' && c != b'\n'
}
pub fn is_char(c: u8) -> bool {
c != 0
}
pub fn is_list_wildcards(c: u8) -> bool {
c == b'%' || c == b'*'
}
pub fn paren_delimited<'a, O, E>(
f: impl Parser<&'a [u8], Output = O, Error = E>,
) -> impl Parser<&'a [u8], Output = O, Error = E>
where
E: nom::error::ParseError<&'a [u8]>,
{
delimited(char('('), f, char(')'))
}
pub fn parenthesized_nonempty_list<'a, O, E>(
f: impl Parser<&'a [u8], Output = O, Error = E>,
) -> impl Parser<&'a [u8], Output = Vec<O>, Error = E>
where
E: nom::error::ParseError<&'a [u8]>,
{
delimited(
char('('),
separated_list1(char(' '), f),
(space0, char(')')),
)
}
pub fn parenthesized_list<'a, O, E>(
f: impl Parser<&'a [u8], Output = O, Error = E>,
) -> impl Parser<&'a [u8], Output = Vec<O>, Error = E>
where
E: nom::error::ParseError<&'a [u8]>,
{
delimited(
char('('),
separated_list0(char(' '), f),
preceded(
opt(char(' ')), char(')'),
),
)
}
pub fn opt_opt<'a, O, E>(
mut f: impl Parser<&'a [u8], Output = Option<O>, Error = E>,
) -> impl Parser<&'a [u8], Output = Option<O>, Error = E>
where
E: nom::error::ParseError<&'a [u8]>,
{
move |i: &'a [u8]| match f.parse(i) {
Ok((i, o)) => Ok((i, o)),
Err(nom::Err::Error(_)) => Ok((i, None)),
Err(e) => Err(e),
}
}
#[cfg(test)]
mod tests {
use super::*;
use assert_matches::assert_matches;
#[test]
fn test_quoted() {
let (rem, val) = quoted(br#""Hello"???"#).unwrap();
assert_eq!(rem, b"???");
assert_eq!(val, b"Hello");
assert_eq!(
quoted(br#""Hello \" "???"#),
Ok((&b"???"[..], &br#"Hello \" "#[..]))
);
assert_eq!(
quoted(br#""Hello \\ "???"#),
Ok((&b"???"[..], &br#"Hello \\ "#[..]))
);
assert!(quoted(br#""Hello \a "???"#).is_err());
assert!(quoted(br#""Hello \z "???"#).is_err());
assert!(quoted(br#""Hello \? "???"#).is_err());
let (rem, val) = quoted(br#""Hello \"World\""???"#).unwrap();
assert_eq!(rem, br#"???"#);
assert_eq!(val, br#"Hello \"World\""#);
assert_matches!(quoted(br#""#), Err(nom::Err::Incomplete(_)));
assert_matches!(quoted(br#""\"#), Err(nom::Err::Incomplete(_)));
assert_matches!(quoted(br#""Hello "#), Err(nom::Err::Incomplete(_)));
assert_matches!(quoted(br"\"), Err(nom::Err::Error(_)));
}
#[test]
fn test_string_literal() {
match string(b"{3}\r\nXYZ") {
Ok((_, value)) => {
assert_eq!(value, b"XYZ");
}
rsp => panic!("unexpected response {rsp:?}"),
}
}
#[test]
fn test_string_literal_containing_null() {
match string(b"{5}\r\nX\0Y\0Z") {
Ok((_, value)) => {
assert_eq!(value, b"X\0Y\0Z");
}
rsp => panic!("unexpected response {rsp:?}"),
}
}
#[test]
fn test_astring() {
match astring(b"text ") {
Ok((_, value)) => {
assert_eq!(value, b"text");
}
rsp => panic!("unexpected response {rsp:?}"),
}
}
#[test]
fn test_sequence_range() {
match sequence_range(b"23:28 ") {
Ok((_, value)) => {
assert_eq!(*value.start(), 23);
assert_eq!(*value.end(), 28);
assert_eq!(value.collect::<Vec<u32>>(), vec![23, 24, 25, 26, 27, 28]);
}
rsp => panic!("Unexpected response {rsp:?}"),
}
}
#[test]
fn test_sequence_set() {
match sequence_set(b"1,2:8,10,15:30 ") {
Ok((_, value)) => {
assert_eq!(value.len(), 4);
let v = &value[0];
assert_eq!(*v.start(), 1);
assert_eq!(*v.end(), 1);
let v = &value[1];
assert_eq!(*v.start(), 2);
assert_eq!(*v.end(), 8);
let v = &value[2];
assert_eq!(*v.start(), 10);
assert_eq!(*v.end(), 10);
let v = &value[3];
assert_eq!(*v.start(), 15);
assert_eq!(*v.end(), 30);
}
rsp => panic!("Unexpected response {rsp:?}"),
}
}
}