Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(error)!: Provide a simple, getting started error #282

Merged
merged 14 commits into from
Jul 7, 2023
Merged
6 changes: 3 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ MSRV was raised to 1.60 (#158, #160, #167)
- `satisfy` with `one_of` thanks to new `FindToken` impls (#44)
- `Parser::and`, `Parser::or` in favor of `impl Parser for <tuples>` and `alt` (#37)
- `tuple`, `pair` parser in favor of using tuples (#42, #46)
- `ParseError::from_char` in favor of `ContextError` (#45)
- `ParserError::from_char` in favor of `ContextError` (#45)
- `error::make_error` and `error::append_error` (#55)
- `error::Finish` in favor of `FinishIResult` (#21)
- `error::error_position!` and `error::error_mode_position!` (#92)
Expand Down Expand Up @@ -290,7 +290,7 @@ MSRV was raised to 1.60 (#158, #160, #167)
- Treat all slices as input, regardless of token type (#111, rust-bakery/nom#1482)
- `FinishIResult::finish` for dropping the `remainder` (#30)
- `FindSlice` now also finds tokens, making `take_until0` and friends more flexible (#105)
- Implement `ParseError` and `FromExternalError` for `ErrMode`, making it easier to create the right kind of error (#120, #124)
- Implement `ParserError` and `FromExternalError` for `ErrMode`, making it easier to create the right kind of error (#120, #124)

### Fixes

Expand Down Expand Up @@ -647,7 +647,7 @@ containing example patterns.
- the minimal Rust version is now 1.44 (1.37 if building without the `alloc` or `std` features)
- streaming parsers return the number of additional bytes they need, not the total. This was supposed to be the case everywhere, but some parsers were forgotten
- removed the `regexp_macros` cargo feature
- the `context` combinator is not linked to `ParseError` anymore, instead it come with its own `ContextError` trait
- the `context` combinator is not linked to `ParserError` anymore, instead it come with its own `ContextError` trait
- `Needed::Size` now contains a `NonZeroUsize`, so we can reduce the structure's size by 8 bytes. When upgrading, `Needed::Size(number)` can be replaced with `Needed::new(number)`
- there is now a more general `Parser` trait, so parsers can be something else than a function. This trait also comes with combinator methods like `map`, `flat_map`, `or`. Since it is implemented on `Fn*` traits, it should not affect existing code too much
- combinators that returned a `impl Fn` now return a `impl FnMut` to allow parser closures that capture some mutable value from the context
Expand Down
16 changes: 10 additions & 6 deletions benches/number.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ use criterion::Criterion;
use winnow::ascii::float;
use winnow::binary::be_u64;
use winnow::error::ErrMode;
use winnow::error::Error;
use winnow::error::ErrorKind;
use winnow::error::InputError;
use winnow::error::ParserError;
use winnow::prelude::*;
use winnow::stream::ParseSlice;

Expand All @@ -31,27 +32,30 @@ fn number(c: &mut Criterion) {
fn float_bytes(c: &mut Criterion) {
println!(
"float_bytes result: {:?}",
float::<_, f64, Error<_>>.parse_peek(&b"-1.234E-12"[..])
float::<_, f64, InputError<_>>.parse_peek(&b"-1.234E-12"[..])
);
c.bench_function("float bytes", |b| {
b.iter(|| float::<_, f64, Error<_>>.parse_peek(&b"-1.234E-12"[..]));
b.iter(|| float::<_, f64, InputError<_>>.parse_peek(&b"-1.234E-12"[..]));
});
}

fn float_str(c: &mut Criterion) {
println!(
"float_str result: {:?}",
float::<_, f64, Error<_>>.parse_peek("-1.234E-12")
float::<_, f64, InputError<_>>.parse_peek("-1.234E-12")
);
c.bench_function("float str", |b| {
b.iter(|| float::<_, f64, Error<_>>.parse_peek("-1.234E-12"));
b.iter(|| float::<_, f64, InputError<_>>.parse_peek("-1.234E-12"));
});
}

fn std_float(input: &mut &[u8]) -> PResult<f64> {
match input.parse_slice() {
Some(n) => Ok(n),
None => Err(ErrMode::Backtrack(ErrorKind::Slice)),
None => Err(ErrMode::from_error_kind(
<&[u8]>::clone(input),
ErrorKind::Slice,
)),
}
}

Expand Down
4 changes: 2 additions & 2 deletions examples/css/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ pub struct Color {

impl std::str::FromStr for Color {
// The error must be owned
type Err = winnow::error::ErrorKind;
type Err = String;

fn from_str(s: &str) -> Result<Self, Self::Err> {
hex_color.parse(s)
hex_color.parse(s).map_err(|e| e.to_string())
}
}

Expand Down
4 changes: 2 additions & 2 deletions examples/custom_error.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use winnow::error::ErrMode;
use winnow::error::ErrorKind;
use winnow::error::ParseError;
use winnow::error::ParserError;
use winnow::prelude::*;

#[derive(Debug, PartialEq, Eq)]
Expand All @@ -9,7 +9,7 @@ pub enum CustomError<I> {
Nom(I, ErrorKind),
}

impl<I> ParseError<I> for CustomError<I> {
impl<I> ParserError<I> for CustomError<I> {
fn from_error_kind(input: I, kind: ErrorKind) -> Self {
CustomError::Nom(input, kind)
}
Expand Down
10 changes: 5 additions & 5 deletions examples/json/bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ fn json_bench(c: &mut criterion::Criterion) {
group.throughput(criterion::Throughput::Bytes(len as u64));

group.bench_with_input(criterion::BenchmarkId::new("basic", name), &len, |b, _| {
type Error<'i> = winnow::error::Error<parser::Stream<'i>>;
type Error<'i> = winnow::error::InputError<parser::Stream<'i>>;

b.iter(|| parser::json::<Error>.parse_peek(sample).unwrap());
});
Expand All @@ -24,10 +24,10 @@ fn json_bench(c: &mut criterion::Criterion) {
b.iter(|| parser::json::<Error>.parse_peek(sample).unwrap());
});
group.bench_with_input(
criterion::BenchmarkId::new("verbose", name),
criterion::BenchmarkId::new("context", name),
&len,
|b, _| {
type Error<'i> = winnow::error::VerboseError<parser::Stream<'i>>;
type Error<'i> = winnow::error::ContextError<parser::Stream<'i>>;

b.iter(|| parser::json::<Error>.parse_peek(sample).unwrap());
},
Expand All @@ -36,7 +36,7 @@ fn json_bench(c: &mut criterion::Criterion) {
criterion::BenchmarkId::new("dispatch", name),
&len,
|b, _| {
type Error<'i> = winnow::error::Error<parser_dispatch::Stream<'i>>;
type Error<'i> = winnow::error::InputError<parser_dispatch::Stream<'i>>;

b.iter(|| parser_dispatch::json::<Error>.parse_peek(sample).unwrap());
},
Expand All @@ -45,7 +45,7 @@ fn json_bench(c: &mut criterion::Criterion) {
criterion::BenchmarkId::new("streaming", name),
&len,
|b, _| {
type Error<'i> = winnow::error::Error<parser_partial::Stream<'i>>;
type Error<'i> = winnow::error::InputError<parser_partial::Stream<'i>>;

b.iter(|| {
parser_partial::json::<Error>
Expand Down
46 changes: 13 additions & 33 deletions examples/json/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ mod parser_dispatch;
#[allow(dead_code)]
mod parser_partial;

use winnow::error::convert_error;
use winnow::error::Error;
use winnow::error::VerboseError;
use winnow::error::ErrorKind;
use winnow::prelude::*;

fn main() -> Result<(), lexopt::Error> {
Expand All @@ -26,30 +24,19 @@ fn main() -> Result<(), lexopt::Error> {
} "
});

if args.verbose {
match parser::json::<VerboseError<&str>>.parse(data) {
Ok(json) => {
println!("{:#?}", json);
}
Err(err) => {
if args.pretty {
println!("{}", convert_error(data, err));
} else {
println!("{:#?}", err);
}
}
let result = match args.implementation {
Impl::Naive => parser::json::<ErrorKind>.parse(data),
Impl::Dispatch => parser_dispatch::json::<ErrorKind>.parse(data),
};
match result {
Ok(json) => {
println!("{:#?}", json);
}
} else {
let result = match args.implementation {
Impl::Naive => parser::json::<Error<&str>>.parse(data),
Impl::Dispatch => parser_dispatch::json::<Error<&str>>.parse(data),
};
match result {
Ok(json) => {
println!("{:#?}", json);
}
Err(err) => {
println!("{:?}", err);
Err(err) => {
if args.pretty {
println!("{}", err);
} else {
println!("{:#?}", err);
}
}
}
Expand All @@ -61,7 +48,6 @@ fn main() -> Result<(), lexopt::Error> {
struct Args {
input: Option<String>,
invalid: bool,
verbose: bool,
pretty: bool,
implementation: Impl,
}
Expand Down Expand Up @@ -89,13 +75,7 @@ impl Args {
Long("invalid") => {
res.invalid = true;
}
Long("verbose") => {
res.verbose = true;
// Only case where verbose matters
res.invalid = true;
}
Long("pretty") => {
res.verbose = true;
// Only case where pretty matters
res.pretty = true;
res.invalid = true;
Expand Down
30 changes: 15 additions & 15 deletions examples/json/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use winnow::{
combinator::cut_err,
combinator::{delimited, preceded, separated_pair, terminated},
combinator::{fold_repeat, separated0},
error::{ContextError, ParseError},
error::{AddContext, ParserError},
token::{any, none_of, take, take_while},
};

Expand All @@ -19,7 +19,7 @@ pub type Stream<'i> = &'i str;
/// The root element of a JSON parser is any value
///
/// A parser has the following signature:
/// `&mut Stream -> PResult<Output, Error>`, with `PResult` defined as:
/// `&mut Stream -> PResult<Output, InputError>`, with `PResult` defined as:
/// `type PResult<O, E = (I, ErrorKind)> = Result<O, Err<E>>;`
///
/// most of the times you can ignore the error type and use the default (but this
Expand All @@ -28,15 +28,15 @@ pub type Stream<'i> = &'i str;
/// Here we use `&str` as input type, but parsers can be generic over
/// the input type, work directly with `&[u8]`, or any other type that
/// implements the required traits.
pub fn json<'i, E: ParseError<Stream<'i>> + ContextError<Stream<'i>, &'static str>>(
pub fn json<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, &'static str>>(
input: &mut Stream<'i>,
) -> PResult<JsonValue, E> {
delimited(ws, json_value, ws).parse_next(input)
}

/// `alt` is a combinator that tries multiple parsers one by one, until
/// one of them succeeds
fn json_value<'i, E: ParseError<Stream<'i>> + ContextError<Stream<'i>, &'static str>>(
fn json_value<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, &'static str>>(
input: &mut Stream<'i>,
) -> PResult<JsonValue, E> {
// `alt` combines the each value parser. It returns the result of the first
Expand All @@ -55,15 +55,15 @@ fn json_value<'i, E: ParseError<Stream<'i>> + ContextError<Stream<'i>, &'static
/// `tag(string)` generates a parser that recognizes the argument string.
///
/// This also shows returning a sub-slice of the original input
fn null<'i, E: ParseError<Stream<'i>>>(input: &mut Stream<'i>) -> PResult<&'i str, E> {
fn null<'i, E: ParserError<Stream<'i>>>(input: &mut Stream<'i>) -> PResult<&'i str, E> {
// This is a parser that returns `"null"` if it sees the string "null", and
// an error otherwise
"null".parse_next(input)
}

/// We can combine `tag` with other functions, like `value` which returns a given constant value on
/// success.
fn boolean<'i, E: ParseError<Stream<'i>>>(input: &mut Stream<'i>) -> PResult<bool, E> {
fn boolean<'i, E: ParserError<Stream<'i>>>(input: &mut Stream<'i>) -> PResult<bool, E> {
// This is a parser that returns `true` if it sees the string "true", and
// an error otherwise
let parse_true = "true".value(true);
Expand All @@ -77,7 +77,7 @@ fn boolean<'i, E: ParseError<Stream<'i>>>(input: &mut Stream<'i>) -> PResult<boo

/// This parser gathers all `char`s up into a `String`with a parse to recognize the double quote
/// character, before the string (using `preceded`) and after the string (using `terminated`).
fn string<'i, E: ParseError<Stream<'i>> + ContextError<Stream<'i>, &'static str>>(
fn string<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, &'static str>>(
input: &mut Stream<'i>,
) -> PResult<String, E> {
preceded(
Expand All @@ -102,7 +102,7 @@ fn string<'i, E: ParseError<Stream<'i>> + ContextError<Stream<'i>, &'static str>

/// You can mix the above declarative parsing with an imperative style to handle more unique cases,
/// like escaping
fn character<'i, E: ParseError<Stream<'i>>>(input: &mut Stream<'i>) -> PResult<char, E> {
fn character<'i, E: ParserError<Stream<'i>>>(input: &mut Stream<'i>) -> PResult<char, E> {
let c = none_of('\"').parse_next(input)?;
if c == '\\' {
alt((
Expand All @@ -125,7 +125,7 @@ fn character<'i, E: ParseError<Stream<'i>>>(input: &mut Stream<'i>) -> PResult<c
}
}

fn unicode_escape<'i, E: ParseError<Stream<'i>>>(input: &mut Stream<'i>) -> PResult<char, E> {
fn unicode_escape<'i, E: ParserError<Stream<'i>>>(input: &mut Stream<'i>) -> PResult<char, E> {
alt((
// Not a surrogate
u16_hex
Expand All @@ -147,7 +147,7 @@ fn unicode_escape<'i, E: ParseError<Stream<'i>>>(input: &mut Stream<'i>) -> PRes
.parse_next(input)
}

fn u16_hex<'i, E: ParseError<Stream<'i>>>(input: &mut Stream<'i>) -> PResult<u16, E> {
fn u16_hex<'i, E: ParserError<Stream<'i>>>(input: &mut Stream<'i>) -> PResult<u16, E> {
take(4usize)
.verify_map(|s| u16::from_str_radix(s, 16).ok())
.parse_next(input)
Expand All @@ -157,7 +157,7 @@ fn u16_hex<'i, E: ParseError<Stream<'i>>>(input: &mut Stream<'i>) -> PResult<u16
/// accumulating results in a `Vec`, until it encounters an error.
/// If you want more control on the parser application, check out the `iterator`
/// combinator (cf `examples/iterator.rs`)
fn array<'i, E: ParseError<Stream<'i>> + ContextError<Stream<'i>, &'static str>>(
fn array<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, &'static str>>(
input: &mut Stream<'i>,
) -> PResult<Vec<JsonValue>, E> {
preceded(
Expand All @@ -168,7 +168,7 @@ fn array<'i, E: ParseError<Stream<'i>> + ContextError<Stream<'i>, &'static str>>
.parse_next(input)
}

fn object<'i, E: ParseError<Stream<'i>> + ContextError<Stream<'i>, &'static str>>(
fn object<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, &'static str>>(
input: &mut Stream<'i>,
) -> PResult<HashMap<String, JsonValue>, E> {
preceded(
Expand All @@ -179,7 +179,7 @@ fn object<'i, E: ParseError<Stream<'i>> + ContextError<Stream<'i>, &'static str>
.parse_next(input)
}

fn key_value<'i, E: ParseError<Stream<'i>> + ContextError<Stream<'i>, &'static str>>(
fn key_value<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, &'static str>>(
input: &mut Stream<'i>,
) -> PResult<(String, JsonValue), E> {
separated_pair(string, cut_err((ws, ':', ws)), json_value).parse_next(input)
Expand All @@ -188,7 +188,7 @@ fn key_value<'i, E: ParseError<Stream<'i>> + ContextError<Stream<'i>, &'static s
/// Parser combinators are constructed from the bottom up:
/// first we write parsers for the smallest elements (here a space character),
/// then we'll combine them in larger parsers
fn ws<'i, E: ParseError<Stream<'i>>>(input: &mut Stream<'i>) -> PResult<&'i str, E> {
fn ws<'i, E: ParserError<Stream<'i>>>(input: &mut Stream<'i>) -> PResult<&'i str, E> {
// Combinators like `take_while` return a function. That function is the
// parser,to which we can pass the input
take_while(0.., WS).parse_next(input)
Expand All @@ -204,7 +204,7 @@ mod test {

#[allow(clippy::useless_attribute)]
#[allow(dead_code)] // its dead for benches
type Error<'i> = winnow::error::Error<&'i str>;
type Error<'i> = winnow::error::InputError<&'i str>;

#[test]
fn json_string() {
Expand Down