Skip to content

Commit

Permalink
Improve the readme and examples
Browse files Browse the repository at this point in the history
  • Loading branch information
marcdejonge committed Jan 3, 2025
1 parent e9c8fc0 commit 14d8b86
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 44 deletions.
9 changes: 4 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,16 @@ include = [
"README.md",
]

[features]
default = ["bytes"]
bytes = []
chars = []

[lib]
path = "src/lib.rs"

[[example]]
name = "simple"
path = "examples/simple.rs"

[[example]]
name = "generic_input"
path = "examples/generic_input.rs"

[dependencies]
nom = "7.1.3"
32 changes: 25 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,30 @@ trait that can be implemented on any data that can be parsed in a singular way.
This means it should have a `parse` function available and the signature of
that function is compatible with the `nom::Parser` trait.

## Generic vs Specific parsers

The `ParseFrom` trait is generic over the input type, which means that you can
define it generically over any input type that nom supports. The downside of this
is that you will need a bunch of restrictions to the input type in a `where` block.
Also, using a generic parser implementation can be more annoying to use, since in
some cases Rust can't infer the type of the input or error. See the
[generic_input](examples/generic_input.rs) example for an example of this.

If you already know what types of input and error you are going to use in the program,
using a specific implementation can be more convenient. See the [simple](examples/simple.rs)
example for an example of this.

## Default implementations

### Primitive numbers
It provides a `ParseFrom` implementation for a couple of primitive types:
There are a couple of default implementations provided by this library. Since for these
standard types you cannot implement `ParseFrom` outside of this library, there are
choices made for the parsing of these types. Mostly these are for text-based parsing.
If you want your own implementation, you can wrap the type in a struct and implement
`ParseFrom` for that struct.

### Primitives
It provides a `ParseFrom` implementation for a couple of primitive numbers, based on
normal text parsing (e.g. the string "123" will be parsed to the number 123).

- `i16`
- `i32`
Expand All @@ -19,16 +39,14 @@ It provides a `ParseFrom` implementation for a couple of primitive types:
- `u64`
- `u128`

Also, the `bool` type has a default implementation, where it will parse the string
"true" to `true` and "false" to `false`.

### `Vec<T>`
A default implementation for Vec<T> has been provided, as long as T implements
`ParseFrom`, where it uses the `nom::character::complete::line_ending` parser
to separate the elements.

Since it's not possible to implement `ParseFrom` for `Vec<T>` outside of this
library, this is the choice that has been made for separating items. If you
want you're own implementation, wrap the `Vec<T>` in a struct and implement
`ParseFrom` for that struct.

## License

Licensed under either of
Expand Down
31 changes: 31 additions & 0 deletions examples/generic_input.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use nom::character::complete::space1;
use nom::combinator::map;
use nom::multi::separated_list1;
use nom::*;
use nom::error::{Error, ParseError};
use nom_parse_trait::{ParseFrom, ParseFromExt};

struct Numbers(Vec<u32>);

impl<I, E: ParseError<I>> ParseFrom<I, E> for Numbers
where
// From separated_list1
I: Clone + InputLength,
// From space1
I: InputTakeAtPosition,
<I as InputTakeAtPosition>::Item: AsChar + Clone,
// From u32::parse
I: InputIter + Slice<std::ops::RangeFrom<usize>> + InputLength,
<I as InputIter>::Item: AsChar,
{
fn parse(input: I) -> IResult<I, Self, E> {
map(separated_list1(space1, u32::parse), |v| Numbers(v)).parse(input)
}
}

fn main() {
let input = "1 2 3 4 5";
if let Ok::<_, Error<_>>(numbers) = Numbers::parse_complete(input) {
println!("Parsed \"{}\" into {:?}", input, numbers.0);
}
}
14 changes: 8 additions & 6 deletions examples/simple.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
use nom::character::complete::space1;
use nom::combinator::map;
use nom::error::Error;
use nom::multi::separated_list1;
use nom::{IResult, Parser};
use nom_parsable::Parsable;
use nom::{
IResult,
Parser,
};
use nom_parse_trait::{ParseFrom, ParseFromExt};

