Skip to content

Commit

Permalink
Implement nom traits for more slices, not just byte slices
Browse files Browse the repository at this point in the history
This can be useful in cases where you already have an in-memory flat representation,
but want to use Nom to traverse it in a custom order.

This has a few opinionated decisions:
- `InputIter` is only implemented when `T: Copy`. This could easily be relaxed to `T: Clone`,
  but that could be surprising if T is expensive to clone.
- `Compare` is only implemented when `T: Eq`. This could be relaxed to PartialEq.
  • Loading branch information
jyn514 committed Dec 24, 2021
1 parent 598a4c0 commit 69b530e
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 8 deletions.
22 changes: 14 additions & 8 deletions src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -332,10 +332,13 @@ pub trait InputTake: Sized {
fn take_split(&self, count: usize) -> (Self, Self);
}

impl<'a> InputIter for &'a [u8] {
type Item = u8;
impl<'a, T> InputIter for &'a [T]
where
T: Copy,
{
type Item = T;
type Iter = Enumerate<Self::IterElem>;
type IterElem = Copied<Iter<'a, u8>>;
type IterElem = Copied<Iter<'a, T>>;

#[inline]
fn iter_indices(&self) -> Self::Iter {
Expand All @@ -362,7 +365,7 @@ impl<'a> InputIter for &'a [u8] {
}
}

impl<'a> InputTake for &'a [u8] {
impl<'a, T> InputTake for &'a [T] {
#[inline]
fn take(&self, count: usize) -> Self {
&self[0..count]
Expand Down Expand Up @@ -738,9 +741,12 @@ fn lowercase_byte(c: u8) -> u8 {
}
}

impl<'a, 'b> Compare<&'b [u8]> for &'a [u8] {
impl<'a, 'b, T> Compare<&'b [T]> for &'a [T]
where
T: Eq,
{
#[inline(always)]
fn compare(&self, t: &'b [u8]) -> CompareResult {
fn compare(&self, t: &'b [T]) -> CompareResult {
let pos = self.iter().zip(t.iter()).position(|(a, b)| a != b);

match pos {
Expand Down Expand Up @@ -790,8 +796,8 @@ impl<'a, 'b> CompareIgnoreCase<&'b [u8]> for &'a [u8] {
}

impl<
T: InputLength + InputIter<Item = u8> + InputTake + UnspecializedInput,
O: InputLength + InputIter<Item = u8> + InputTake,
T: Eq + InputLength + InputIter<Item = T> + InputTake + UnspecializedInput,
O: InputLength + InputIter<Item = T> + InputTake,
> Compare<O> for T
{
#[inline(always)]
Expand Down
37 changes: 37 additions & 0 deletions tests/issues.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,3 +206,40 @@ fn issue_1231_bits_expect_fn_closure() {
}
assert_eq!(example(&[0xff]), Ok((&b""[..], (1, 1))));
}

#[test]
fn non_byte_slices_can_be_parsed() {
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::combinator::map;
use nom::sequence::tuple;

#[derive(Debug, PartialEq, Eq)]
enum Token {
Open, Close, Comma, Num(u8),
}

fn magnitude(input: &[Token]) -> IResult<&[Token], u8> {
// An element can be either a pair of elements, or a number literal.
alt((magnitude_of_pair, parse_number_from_token))(input)
}
fn magnitude_of_pair(input: &[Token]) -> IResult<&[Token], u8> {
let eat = |t| tag(std::slice::from_ref(t));
let parser = tuple((eat(&Token::Open), magnitude, eat(&Token::Comma), magnitude, eat(&Token::Close)));
let mut discard_delimiter_parser = map(parser, |(_, l, _, r, _)| 3 * l + 2 * r);
discard_delimiter_parser(input)
}
fn parse_number_from_token(input: &[Token]) -> IResult<&[Token], u8> {
use nom::Err;
match input.get(0) {
None => Err(Err::Incomplete(Needed::new(1))),
Some(Token::Num(n)) => Ok((&input[1..], *n)),
Some(_) => Err(Err::Error(nom::error::Error { input, code: nom::error::ErrorKind::Tag })),
}
}

let tokenstream = vec![Token::Open, Token::Num(4), Token::Comma, Token::Num(3), Token::Close];
let (remaining, magnitude) = magnitude_of_pair(&tokenstream).unwrap();
assert_eq!(remaining, &[]);
assert_eq!(magnitude, 18);
}

0 comments on commit 69b530e

Please sign in to comment.