struct Numbers(Vec<u32>);

impl Parsable for Numbers {
fn parse(input: &str) -> IResult<&str, Self, Error<&str>> {
impl<'a> ParseFrom<&'a str> for Numbers {
fn parse(input: &'a str) -> IResult<&'a str, Self> {
map(separated_list1(space1, u32::parse), |v| Numbers(v)).parse(input)
}
}

fn main() {
let input = "1 2 3 4 5";
if let Ok( numbers) = Numbers::parse_complete(input) {
if let Ok(numbers) = Numbers::parse_complete(input) {
println!("Parsed \"{}\" into {:?}", input, numbers.0);
}
}
70 changes: 44 additions & 26 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use nom::*;
use std::ops::*;
use nom::error::ParseError;

/// #nom-parsable
///
Expand All @@ -16,7 +17,7 @@ where
}

/// An extension for the ParseFrom trait with extra functionality to make parse a bit easier.
trait ParseFromExt<I, E>
pub trait ParseFromExt<I, E>
where
Self: Sized,
{
Expand Down Expand Up @@ -45,46 +46,63 @@ where
}
}

macro_rules! parse_trait_impl {
($ty:ty => $function:path) => {
/// Default implementation of the ParseFrom trait for $ty.
macro_rules! unsigned_parsable {
($($ty:tt)+) => {
$(
impl<I, E: error::ParseError<I>> ParseFrom<I, E> for $ty
where
I: Clone
+ nom::AsBytes
+ nom::InputLength
+ nom::InputTake
+ nom::Offset
+ nom::Slice<std::ops::RangeFrom<usize>>
+ nom::Slice<std::ops::Range<usize>>
+ nom::Slice<std::ops::RangeTo<usize>>
+ for<'a> nom::Compare<&'a [u8]>
+ nom::InputIter
+ nom::InputTakeAtPosition,
<I as nom::InputIter>::Item: AsChar + Copy,
<I as nom::InputIter>::IterElem: Clone,
<I as nom::InputTakeAtPosition>::Item: AsChar + Clone,
I: InputIter + Slice<RangeFrom<usize>> + InputLength,
<I as InputIter>::Item: AsChar,
{
fn parse(input: I) -> nom::IResult<I, $ty, E> {
$function(input)
nom::character::complete::$ty(input)
}
}
};
)*
}
}

macro_rules! primitive_parsable {
unsigned_parsable!(u16 u32 u64 u128);

macro_rules! signed_parsable {
($($ty:tt)+) => {
$( parse_trait_impl!($ty => character::complete::$ty); )*
$(
impl<I, E: error::ParseError<I>> ParseFrom<I, E> for $ty
where
I: InputIter + Slice<RangeFrom<usize>> + InputLength + InputTake + Clone,
<I as InputIter>::Item: AsChar,
I: for <'a> Compare<&'a[u8]>,
{
fn parse(input: I) -> nom::IResult<I, $ty, E> {
nom::character::complete::$ty(input)
}
}
)*
}
}

primitive_parsable!(u16 u32 u64 u128);
primitive_parsable!(i16 i32 i64 i128);
signed_parsable!(i16 i32 i64 i128);

impl<I, E, T: ParseFrom<I, E>> ParseFrom<I, E> for Vec<T>
impl<I, E: ParseError<I>> ParseFrom<I, E> for bool
where
E: error::ParseError<I>,
// From alt
I: Clone,
// From tag
I: InputTake + Compare<&'static str>,
{
fn parse(input: I) -> IResult<I, Self, E> {
branch::alt((
combinator::value(true, bytes::complete::tag("true")),
combinator::value(false, bytes::complete::tag("false")),
)).parse(input)
}
}

impl<I, E: ParseError<I>, T: ParseFrom<I, E>> ParseFrom<I, E> for Vec<T>
where
// From separated_list0
I: Clone + InputLength,
// From line_ending
I: Slice<Range<usize>> + Slice<RangeFrom<usize>> + Slice<RangeTo<usize>>,
I: InputIter + InputLength,
I: Compare<&'static str>,
Expand Down

0 comments on commit 14d8b86

Please sign in to comment.