feat: add display UI with splash screen, dashboard, and RSSI sparkline
Implement full SSD1306 OLED display support: - Splash screen on boot (1.5s) with project name, version, board - Dashboard showing radio state, config, counters, RSSI/SNR - RSSI history sparkline (64-sample ring buffer, 2px-wide bars) - DisplayOn/DisplayOff host commands routed via dedicated channel - Pure render functions against generic DrawTarget (no hardware coupling) - Display on/off control bypasses radio task entirely Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
17624 files changed
+4564928
-38
No files matched your search
+10
@@ -0,0 +1,10 @@
|
||||
//! # Arithmetic
|
||||
//!
|
||||
//! This parses arithmetic expressions and directly evaluates them.
|
||||
//!
|
||||
//! See also [C-style Expressions][crate::_topic::language#c-style-expressions],
|
||||
//! for a more complex example.
|
||||
//!
|
||||
//! ```rust
|
||||
#![doc = include_str!("../../examples/arithmetic/parser.rs")]
|
||||
//! ```
|
||||
Vendored
+44
@@ -0,0 +1,44 @@
|
||||
//! # Custom Errors
|
||||
//!
|
||||
//! A lot can be accomplished with the built-in error tools, like:
|
||||
//! - [`ContextError`]
|
||||
//! - [`Parser::context`]
|
||||
//! - [`cut_err`]
|
||||
//!
|
||||
//! *(see [tutorial][chapter_7])*
|
||||
//!
|
||||
//! Most other needs can likely be met by using a custom context type with [`ContextError`] instead
|
||||
//! of [`StrContext`].
|
||||
//! This will require implementing a custom renderer.
|
||||
//!
|
||||
//! ## `ParserError` Trait
|
||||
//!
|
||||
//! When needed, you can also create your own type that implements [`ParserError`].
|
||||
//!
|
||||
//! Optional traits include:
|
||||
//! - [`AddContext`]
|
||||
//! - [`FromExternalError`]
|
||||
//! - [`ErrorConvert`]
|
||||
//!
|
||||
//! There are multiple strategies for implementing support for [`AddContext`] and [`FromExternalError`]:
|
||||
//! - Make your error type generic over the context or external error
|
||||
//! - Require a trait for the context or external error and `Box` it
|
||||
//! - Make the context an enum like [`StrContext`]
|
||||
//! - Implement the trait multiple times, one for each concrete context or external error type,
|
||||
//! allowing custom behavior per type
|
||||
//!
|
||||
//! Example:
|
||||
//!```rust
|
||||
#![doc = include_str!("../../examples/custom_error.rs")]
|
||||
//!```
|
||||
|
||||
#![allow(unused_imports)]
|
||||
use crate::combinator::cut_err;
|
||||
use crate::error::ContextError;
|
||||
use crate::error::ErrorConvert;
|
||||
use crate::error::StrContext;
|
||||
use crate::Parser;
|
||||
use crate::_tutorial::chapter_7;
|
||||
use crate::error::AddContext;
|
||||
use crate::error::FromExternalError;
|
||||
use crate::error::ParserError;
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
//! # Implementing `FromStr`
|
||||
//!
|
||||
//! The [`FromStr` trait][std::str::FromStr] provides
|
||||
//! a common interface to parse from a string.
|
||||
//!
|
||||
//! ```rust
|
||||
#![doc = include_str!("../../examples/css/parser.rs")]
|
||||
//! ```
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
//! # HTTP
|
||||
//!
|
||||
//! ```rust
|
||||
#![doc = include_str!("../../examples/http/parser.rs")]
|
||||
//! ```
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
//! # INI
|
||||
//!
|
||||
//! ```rust
|
||||
#![doc = include_str!("../../examples/ini/parser.rs")]
|
||||
//! ```
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
//! # json
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
#![doc = include_str!("../../examples/json/parser_dispatch.rs")]
|
||||
//! ```
|
||||
Vendored
+341
@@ -0,0 +1,341 @@
|
||||
//! # Elements of Programming Languages
|
||||
//!
|
||||
//! These are short recipes for accomplishing common tasks.
|
||||
//!
|
||||
//! * [Whitespace](#whitespace)
|
||||
//! + [Wrapper combinators that eat whitespace before and after a parser](#wrapper-combinators-that-eat-whitespace-before-and-after-a-parser)
|
||||
//! * [Comments](#comments)
|
||||
//! + [`// C++/EOL-style comments`](#-ceol-style-comments)
|
||||
//! + [`/* C-style comments */`](#-c-style-comments-)
|
||||
//! * [Identifiers](#identifiers)
|
||||
//! + [`Rust-Style Identifiers`](#rust-style-identifiers)
|
||||
//! * [Literal Values](#literal-values)
|
||||
//! + [Escaped Strings](#escaped-strings)
|
||||
//! + [Integers](#integers)
|
||||
//! - [Hexadecimal](#hexadecimal)
|
||||
//! - [Octal](#octal)
|
||||
//! - [Binary](#binary)
|
||||
//! - [Decimal](#decimal)
|
||||
//! + [Floating Point Numbers](#floating-point-numbers)
|
||||
//! * [C-style Expressions](#c-style-expressions)
|
||||
//!
|
||||
//! ## Whitespace
|
||||
//!
|
||||
//!
|
||||
//!
|
||||
//! ### Wrapper combinators that eat whitespace before and after a parser
|
||||
//!
|
||||
//! ```rust
|
||||
//! use winnow::prelude::*;
|
||||
//! use winnow::{
|
||||
//! error::ParserError,
|
||||
//! combinator::delimited,
|
||||
//! ascii::multispace0,
|
||||
//! };
|
||||
//!
|
||||
//! /// A combinator that takes a parser `inner` and produces a parser that also consumes both leading and
|
||||
//! /// trailing whitespace, returning the output of `inner`.
|
||||
//! fn ws<'a, F, O, E: ParserError<&'a str>>(inner: F) -> impl Parser<&'a str, O, E>
|
||||
//! where
|
||||
//! F: Parser<&'a str, O, E>,
|
||||
//! {
|
||||
//! delimited(
|
||||
//! multispace0,
|
||||
//! inner,
|
||||
//! multispace0
|
||||
//! )
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! To eat only trailing whitespace, replace `delimited(...)` with `terminated(&inner, multispace0)`.
|
||||
//! Likewise, the eat only leading whitespace, replace `delimited(...)` with `preceded(multispace0,
|
||||
//! &inner)`. You can use your own parser instead of `multispace0` if you want to skip a different set
|
||||
//! of lexemes.
|
||||
//!
|
||||
//! ## Comments
|
||||
//!
|
||||
//! ### `// C++/EOL-style comments`
|
||||
//!
|
||||
//! This version uses `%` to start a comment, does not consume the newline character, and returns an
|
||||
//! output of `()`.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use winnow::prelude::*;
|
||||
//! use winnow::{
|
||||
//! error::ParserError,
|
||||
//! token::take_till,
|
||||
//! };
|
||||
//!
|
||||
//! pub fn peol_comment<'a, E: ParserError<&'a str>>(i: &mut &'a str) -> ModalResult<(), E>
|
||||
//! {
|
||||
//! ('%', take_till(1.., ['\n', '\r']))
|
||||
//! .void() // Output is thrown away.
|
||||
//! .parse_next(i)
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ### `/* C-style comments */`
|
||||
//!
|
||||
//! Inline comments surrounded with sentinel literals `(*` and `*)`. This version returns an output of `()`
|
||||
//! and does not handle nested comments.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use winnow::prelude::*;
|
||||
//! use winnow::{
|
||||
//! error::ParserError,
|
||||
//! token::take_until,
|
||||
//! };
|
||||
//!
|
||||
//! pub fn pinline_comment<'a, E: ParserError<&'a str>>(i: &mut &'a str) -> ModalResult<(), E> {
|
||||
//! (
|
||||
//! "(*",
|
||||
//! take_until(0.., "*)"),
|
||||
//! "*)"
|
||||
//! )
|
||||
//! .void() // Output is thrown away.
|
||||
//! .parse_next(i)
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Identifiers
|
||||
//!
|
||||
//! ### `Rust-Style Identifiers`
|
||||
//!
|
||||
//! Parsing identifiers that may start with a letter (or underscore) and may contain underscores,
|
||||
//! letters and numbers may be parsed like this:
|
||||
//!
|
||||
//! ```rust
|
||||
//! use winnow::prelude::*;
|
||||
//! use winnow::{
|
||||
//! stream::AsChar,
|
||||
//! token::take_while,
|
||||
//! token::one_of,
|
||||
//! };
|
||||
//!
|
||||
//! pub fn identifier<'s>(input: &mut &'s str) -> ModalResult<&'s str> {
|
||||
//! (
|
||||
//! one_of(|c: char| c.is_alpha() || c == '_'),
|
||||
//! take_while(0.., |c: char| c.is_alphanum() || c == '_')
|
||||
//! )
|
||||
//! .take()
|
||||
//! .parse_next(input)
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! Let's say we apply this to the identifier `hello_world123abc`. The first element of the tuple
|
||||
//! would uses [`one_of`][crate::token::one_of] which would take `h`. The tuple ensures that
|
||||
//! `ello_world123abc` will be piped to the next [`take_while`][crate::token::take_while] parser,
|
||||
//! which takes every remaining character. However, the tuple returns a tuple of the results
|
||||
//! of its sub-parsers. The [`take`][crate::Parser::take] parser produces a `&str` of the
|
||||
//! input text that was parsed, which in this case is the entire `&str` `hello_world123abc`.
|
||||
//!
|
||||
//! ## Literal Values
|
||||
//!
|
||||
//! ### Escaped Strings
|
||||
//!
|
||||
//! ```rust
|
||||
#![doc = include_str!("../../examples/string/parser.rs")]
|
||||
//! ```
|
||||
//!
|
||||
//! See also [`take_escaped`] and [`escaped`].
|
||||
//!
|
||||
//! ### Integers
|
||||
//!
|
||||
//! The following recipes all return string slices rather than integer values. How to obtain an
|
||||
//! integer value instead is demonstrated for hexadecimal integers. The others are similar.
|
||||
//!
|
||||
//! The parsers allow the grouping character `_`, which allows one to group the digits by byte, for
|
||||
//! example: `0xA4_3F_11_28`. If you prefer to exclude the `_` character, the lambda to convert from a
|
||||
//! string slice to an integer value is slightly simpler. You can also strip the `_` from the string
|
||||
//! slice that is returned, which is demonstrated in the second hexadecimal number parser.
|
||||
//!
|
||||
//! #### Hexadecimal
|
||||
//!
|
||||
//! The parser outputs the string slice of the digits without the leading `0x`/`0X`.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use winnow::prelude::*;
|
||||
//! use winnow::{
|
||||
//! combinator::alt,
|
||||
//! combinator::repeat,
|
||||
//! combinator::{preceded, terminated},
|
||||
//! token::one_of,
|
||||
//! };
|
||||
//!
|
||||
//! fn hexadecimal<'s>(input: &mut &'s str) -> ModalResult<&'s str> { // <'a, E: ParserError<&'a str>>
|
||||
//! preceded(
|
||||
//! alt(("0x", "0X")),
|
||||
//! repeat(1..,
|
||||
//! terminated(one_of(('0'..='9', 'a'..='f', 'A'..='F')), repeat(0.., '_').map(|()| ()))
|
||||
//! ).map(|()| ()).take()
|
||||
//! ).parse_next(input)
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! If you want it to return the integer value instead, use map:
|
||||
//!
|
||||
//! ```rust
|
||||
//! use winnow::prelude::*;
|
||||
//! use winnow::{
|
||||
//! combinator::alt,
|
||||
//! combinator::repeat,
|
||||
//! combinator::{preceded, terminated},
|
||||
//! token::one_of,
|
||||
//! };
|
||||
//!
|
||||
//! fn hexadecimal_value(input: &mut &str) -> ModalResult<i64> {
|
||||
//! preceded(
|
||||
//! alt(("0x", "0X")),
|
||||
//! repeat(1..,
|
||||
//! terminated(one_of(('0'..='9', 'a'..='f', 'A'..='F')), repeat(0.., '_').map(|()| ()))
|
||||
//! ).map(|()| ()).take()
|
||||
//! ).try_map(
|
||||
//! |out: &str| i64::from_str_radix(&str::replace(&out, "_", ""), 16)
|
||||
//! ).parse_next(input)
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! See also [`hex_uint`]
|
||||
//!
|
||||
//! #### Octal
|
||||
//!
|
||||
//! ```rust
|
||||
//! use winnow::prelude::*;
|
||||
//! use winnow::{
|
||||
//! combinator::alt,
|
||||
//! combinator::repeat,
|
||||
//! combinator::{preceded, terminated},
|
||||
//! token::one_of,
|
||||
//! };
|
||||
//!
|
||||
//! fn octal<'s>(input: &mut &'s str) -> ModalResult<&'s str> {
|
||||
//! preceded(
|
||||
//! alt(("0o", "0O")),
|
||||
//! repeat(1..,
|
||||
//! terminated(one_of('0'..='7'), repeat(0.., '_').map(|()| ()))
|
||||
//! ).map(|()| ()).take()
|
||||
//! ).parse_next(input)
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! #### Binary
|
||||
//!
|
||||
//! ```rust
|
||||
//! use winnow::prelude::*;
|
||||
//! use winnow::{
|
||||
//! combinator::alt,
|
||||
//! combinator::repeat,
|
||||
//! combinator::{preceded, terminated},
|
||||
//! token::one_of,
|
||||
//! };
|
||||
//!
|
||||
//! fn binary<'s>(input: &mut &'s str) -> ModalResult<&'s str> {
|
||||
//! preceded(
|
||||
//! alt(("0b", "0B")),
|
||||
//! repeat(1..,
|
||||
//! terminated(one_of('0'..='1'), repeat(0.., '_').map(|()| ()))
|
||||
//! ).map(|()| ()).take()
|
||||
//! ).parse_next(input)
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! #### Decimal
|
||||
//!
|
||||
//! ```rust
|
||||
//! use winnow::prelude::*;
|
||||
//! use winnow::{
|
||||
//! combinator::repeat,
|
||||
//! combinator::terminated,
|
||||
//! token::one_of,
|
||||
//! };
|
||||
//!
|
||||
//! fn decimal<'s>(input: &mut &'s str) -> ModalResult<&'s str> {
|
||||
//! repeat(1..,
|
||||
//! terminated(one_of('0'..='9'), repeat(0.., '_').map(|()| ()))
|
||||
//! ).map(|()| ())
|
||||
//! .take()
|
||||
//! .parse_next(input)
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! See also [`dec_uint`] and [`dec_int`]
|
||||
//!
|
||||
//! ### Floating Point Numbers
|
||||
//!
|
||||
//! The following is adapted from [the Python parser by Valentin Lorentz](https://github.com/ProgVal/rust-python-parser/blob/master/src/numbers.rs).
|
||||
//!
|
||||
//! ```rust
|
||||
//! use winnow::prelude::*;
|
||||
//! use winnow::{
|
||||
//! combinator::alt,
|
||||
//! combinator::repeat,
|
||||
//! combinator::opt,
|
||||
//! combinator::{preceded, terminated},
|
||||
//! token::one_of,
|
||||
//! };
|
||||
//!
|
||||
//! fn float<'s>(input: &mut &'s str) -> ModalResult<&'s str> {
|
||||
//! alt((
|
||||
//! // Case one: .42
|
||||
//! (
|
||||
//! '.',
|
||||
//! decimal,
|
||||
//! opt((
|
||||
//! one_of(['e', 'E']),
|
||||
//! opt(one_of(['+', '-'])),
|
||||
//! decimal
|
||||
//! ))
|
||||
//! ).take()
|
||||
//! , // Case two: 42e42 and 42.42e42
|
||||
//! (
|
||||
//! decimal,
|
||||
//! opt(preceded(
|
||||
//! '.',
|
||||
//! decimal,
|
||||
//! )),
|
||||
//! one_of(['e', 'E']),
|
||||
//! opt(one_of(['+', '-'])),
|
||||
//! decimal
|
||||
//! ).take()
|
||||
//! , // Case three: 42. and 42.42
|
||||
//! (
|
||||
//! decimal,
|
||||
//! '.',
|
||||
//! opt(decimal)
|
||||
//! ).take()
|
||||
//! )).parse_next(input)
|
||||
//! }
|
||||
//!
|
||||
//! fn decimal<'s>(input: &mut &'s str) -> ModalResult<&'s str> {
|
||||
//! repeat(1..,
|
||||
//! terminated(one_of('0'..='9'), repeat(0.., '_').map(|()| ()))
|
||||
//! ).
|
||||
//! map(|()| ())
|
||||
//! .take()
|
||||
//! .parse_next(input)
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! See also [`float`]
|
||||
//!
|
||||
//! ## C-style Expressions
|
||||
//!
|
||||
//! An example using the [`expression()`] parser to build an abstract syntax tree
|
||||
//! for C-style expressions.
|
||||
//!
|
||||
//! The operator precedence level is based on the [C language][c-precedence].
|
||||
//!
|
||||
//! [c-precedence]: https://en.cppreference.com/w/c/language/operator_precedence.html
|
||||
//!
|
||||
//! ```rust
|
||||
#![doc = include_str!("../../examples/c_expression/parser.rs")]
|
||||
//! ```
|
||||
|
||||
#![allow(unused_imports)]
|
||||
use crate::ascii::dec_int;
|
||||
use crate::ascii::dec_uint;
|
||||
use crate::ascii::escaped;
|
||||
use crate::ascii::float;
|
||||
use crate::ascii::hex_uint;
|
||||
use crate::ascii::take_escaped;
|
||||
use crate::combinator::expression;
|
||||
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
//! # Lexing and Parsing
|
||||
//!
|
||||
//! ## Parse to AST
|
||||
//!
|
||||
//! The simplest way to write a parser is to parse directly to the AST.
|
||||
//!
|
||||
//! Example:
|
||||
//! ```rust
|
||||
#![doc = include_str!("../../examples/arithmetic/parser_ast.rs")]
|
||||
//! ```
|
||||
//!
|
||||
//! ## Lexing
|
||||
//!
|
||||
//! However, there are times when you may want to separate lexing from parsing.
|
||||
//! Winnow provides [`TokenSlice`] to simplify this.
|
||||
//!
|
||||
//! Example:
|
||||
//! ```rust
|
||||
#![doc = include_str!("../../examples/arithmetic/parser_lexer.rs")]
|
||||
//! ```
|
||||
|
||||
#![allow(unused_imports)]
|
||||
use crate::stream::TokenSlice;
|
||||
Vendored
+43
@@ -0,0 +1,43 @@
|
||||
//! # Special Topics
|
||||
//!
|
||||
//! These are short recipes for accomplishing common tasks.
|
||||
//!
|
||||
//! - [Why `winnow`?][why]
|
||||
//! - [For `nom` users][nom]
|
||||
//! - Formats:
|
||||
//! - [Elements of Programming Languages][language]
|
||||
//! - [Arithmetic][arithmetic]
|
||||
//! - [s-expression][s_expression]
|
||||
//! - [json]
|
||||
//! - [INI][ini]
|
||||
//! - [HTTP][http]
|
||||
//! - Special Topics:
|
||||
//! - [Implementing `FromStr`][fromstr]
|
||||
//! - [Performance][performance]
|
||||
//! - [Parsing Partial Input][partial]
|
||||
//! - [Lexing and Parsing][lexing]
|
||||
//! - [Custom stream or token][stream]
|
||||
//! - [Custom errors][error]
|
||||
//! - [Debugging][crate::_tutorial::chapter_8]
|
||||
//!
|
||||
//! See also parsers written with `winnow`:
|
||||
//!
|
||||
//! - [`toml_edit`](https://crates.io/crates/toml_edit)
|
||||
//! - [`hcl-edit`](https://crates.io/crates/hcl-edit)
|
||||
#![allow(clippy::std_instead_of_core)]
|
||||
#![allow(clippy::test_attr_in_doctest)]
|
||||
|
||||
pub mod arithmetic;
|
||||
pub mod error;
|
||||
pub mod fromstr;
|
||||
pub mod http;
|
||||
pub mod ini;
|
||||
pub mod json;
|
||||
pub mod language;
|
||||
pub mod lexing;
|
||||
pub mod nom;
|
||||
pub mod partial;
|
||||
pub mod performance;
|
||||
pub mod s_expression;
|
||||
pub mod stream;
|
||||
pub mod why;
|
||||
Vendored
+240
@@ -0,0 +1,240 @@
|
||||
//! # For `nom` users
|
||||
//!
|
||||
//! ## Migrating from `nom`
|
||||
//!
|
||||
//! For comparisons with `nom`, see
|
||||
//! - [Why `winnow`][super::why]
|
||||
//! - [parse-rosetta-rs](https://github.com/rosetta-rs/parse-rosetta-rs/)
|
||||
//!
|
||||
//! What approach you take depends on the size and complexity of your parser.
|
||||
//! For small, simple parsers, its likely easiest to directly port from `nom`.
|
||||
//! When trying to look for the equivalent of a `nom` combinator, search in the docs for the name
|
||||
//! of the `nom` combinator. It is expected that, where names diverge, a doc alias exists.
|
||||
//! See also the [List of combinators][crate::combinator].
|
||||
//!
|
||||
//! ### Complex migrations
|
||||
//!
|
||||
//! For larger parsers, it is likely best to take smaller steps
|
||||
//! - Easier to debug when something goes wrong
|
||||
//! - Deprecation messages will help assist through the process
|
||||
//!
|
||||
//! The workflow goes something like:
|
||||
//! 1. Run `cargo rm nom && cargo add winnow@0.3`
|
||||
//! 1. Ensure everything compiles and tests pass, ignoring deprecation messages (see [migration
|
||||
//! notes](https://github.com/winnow-rs/winnow/blob/main/CHANGELOG.md#nom-migration-guide))
|
||||
//! 1. Commit
|
||||
//! 1. Switch any `impl FnMut(I) -> IResult<I, O, E>` to `impl Parser<I, O, E>`
|
||||
//! 1. Resolve deprecation messages
|
||||
//! 1. Commit
|
||||
//! 1. Run `cargo add winnow@0.4`
|
||||
//! 1. Ensure everything compiles and tests pass, ignoring deprecation messages (see [changelog](https://github.com/winnow-rs/winnow/blob/main/CHANGELOG.md#040---2023-03-18) for more details)
|
||||
//! 1. Commit
|
||||
//! 1. Resolve deprecation messages
|
||||
//! 1. Commit
|
||||
//! 1. Run `cargo add winnow@0.5`
|
||||
//! 1. Ensure everything compiles and tests pass, ignoring deprecation messages
|
||||
//! (see [migration notes](https://github.com/winnow-rs/winnow/blob/main/CHANGELOG.md#050---2023-07-13))
|
||||
//! 1. Commit
|
||||
//! 1. Resolve deprecation messages
|
||||
//! 1. Commit
|
||||
//!
|
||||
//! ### Examples
|
||||
//!
|
||||
//! For example migrations, see
|
||||
//! - [git-config-env](https://github.com/gitext-rs/git-config-env/pull/11) (nom to winnow 0.3)
|
||||
//! - [git-conventional](https://github.com/crate-ci/git-conventional/pull/37) (nom to winnow 0.3,
|
||||
//! adds explicit tracing for easier debugging)
|
||||
//! - [typos](https://github.com/crate-ci/typos/pull/664) (nom to winnow 0.3)
|
||||
//! - [cargo-smart-release](https://github.com/Byron/gitoxide/pull/948) (gradual migration from nom
|
||||
//! to winnow 0.5)
|
||||
//! - [gix-config](https://github.com/Byron/gitoxide/pull/951) (gradual migration from nom
|
||||
//! to winnow 0.5)
|
||||
//! - [gix-protocol](https://github.com/Byron/gitoxide/pull/1009) (gradual migration from nom
|
||||
//! to winnow 0.5)
|
||||
//! - [gitoxide](https://github.com/Byron/gitoxide/pull/956) (gradual migration from nom
|
||||
//! to winnow 0.5)
|
||||
//!
|
||||
//! ## Differences
|
||||
//!
|
||||
//! These are key differences to help Nom users adapt to writing parsers with Winnow.
|
||||
//!
|
||||
//! ### Renamed APIs
|
||||
//!
|
||||
//! Names have changed for consistency or clarity.
|
||||
//!
|
||||
//! To find a parser you are looking for,
|
||||
//! - Search the docs for the `nom` parser
|
||||
//! - See the [List of combinators][crate::combinator]
|
||||
//!
|
||||
//! ### GATs
|
||||
//!
|
||||
//! `nom` v8 back-propagates how you will use a parser to parser functions using a language feature
|
||||
//! called GATs.
|
||||
//! Winnow has made the conscious choice not to use this feature, finding alternative ways of
|
||||
//! getting most of the benefits.
|
||||
//!
|
||||
//! Benefits of GATs:
|
||||
//! - Performance as the compiler is able to instantiate copies of a parser that are
|
||||
//! better tailored to how it will be used, like discarding unused allocations for output or
|
||||
//! errors.
|
||||
//!
|
||||
//! Benefits of not using GATs:
|
||||
//! - Predictable performance:
|
||||
//! With GATs, seemingly innocuous changes like choosing to hand write a parser using idiomatic function parsers
|
||||
//! (`fn(&mut I) -> Result<O>`) can cause surprising slow downs because these functions sever the back-propagation from GATs.
|
||||
//! The causes of these slowdowns could be hard to identify by inspection or profiling.
|
||||
//! - No "eek out X% perf improvement" pressure to contort a parser
|
||||
//! that is more easily written imperatively
|
||||
//! to be written declaratively
|
||||
//! so it can preserve the back-propagation from GATs.
|
||||
//! - Built-in parsers serve are can serve as examples to users of idiomatic function parsers
|
||||
//! (`fn(&mut I) -> Result<O>`).
|
||||
//! With GATs, built-in parsers tend to be complex implementations of traits.
|
||||
//! - Faster build times and smaller binary size as parsers only need to be generated for one mode,
|
||||
//! not upto 8
|
||||
//!
|
||||
//! #### Partial/streaming parsers
|
||||
//!
|
||||
//! `nom` v8 back-propagates whether `Parser::parse_complete` was used to select `complete`
|
||||
//! parsers.
|
||||
//! Previously, users had ensure consistently using a parser from the `streaming` or `complete` module.
|
||||
//!
|
||||
//! Instead, you tag the input type (`I`) by wrapping it in [`Partial<I>`] and parsers will adjust
|
||||
//! their behavior accordingly.
|
||||
//! See [partial] special topic.
|
||||
//!
|
||||
//! #### Eliding Output
|
||||
//!
|
||||
//! `nom` v8 back-propagates whether an Output will be used and skips its creation.
|
||||
//! For example, `value(Null, many0(_))` will avoid creating and pushing to a `Vec`.
|
||||
//! Previously, users had to select `count_many0` over `many0` to avoid creating a `Vec`.
|
||||
//!
|
||||
//! Instead, `repeat` returns an `impl Accumulate<T>` which could be a `Vec`, a `usize` for `count`
|
||||
//! variants, or `()` to do no extra work.
|
||||
//!
|
||||
//! #### Eliding Backtracked Errors
|
||||
//!
|
||||
//! Under the hood, [`alt`] is an `if-not-error-else` ladder, see [`_tutorial::chapter_3`].
|
||||
//! nom v8 back-propagates whether the error will be discarded and avoids any expensive work done
|
||||
//! for rich error messages.
|
||||
//!
|
||||
//! Instead, [`ContextError`] and other changes have made it so errors have very little overhead.
|
||||
//! [`dispatch!`] can also be used in some situations to avoid `alt`s overhead.
|
||||
//!
|
||||
//! ### Parsers return [`Stream::Slice`], rather than [`Stream`]
|
||||
//!
|
||||
//! In `nom`, parsers like [`take_while`] parse a [`Stream`] and return a [`Stream`].
|
||||
//! When wrapping the input, like with [`Stateful`],
|
||||
//! you have to unwrap the input to integrate it in your application,
|
||||
//! and it requires [`Stream`] to be `Clone`
|
||||
//! (which requires `RefCell` for mutable external state and can be expensive).
|
||||
//!
|
||||
//! Instead, [`Stream::Slice`] was added to track the intended type for parsers to return.
|
||||
//! If you want to then parse the slice, you then need to take it and turn it back into a
|
||||
//! [`Stream`].
|
||||
//!
|
||||
//! ### `&mut I`
|
||||
//!
|
||||
//! `winnow` switched from pure-function parser (`Fn(I) -> (I, O)` to `Fn(&mut I) -> O`).
|
||||
//! On error, `i` is left pointing at where the error happened.
|
||||
//!
|
||||
//! Benefits of `Fn(&mut I) -> O`:
|
||||
//! - Cleaner code: Removes need to pass `i` everywhere and makes changes to `i` more explicit
|
||||
//! - Correctness: No forgetting to chain `i` through a parser
|
||||
//! - Flexibility: `I` does not need to be `Copy` or even `Clone`. For example, [`Stateful`] can use `&mut S` instead of `RefCell<S>`.
|
||||
//! - Performance: `Result::Ok` is smaller without `i`, reducing the risk that the output will be
|
||||
//! returned on the stack, rather than the much faster CPU registers.
|
||||
//! `Result::Err` can also be smaller because the error type does not need to carry `i` to point
|
||||
//! to the error.
|
||||
//! See also [#72](https://github.com/winnow-rs/winnow/issues/72).
|
||||
//!
|
||||
//! Benefits of `Fn(I) -> (I, O)`:
|
||||
//! - Pure functions can be easier to reason about
|
||||
//! - Less boilerplate in some situations (see below)
|
||||
//! - Less syntactic noise in some situations (see below)
|
||||
//!
|
||||
//! When returning a slice from the input, you have to add a lifetime:
|
||||
//! ```rust
|
||||
//! # use winnow::prelude::*;
|
||||
//! fn foo<'i>(i: &mut &'i str) -> ModalResult<&'i str> {
|
||||
//! # Ok("")
|
||||
//! // ...
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! When writing a closure, you need to annotate the type
|
||||
//! ```rust
|
||||
//! # use winnow::prelude::*;
|
||||
//! # use winnow::combinator::alt;
|
||||
//! # use winnow::error::ContextError;
|
||||
//! # let mut input = "";
|
||||
//! # fn foo<'i>() -> impl ModalParser<&'i str, &'i str, ContextError> {
|
||||
//! alt((
|
||||
//! |i: &mut _| {
|
||||
//! # Ok("")
|
||||
//! // ...
|
||||
//! },
|
||||
//! |i: &mut _| {
|
||||
//! # Ok("")
|
||||
//! // ...
|
||||
//! },
|
||||
//! ))
|
||||
//! # }
|
||||
//! ```
|
||||
//! *(at least the full type isn't needed)*
|
||||
//!
|
||||
//! To save and restore from intermediate states, [`Stream::checkpoint`] and [`Stream::reset`] can help:
|
||||
//! ```rust
|
||||
//! use winnow::prelude::*;
|
||||
//! # let mut i = "";
|
||||
//! # let i = &mut i;
|
||||
//!
|
||||
//! let start = i.checkpoint();
|
||||
//! // ...
|
||||
//! i.reset(&start);
|
||||
//! ```
|
||||
//!
|
||||
//! When the Output of a parser is a slice, you have to add a lifetime:
|
||||
//! ```rust
|
||||
//! # use winnow::prelude::*;
|
||||
//! fn foo<'i>(i: &mut &'i str) -> ModalResult<&'i str> {
|
||||
//! // ...
|
||||
//! # winnow::token::rest.parse_next(i)
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! When writing a closure, you need to annotate the type:
|
||||
//! ```rust
|
||||
//! # use winnow::prelude::*;
|
||||
//! # use winnow::combinator::trace;
|
||||
//! fn foo(i: &mut &str) -> ModalResult<usize> {
|
||||
//! trace("foo", |i: &mut _| {
|
||||
//! // ...
|
||||
//! # Ok(0)
|
||||
//! }).parse_next(i)
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ### Optional [`ErrMode`]
|
||||
//!
|
||||
//! Called `Err` in `nom`, [`ErrMode`] is responsible for
|
||||
//! - Deciding whether to backtrack and try another branch in cases like `alt` or report back to
|
||||
//! the error back to users
|
||||
//! - Tracking incomplete input on partial parsing
|
||||
//!
|
||||
//! As this isn't needed in every parser, it was made optional. [`ModalResult`] is a convenience
|
||||
//! type for using [`ErrMode`].
|
||||
|
||||
#![allow(unused_imports)]
|
||||
use crate::_topic::partial;
|
||||
use crate::_tutorial;
|
||||
use crate::combinator::alt;
|
||||
use crate::combinator::dispatch;
|
||||
use crate::error::ContextError;
|
||||
use crate::error::ErrMode;
|
||||
use crate::error::ModalResult;
|
||||
use crate::stream::Accumulate;
|
||||
use crate::stream::Partial;
|
||||
use crate::stream::Stateful;
|
||||
use crate::stream::Stream;
|
||||
use crate::token::take_while;
|
||||
Vendored
+46
@@ -0,0 +1,46 @@
|
||||
//! # Parsing Partial Input
|
||||
//!
|
||||
//! Typically, the input being parsed is all in-memory, or is complete. Some data sources are too
|
||||
//! large to fit into memory, only allowing parsing an incomplete or [`Partial`] subset of the
|
||||
//! data, requiring incrementally parsing.
|
||||
//!
|
||||
//! By wrapping a stream, like `&[u8]`, with [`Partial`], parsers will report when the data is
|
||||
//! [`Incomplete`] and more input is [`Needed`], allowing the caller to stream-in additional data
|
||||
//! to be parsed. The data is then parsed a chunk at a time.
|
||||
//!
|
||||
//! Chunks are typically defined by either:
|
||||
//! - A header reporting the number of bytes, like with [`length_and_then`]
|
||||
//! - [`Partial`] can explicitly be changed to being complete once the specified bytes are
|
||||
//! acquired via [`StreamIsPartial::complete`].
|
||||
//! - A delimiter, like with [ndjson](https://github.com/ndjson/ndjson-spec/)
|
||||
//! - You can parse up-to the delimiter or do a `take_until(0.., delim).and_then(parser)`
|
||||
//!
|
||||
//! If the chunks are not homogeneous, a state machine will be needed to track what the expected
|
||||
//! parser is for the next chunk.
|
||||
//!
|
||||
//! Caveats:
|
||||
//! - `winnow` takes the approach of re-parsing from scratch. Chunks should be relatively small to
|
||||
//! prevent the re-parsing overhead from dominating.
|
||||
//! - Parsers like [`repeat`] do not know when an `eof` is from insufficient data or the end of the
|
||||
//! stream, causing them to always report [`Incomplete`].
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! `main.rs`:
|
||||
//! ```rust,ignore
|
||||
#![doc = include_str!("../../examples/ndjson/main.rs")]
|
||||
//! ```
|
||||
//!
|
||||
//! `parser.rs`:
|
||||
//! ```rust,ignore
|
||||
#![doc = include_str!("../../examples/ndjson/parser.rs")]
|
||||
//! ```
|
||||
|
||||
#![allow(unused_imports)] // Used for intra-doc links
|
||||
|
||||
use crate::binary::length_and_then;
|
||||
use crate::combinator::repeat;
|
||||
use crate::error::ErrMode::Incomplete;
|
||||
use crate::error::Needed;
|
||||
use crate::stream::Partial;
|
||||
use crate::stream::StreamIsPartial;
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
//! # Performance
|
||||
//!
|
||||
//! ## Runtime Performance
|
||||
//!
|
||||
//! See also the general Rust [Performance Book](https://nnethercote.github.io/perf-book/)
|
||||
//!
|
||||
//! Tips
|
||||
//! - Try `cargo add winnow -F simd`. For some it offers significant performance improvements
|
||||
//! - When enough cases of an [`alt`] have unique prefixes, prefer [`dispatch`]
|
||||
//! - When parsing text, try to parse as bytes (`u8`) rather than `char`s ([`BStr`] can make
|
||||
//! debugging easier)
|
||||
//! - Find simplified subsets of the grammar to parse, falling back to the full grammar when it
|
||||
//! doesn't work. For example, when parsing json strings, parse them without support for escapes,
|
||||
//! falling back to escape support if it fails.
|
||||
//! - Watch for large return types. A surprising place these can show up is when chaining parsers
|
||||
//! with a tuple.
|
||||
//!
|
||||
//! ## Build-time Performance
|
||||
//!
|
||||
//! Returning complex types as `impl Trait` can negatively impact build times. This can hit in
|
||||
//! surprising cases like:
|
||||
//! ```rust
|
||||
//! # use winnow::prelude::*;
|
||||
//! fn foo<I, O, E>() -> impl Parser<I, O, E>
|
||||
//! # where
|
||||
//! # I: winnow::stream::Stream<Token=O>,
|
||||
//! # I: winnow::stream::StreamIsPartial,
|
||||
//! # E: winnow::error::ParserError<I>,
|
||||
//! {
|
||||
//! // ...some chained combinators...
|
||||
//! # winnow::token::any
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! Instead, wrap the combinators in a closure to simplify the type:
|
||||
//! ```rust
|
||||
//! # use winnow::prelude::*;
|
||||
//! fn foo<I, O, E>() -> impl Parser<I, O, E>
|
||||
//! # where
|
||||
//! # I: winnow::stream::Stream<Token=O>,
|
||||
//! # I: winnow::stream::StreamIsPartial,
|
||||
//! # E: winnow::error::ParserError<I>,
|
||||
//! {
|
||||
//! move |input: &mut I| {
|
||||
//! // ...some chained combinators...
|
||||
//! # winnow::token::any
|
||||
//! .parse_next(input)
|
||||
//! }
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
#![allow(unused_imports)]
|
||||
use crate::combinator::alt;
|
||||
use crate::combinator::dispatch;
|
||||
use crate::stream::BStr;
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
//! # s-expression
|
||||
//!
|
||||
//! ```rust
|
||||
#![doc = include_str!("../../examples/s_expression/parser.rs")]
|
||||
//! ```
|
||||
Vendored
+63
@@ -0,0 +1,63 @@
|
||||
//! # Custom [`Stream`]
|
||||
//!
|
||||
//! `winnow` is batteries included with support for
|
||||
//! - Basic inputs like `&str`, newtypes with
|
||||
//! - Improved debug output like [`Bytes`]
|
||||
//! - [`Stateful`] for passing state through your parser, like tracking recursion
|
||||
//! depth
|
||||
//! - [`LocatingSlice`] for looking up the absolute position of a token
|
||||
//!
|
||||
//! ## Implementing a custom token
|
||||
//!
|
||||
//! The first level of customization is parsing [`&[MyItem]`][Stream#impl-Stream-for-%26%5BT%5D]
|
||||
//! or [`TokenSlice<MyItem>`].
|
||||
//!
|
||||
//! The basic traits you may want for a custom token type are:
|
||||
//!
|
||||
//! | trait | usage |
|
||||
//! |---|---|
|
||||
//! | [`AsChar`] |Transforms common types to a char for basic token parsing|
|
||||
//! | [`ContainsToken`] |Look for the token in the given set|
|
||||
//!
|
||||
//! See also [`TokenSlice<MyItem>`], [lexing].
|
||||
//!
|
||||
//! ## Implementing a custom stream
|
||||
//!
|
||||
//! Let's assume we have an input type we'll call `MyStream`.
|
||||
//! `MyStream` is a sequence of `MyItem` tokens.
|
||||
//!
|
||||
//! The goal is to define parsers with this signature: `&mut MyStream -> ModalResult<Output>`.
|
||||
//! ```rust
|
||||
//! # use winnow::prelude::*;
|
||||
//! # type MyStream<'i> = &'i str;
|
||||
//! # type Output<'i> = &'i str;
|
||||
//! fn parser<'s>(i: &mut MyStream<'s>) -> ModalResult<Output<'s>> {
|
||||
//! "test".parse_next(i)
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! Like above, you'll need to implement the related token traits for `MyItem`.
|
||||
//!
|
||||
//! The traits you may want to implement for `MyStream` include:
|
||||
//!
|
||||
//! | trait | usage |
|
||||
//! |---|---|
|
||||
//! | [`Stream`] |Core trait for driving parsing|
|
||||
//! | [`StreamIsPartial`] | Marks the input as being the complete buffer or a partial buffer for streaming input |
|
||||
//! | [`AsBytes`] |Casts the input type to a byte slice|
|
||||
//! | [`AsBStr`] |Casts the input type to a slice of ASCII / UTF-8-like bytes|
|
||||
//! | [`Compare`] |Character comparison operations|
|
||||
//! | [`FindSlice`] |Look for a substring in self|
|
||||
//! | [`Location`] |Calculate location within initial input|
|
||||
//! | [`Offset`] |Calculate the offset between slices|
|
||||
//!
|
||||
//! And for `&[MyItem]` (slices returned by [`Stream`]):
|
||||
//!
|
||||
//! | trait | usage |
|
||||
//! |---|---|
|
||||
//! | [`SliceLen`] |Calculate the input length|
|
||||
//! | [`ParseSlice`] |Used to integrate `&str`'s `parse()` method|
|
||||
|
||||
#![allow(unused_imports)] // Here for intra-dock links
|
||||
use super::lexing;
|
||||
use crate::stream::*;
|
||||
Vendored
+110
@@ -0,0 +1,110 @@
|
||||
//! # Why `winnow`?
|
||||
//!
|
||||
//! To answer this question, it will be useful to contrast this with other approaches to parsing.
|
||||
//!
|
||||
//! <div class="warning">
|
||||
//!
|
||||
//! **Note:** This will focus on principles and priorities. For a deeper and wider
|
||||
//! comparison with other Rust parser libraries, see
|
||||
//! [parse-rosetta-rs](https://github.com/rosetta-rs/parse-rosetta-rs).
|
||||
//!
|
||||
//! </div>
|
||||
//!
|
||||
//! ## Hand-written parsers
|
||||
//!
|
||||
//! Typically, a hand-written parser gives you the flexibility to get
|
||||
//! - Fast parse performance
|
||||
//! - Fast compile-time
|
||||
//! - Small binary sizes
|
||||
//! - High quality error message
|
||||
//! - Fewer dependencies to audit
|
||||
//!
|
||||
//! However, this comes at the cost of doing it all yourself, including
|
||||
//! - Optimizing for each of the above characteristics you care about
|
||||
//! - Ensuring the safety of any `unsafe` code (buffer overflows being a common bug with parsers)
|
||||
//! - Being aware of, familiar with, and correctly implement the relevant algorithms.
|
||||
//! matklad, who has written two rust compile frontends, commented
|
||||
//! ["I’ve implemented a production-grade Pratt parser once, but I no longer immediately understand that code :-)"](https://matklad.github.io/2020/04/13/simple-but-powerful-pratt-parsing.html)
|
||||
//!
|
||||
//! This approach works well if:
|
||||
//! - Your format is small and is unlikely to change
|
||||
//! - Your format is large but you have people who can focus solely on parsing, like with large
|
||||
//! programming languages
|
||||
//!
|
||||
//! ## `winnow`
|
||||
//!
|
||||
//! Unlike traditional programming language parsers that use
|
||||
//! [lex](https://en.wikipedia.org/wiki/Lex_(software)) or
|
||||
//! [yacc](https://en.wikipedia.org/wiki/Yacc), you can think of `winnow` as a general version of
|
||||
//! the helpers you would create along the way to writing a hand-written parser.
|
||||
//!
|
||||
//! `winnow` includes support for:
|
||||
//! - Zero-copy parsing
|
||||
//! - [Parse traces][trace] for easier debugging
|
||||
//! - [Streaming parsing][Partial] for network communication or large file
|
||||
//! - [Stateful] parsers
|
||||
//!
|
||||
//! For binary formats, `winnow` includes:
|
||||
//! - [A hexadecimal view][crate::Bytes] in [trace]
|
||||
//! - [TLV](https://en.wikipedia.org/wiki/Type-length-value) (e.g. [`length_take`])
|
||||
//! - Some common parsers to help get started, like numbers
|
||||
//!
|
||||
//! For text formats, `winnow` includes:
|
||||
//! - [Tracking of spans][crate::LocatingSlice]
|
||||
//! - [A textual view when parsing as bytes][crate::BStr] in [trace]
|
||||
//! - Ability to evaluate directly, parse to an AST, or lex and parse the format
|
||||
//!
|
||||
//! This works well for:
|
||||
//! - Prototyping for what will be a hand-written parser
|
||||
//! - When you want to minimize the work to evolve your format
|
||||
//! - When you don't have contributors focused solely on parsing and your grammar is large enough
|
||||
//! to be unwieldy to hand write.
|
||||
//!
|
||||
//! ## `nom`
|
||||
//!
|
||||
//! `winnow` is a fork of the venerable [`nom`](https://crates.io/crates/nom). The difference
|
||||
//! between them is largely in priorities. `nom` prioritizes:
|
||||
//! - Lower churn for existing users while `winnow` is trying to find ways to make things better
|
||||
//! for the parsers yet to be written.
|
||||
//! - Having a small core, relying on external crates like
|
||||
//! [`nom-locate`](https://crates.io/crates/nom_locate) and
|
||||
//! [`nom-supreme`](https://crates.io/crates/nom-supreme), encouraging flexibility among users
|
||||
//! and to not block users on new features being merged while `winnow` aims to include all the
|
||||
//! fundamentals for parsing to ensure the experience is cohesive and high quality.
|
||||
//!
|
||||
//! For more details, see the [design differences][super::nom#api-differences].
|
||||
//!
|
||||
//! See also our [nom migration guide][super::nom#migrating-from-nom].
|
||||
//!
|
||||
//! ## `chumsky`
|
||||
//!
|
||||
//! [`chumsky`](https://crates.io/crates/chumsky) is an up and coming parser-combinator library
|
||||
//! that includes advanced features like error recovery.
|
||||
//!
|
||||
//! Probably the biggest diverging philosophy is `chumsky`s stance:
|
||||
//!
|
||||
//! > "If you need to implement either `Parser` or `Strategy` by hand, that's a problem that needs fixing".
|
||||
//!
|
||||
//! This is under "batteries included" but it also ties into the feeling that `chumsky` acts more like
|
||||
//! a framework. Instead of composing together helpers, you are expected to do everything through
|
||||
//! their system to the point that it is non-trivial to implement their `Parser` trait and are
|
||||
//! encouraged to use the
|
||||
//! [`custom`](https://docs.rs/chumsky/0.9.0/chumsky/primitive/fn.custom.html) helper. This
|
||||
//! requires re-framing everything to fit within their model and makes the code harder to understand
|
||||
//! and debug as you are working with abstract operations that will eventually be applied
|
||||
//! rather than directly with the parsers.
|
||||
//!
|
||||
//! In contrast, `winnow` is an introspectable toolbox that can easily be customized at any level.
|
||||
//! Probably the biggest thing that `winnow` loses out on is optimizations from ["parse modes" via
|
||||
//! GATs](https://github.com/zesterer/chumsky/pull/82) which allows downstream parsers to tell
|
||||
//! upstream parsers when information will be discarded, allowing bypassing expensive operations,
|
||||
//! like allocations. This requires a lot more complex interaction with parsers that isn't as
|
||||
//! trivial to do with bare functions which would lose out on any of that side-band information.
|
||||
//! Instead, we work around this with things like the [`Accumulate`] trait.
|
||||
|
||||
#![allow(unused_imports)]
|
||||
use crate::binary::length_take;
|
||||
use crate::combinator::trace;
|
||||
use crate::stream::Accumulate;
|
||||
use crate::stream::Partial;
|
||||
use crate::stream::Stateful;
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
//! # Chapter 0: Introduction
|
||||
//!
|
||||
//! This tutorial assumes that you are:
|
||||
//! - Already familiar with Rust
|
||||
//! - Using `winnow` for the first time
|
||||
//!
|
||||
//! The focus will be on parsing in-memory strings (`&str`). Once done, you might want to check the
|
||||
//! [Special Topics][_topic] for more specialized topics or examples.
|
||||
//!
|
||||
//! ## About
|
||||
//!
|
||||
//! `winnow` is a parser-combinator library. In other words, it gives you tools to define:
|
||||
//! - "parsers", or functions that take an input and give back an output
|
||||
//! - "combinators", or functions that take parsers and _combine_ them together!
|
||||
//!
|
||||
//! While "combinator" might be an unfamiliar word, you are likely using them in your rust code
|
||||
//! today, like with the [`Iterator`] trait:
|
||||
//! ```rust
|
||||
//! let data = vec![1, 2, 3, 4, 5];
|
||||
//! let even_count = data.iter()
|
||||
//! .copied() // combinator
|
||||
//! .filter(|d| d % 2 == 0) // combinator
|
||||
//! .count(); // combinator
|
||||
//! ```
|
||||
//!
|
||||
//! Parser combinators are great because:
|
||||
//!
|
||||
//! - Individual parser functions are small, focused on one thing, ignoring the rest
|
||||
//! - You can write tests focused on individual parsers (unit tests and property-based tests)
|
||||
//! in addition to testing the top-level parser as a whole.
|
||||
//! - Top-level parsing code looks close to the grammar you would have written
|
||||
|
||||
#![allow(unused_imports)]
|
||||
use crate::_topic;
|
||||
use std::iter::Iterator;
|
||||
|
||||
pub use super::chapter_1 as next;
|
||||
pub use crate::_tutorial as table_of_contents;
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
//! # Chapter 1: The Winnow Way
|
||||
//!
|
||||
//! First of all, we need to understand the way that winnow thinks about parsing.
|
||||
//! As discussed in the introduction, winnow lets us compose more complex parsers from more simple
|
||||
//! ones (using "combinators").
|
||||
//!
|
||||
//! Let's discuss what a "parser" actually does. A parser takes an input and advances it until it returns
|
||||
//! a result, where:
|
||||
//! - `Ok` indicates the parser successfully found what it was looking for; or
|
||||
//! - `Err` indicates the parser could not find what it was looking for.
|
||||
//!
|
||||
//! Parsers do more than just return a binary "success"/"failure" code.
|
||||
//! - On success, the parser will return the processed data. The input will be advanced to the end of
|
||||
//! what was processed, pointing to what will be parsed next.
|
||||
//! - If the parser failed, then there are multiple errors that could be returned.
|
||||
//! We'll explore this further in [`chapter_7`].
|
||||
//!
|
||||
//! ```text
|
||||
//! ┌─► Ok(what matched the parser)
|
||||
//! ┌────────┐ │
|
||||
//! my input───►│a parser├──►either──┤
|
||||
//! └────────┘ └─► Err(...)
|
||||
//! ```
|
||||
//!
|
||||
//!
|
||||
//! To represent this model of the world, winnow uses the [`Result<O>`] type.
|
||||
//! The `Ok` variant has `output: O`;
|
||||
//! whereas the `Err` variant stores an error.
|
||||
//!
|
||||
//! You can import that from:
|
||||
//!
|
||||
//! ```rust
|
||||
//! use winnow::Result;
|
||||
//! ```
|
||||
//!
|
||||
//! To combine parsers, we need a common way to refer to them which is where the [`Parser<I, O, E>`]
|
||||
//! trait comes in with [`Parser::parse_next`] being the primary way to drive
|
||||
//! parsing forward.
|
||||
//! In [`chapter_6`], we'll cover how to integrate these into your application, particularly with
|
||||
//! [`Parser::parse`].
|
||||
//!
|
||||
//! You'll note that `I` and `O` are parameterized -- while most of the examples in this book
|
||||
//! will be with `&str` (i.e. parsing a string); [they do not have to be strings][stream]; nor do they
|
||||
//! have to be the same type (consider the simple example where `I = &str`, and `O = u64` -- this
|
||||
//! parses a string into an unsigned integer.)
|
||||
//!
|
||||
//! # Let's write our first parser!
|
||||
//!
|
||||
//! The simplest parser we can write is one which successfully does nothing.
|
||||
//!
|
||||
//! To make it easier to implement a [`Parser`], the trait is implemented for
|
||||
//! functions of the form `Fn(&mut I) -> Result<O>`.
|
||||
//!
|
||||
//! This parser function should take in a `&str`:
|
||||
//!
|
||||
//! - Since it is supposed to succeed, we know it will return the `Ok` variant.
|
||||
//! - Since it does nothing to our input, the input will be left where it started.
|
||||
//! - Since it doesn't parse anything, it also should just return an empty string.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use winnow::Result;
|
||||
//! use winnow::Parser;
|
||||
//!
|
||||
//! pub fn do_nothing_parser<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! Ok("")
|
||||
//! }
|
||||
//!
|
||||
//! fn main() {
|
||||
//! let mut input = "0x1a2b Hello";
|
||||
//!
|
||||
//! let output = do_nothing_parser.parse_next(&mut input).unwrap();
|
||||
//! // Same as:
|
||||
//! // let output = do_nothing_parser(&mut input).unwrap();
|
||||
//!
|
||||
//! assert_eq!(input, "0x1a2b Hello");
|
||||
//! assert_eq!(output, "");
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
#![allow(unused_imports)]
|
||||
use super::chapter_6;
|
||||
use super::chapter_7;
|
||||
use crate::Parser;
|
||||
use crate::_topic::stream;
|
||||
|
||||
pub use super::chapter_0 as previous;
|
||||
pub use super::chapter_2 as next;
|
||||
pub use crate::_tutorial as table_of_contents;
|
||||
+301
@@ -0,0 +1,301 @@
|
||||
//! # Chapter 2: Tokens and Tags
|
||||
//!
|
||||
//! The simplest *useful* parser you can write is one which matches tokens.
|
||||
//! In our case, tokens are `char`.
|
||||
//!
|
||||
//! ## Tokens
|
||||
//!
|
||||
//! [`Stream`] provides some core operations to help with parsing. For example, to process a
|
||||
//! single token, you can do:
|
||||
//! ```rust
|
||||
//! # use winnow::Parser;
|
||||
//! # use winnow::Result;
|
||||
//! use winnow::stream::Stream;
|
||||
//! use winnow::error::ParserError;
|
||||
//!
|
||||
//! fn parse_prefix(input: &mut &str) -> Result<char> {
|
||||
//! let c = input.next_token().ok_or_else(|| {
|
||||
//! ParserError::from_input(input)
|
||||
//! })?;
|
||||
//! if c != '0' {
|
||||
//! return Err(ParserError::from_input(input));
|
||||
//! }
|
||||
//! Ok(c)
|
||||
//! }
|
||||
//!
|
||||
//! fn main() {
|
||||
//! let mut input = "0x1a2b Hello";
|
||||
//!
|
||||
//! let output = parse_prefix.parse_next(&mut input).unwrap();
|
||||
//!
|
||||
//! assert_eq!(input, "x1a2b Hello");
|
||||
//! assert_eq!(output, '0');
|
||||
//!
|
||||
//! assert!(parse_prefix.parse_next(&mut "d").is_err());
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! This extraction of a token is encapsulated in the [`any`] parser:
|
||||
//! ```rust
|
||||
//! # use winnow::Result;
|
||||
//! # use winnow::error::ParserError;
|
||||
//! use winnow::Parser;
|
||||
//! use winnow::token::any;
|
||||
//!
|
||||
//! fn parse_prefix(input: &mut &str) -> Result<char> {
|
||||
//! let c = any
|
||||
//! .parse_next(input)?;
|
||||
//! if c != '0' {
|
||||
//! return Err(ParserError::from_input(input));
|
||||
//! }
|
||||
//! Ok(c)
|
||||
//! }
|
||||
//! #
|
||||
//! # fn main() {
|
||||
//! # let mut input = "0x1a2b Hello";
|
||||
//! #
|
||||
//! # let output = parse_prefix.parse_next(&mut input).unwrap();
|
||||
//! #
|
||||
//! # assert_eq!(input, "x1a2b Hello");
|
||||
//! # assert_eq!(output, '0');
|
||||
//! #
|
||||
//! # assert!(parse_prefix.parse_next(&mut "d").is_err());
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! Using the higher level [`any`] parser opens `parse_prefix` to the helpers on the [`Parser`] trait,
|
||||
//! like [`Parser::verify`] which fails a parse if a condition isn't met, like our check above:
|
||||
//! ```rust
|
||||
//! # use winnow::Result;
|
||||
//! use winnow::Parser;
|
||||
//! use winnow::token::any;
|
||||
//!
|
||||
//! fn parse_prefix(input: &mut &str) -> Result<char> {
|
||||
//! let c = any
|
||||
//! .verify(|c| *c == '0')
|
||||
//! .parse_next(input)?;
|
||||
//! Ok(c)
|
||||
//! }
|
||||
//! #
|
||||
//! # fn main() {
|
||||
//! # let mut input = "0x1a2b Hello";
|
||||
//! #
|
||||
//! # let output = parse_prefix.parse_next(&mut input).unwrap();
|
||||
//! #
|
||||
//! # assert_eq!(input, "x1a2b Hello");
|
||||
//! # assert_eq!(output, '0');
|
||||
//! #
|
||||
//! # assert!(parse_prefix.parse_next(&mut "d").is_err());
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! Matching a single token literal is common enough that [`Parser`] is implemented for
|
||||
//! the `char` type, encapsulating both [`any`] and [`Parser::verify`]:
|
||||
//! ```rust
|
||||
//! # use winnow::Result;
|
||||
//! use winnow::Parser;
|
||||
//!
|
||||
//! fn parse_prefix(input: &mut &str) -> Result<char> {
|
||||
//! let c = '0'.parse_next(input)?;
|
||||
//! Ok(c)
|
||||
//! }
|
||||
//! #
|
||||
//! # fn main() {
|
||||
//! # let mut input = "0x1a2b Hello";
|
||||
//! #
|
||||
//! # let output = parse_prefix.parse_next(&mut input).unwrap();
|
||||
//! #
|
||||
//! # assert_eq!(input, "x1a2b Hello");
|
||||
//! # assert_eq!(output, '0');
|
||||
//! #
|
||||
//! # assert!(parse_prefix.parse_next(&mut "d").is_err());
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Tags
|
||||
//!
|
||||
//! [`Stream`] also supports processing slices of tokens:
|
||||
//! ```rust
|
||||
//! # use winnow::Parser;
|
||||
//! # use winnow::Result;
|
||||
//! use winnow::stream::Stream;
|
||||
//! use winnow::error::ParserError;
|
||||
//!
|
||||
//! fn parse_prefix<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! let expected = "0x";
|
||||
//! if input.len() < expected.len() {
|
||||
//! return Err(ParserError::from_input(input));
|
||||
//! }
|
||||
//! let actual = input.next_slice(expected.len());
|
||||
//! if actual != expected {
|
||||
//! return Err(ParserError::from_input(input));
|
||||
//! }
|
||||
//! Ok(actual)
|
||||
//! }
|
||||
//!
|
||||
//! fn main() {
|
||||
//! let mut input = "0x1a2b Hello";
|
||||
//!
|
||||
//! let output = parse_prefix.parse_next(&mut input).unwrap();
|
||||
//! assert_eq!(input, "1a2b Hello");
|
||||
//! assert_eq!(output, "0x");
|
||||
//!
|
||||
//! assert!(parse_prefix.parse_next(&mut "0o123").is_err());
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! Matching the input position against a string literal is encapsulated in the [`literal`] parser:
|
||||
//! ```rust
|
||||
//! # use winnow::Result;
|
||||
//! # use winnow::Parser;
|
||||
//! use winnow::token::literal;
|
||||
//!
|
||||
//! fn parse_prefix<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! let expected = "0x";
|
||||
//! let actual = literal(expected).parse_next(input)?;
|
||||
//! Ok(actual)
|
||||
//! }
|
||||
//! #
|
||||
//! # fn main() {
|
||||
//! # let mut input = "0x1a2b Hello";
|
||||
//! #
|
||||
//! # let output = parse_prefix.parse_next(&mut input).unwrap();
|
||||
//! # assert_eq!(input, "1a2b Hello");
|
||||
//! # assert_eq!(output, "0x");
|
||||
//! #
|
||||
//! # assert!(parse_prefix.parse_next(&mut "0o123").is_err());
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! Like for a single token, matching a string literal is common enough that [`Parser`] is implemented for the `&str` type:
|
||||
//! ```rust
|
||||
//! # use winnow::Result;
|
||||
//! use winnow::Parser;
|
||||
//!
|
||||
//! fn parse_prefix<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! let actual = "0x".parse_next(input)?;
|
||||
//! Ok(actual)
|
||||
//! }
|
||||
//! #
|
||||
//! # fn main() {
|
||||
//! # let mut input = "0x1a2b Hello";
|
||||
//! #
|
||||
//! # let output = parse_prefix.parse_next(&mut input).unwrap();
|
||||
//! # assert_eq!(input, "1a2b Hello");
|
||||
//! # assert_eq!(output, "0x");
|
||||
//! #
|
||||
//! # assert!(parse_prefix.parse_next(&mut "0o123").is_err());
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! See [`token`] for additional individual and token-slice parsers.
|
||||
//!
|
||||
//! ## Character Classes
|
||||
//!
|
||||
//! Selecting a single `char` or a [`literal`] is fairly limited. Sometimes, you will want to select one of several
|
||||
//! `chars` of a specific class, like digits. For this, we use the [`one_of`] parser:
|
||||
//!
|
||||
//! ```rust
|
||||
//! # use winnow::Parser;
|
||||
//! # use winnow::Result;
|
||||
//! use winnow::token::one_of;
|
||||
//!
|
||||
//! fn parse_digits(input: &mut &str) -> Result<char> {
|
||||
//! one_of(('0'..='9', 'a'..='f', 'A'..='F')).parse_next(input)
|
||||
//! }
|
||||
//!
|
||||
//! fn main() {
|
||||
//! let mut input = "1a2b Hello";
|
||||
//!
|
||||
//! let output = parse_digits.parse_next(&mut input).unwrap();
|
||||
//! assert_eq!(input, "a2b Hello");
|
||||
//! assert_eq!(output, '1');
|
||||
//!
|
||||
//! assert!(parse_digits.parse_next(&mut "Z").is_err());
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! > **Aside:** [`one_of`] might look straightforward, a function returning a value that implements `Parser`.
|
||||
//! > Let's look at it more closely as its used above (resolving all generic parameters):
|
||||
//! > ```rust
|
||||
//! > # use winnow::prelude::*;
|
||||
//! > # use winnow::error::ContextError;
|
||||
//! > pub fn one_of<'i>(
|
||||
//! > list: &'static [char]
|
||||
//! > ) -> impl Parser<&'i str, char, ContextError> {
|
||||
//! > // ...
|
||||
//! > # winnow::token::one_of(list)
|
||||
//! > }
|
||||
//! > ```
|
||||
//! > If you have not programmed in a language where functions are values, the type signature of the
|
||||
//! > [`one_of`] function might be a surprise.
|
||||
//! > The function [`one_of`] *returns a function*. The function it returns is a
|
||||
//! > [`Parser`], taking a `&str` and returning an [`Result`]. This is a common pattern in winnow for
|
||||
//! > configurable or stateful parsers.
|
||||
//!
|
||||
//! Some of character classes are common enough that a named parser is provided, like with:
|
||||
//! - [`line_ending`][crate::ascii::line_ending]: Recognizes an end of line (both `\n` and `\r\n`)
|
||||
//! - [`newline`][crate::ascii::newline]: Matches a newline character `\n`
|
||||
//! - [`tab`][crate::ascii::tab]: Matches a tab character `\t`
|
||||
//!
|
||||
//! You can then capture sequences of these characters with parsers like [`take_while`].
|
||||
//! ```rust
|
||||
//! # use winnow::Parser;
|
||||
//! # use winnow::Result;
|
||||
//! use winnow::token::take_while;
|
||||
//!
|
||||
//! fn parse_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! take_while(1.., ('0'..='9', 'a'..='f', 'A'..='F')).parse_next(input)
|
||||
//! }
|
||||
//!
|
||||
//! fn main() {
|
||||
//! let mut input = "1a2b Hello";
|
||||
//!
|
||||
//! let output = parse_digits.parse_next(&mut input).unwrap();
|
||||
//! assert_eq!(input, " Hello");
|
||||
//! assert_eq!(output, "1a2b");
|
||||
//!
|
||||
//! assert!(parse_digits.parse_next(&mut "Z").is_err());
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! We could simplify this further by using one of the built-in character classes, [`hex_digit1`]:
|
||||
//! ```rust
|
||||
//! # use winnow::Parser;
|
||||
//! # use winnow::Result;
|
||||
//! use winnow::ascii::hex_digit1;
|
||||
//!
|
||||
//! fn parse_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! hex_digit1.parse_next(input)
|
||||
//! }
|
||||
//!
|
||||
//! fn main() {
|
||||
//! let mut input = "1a2b Hello";
|
||||
//!
|
||||
//! let output = parse_digits.parse_next(&mut input).unwrap();
|
||||
//! assert_eq!(input, " Hello");
|
||||
//! assert_eq!(output, "1a2b");
|
||||
//!
|
||||
//! assert!(parse_digits.parse_next(&mut "Z").is_err());
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! See [`ascii`] for more text-based parsers.
|
||||
|
||||
#![allow(unused_imports)]
|
||||
use crate::ascii;
|
||||
use crate::ascii::hex_digit1;
|
||||
use crate::stream::ContainsToken;
|
||||
use crate::stream::Stream;
|
||||
use crate::token;
|
||||
use crate::token::any;
|
||||
use crate::token::literal;
|
||||
use crate::token::one_of;
|
||||
use crate::token::take_while;
|
||||
use crate::Parser;
|
||||
use crate::Result;
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
pub use super::chapter_1 as previous;
|
||||
pub use super::chapter_3 as next;
|
||||
pub use crate::_tutorial as table_of_contents;
|
||||
+394
@@ -0,0 +1,394 @@
|
||||
//! # Chapter 3: Sequencing and Alternatives
|
||||
//!
|
||||
//! In the last chapter, we saw how to create parsers using prebuilt parsers.
|
||||
//!
|
||||
//! In this chapter, we explore two other widely used features:
|
||||
//! sequencing and alternatives.
|
||||
//!
|
||||
//! ## Sequencing
|
||||
//!
|
||||
//! Now that we can create more interesting parsers, we can sequence them together, like:
|
||||
//!
|
||||
//! ```rust
|
||||
//! # use winnow::prelude::*;
|
||||
//! # use winnow::Result;
|
||||
//! # use winnow::token::take_while;
|
||||
//! #
|
||||
//! fn parse_prefix<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! "0x".parse_next(input)
|
||||
//! }
|
||||
//!
|
||||
//! fn parse_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! take_while(1.., (
|
||||
//! ('0'..='9'),
|
||||
//! ('A'..='F'),
|
||||
//! ('a'..='f'),
|
||||
//! )).parse_next(input)
|
||||
//! }
|
||||
//!
|
||||
//! fn main() {
|
||||
//! let mut input = "0x1a2b Hello";
|
||||
//!
|
||||
//! let prefix = parse_prefix.parse_next(&mut input).unwrap();
|
||||
//! let digits = parse_digits.parse_next(&mut input).unwrap();
|
||||
//!
|
||||
//! assert_eq!(prefix, "0x");
|
||||
//! assert_eq!(digits, "1a2b");
|
||||
//! assert_eq!(input, " Hello");
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! To sequence these together, you can just put them in a tuple:
|
||||
//! ```rust
|
||||
//! # use winnow::prelude::*;
|
||||
//! # use winnow::Result;
|
||||
//! # use winnow::token::take_while;
|
||||
//! #
|
||||
//! # fn parse_prefix<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # "0x".parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='9'),
|
||||
//! # ('A'..='F'),
|
||||
//! # ('a'..='f'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! //...
|
||||
//!
|
||||
//! fn main() {
|
||||
//! let mut input = "0x1a2b Hello";
|
||||
//!
|
||||
//! let (prefix, digits) = (
|
||||
//! parse_prefix,
|
||||
//! parse_digits
|
||||
//! ).parse_next(&mut input).unwrap();
|
||||
//!
|
||||
//! assert_eq!(prefix, "0x");
|
||||
//! assert_eq!(digits, "1a2b");
|
||||
//! assert_eq!(input, " Hello");
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! Frequently, you won't care about the literal and you can instead use one of the provided combinators,
|
||||
//! like [`preceded`]:
|
||||
//! ```rust
|
||||
//! # use winnow::prelude::*;
|
||||
//! # use winnow::Result;
|
||||
//! # use winnow::token::take_while;
|
||||
//! use winnow::combinator::preceded;
|
||||
//!
|
||||
//! # fn parse_prefix<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # "0x".parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='9'),
|
||||
//! # ('A'..='F'),
|
||||
//! # ('a'..='f'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! //...
|
||||
//!
|
||||
//! fn main() {
|
||||
//! let mut input = "0x1a2b Hello";
|
||||
//!
|
||||
//! let digits = preceded(
|
||||
//! parse_prefix,
|
||||
//! parse_digits
|
||||
//! ).parse_next(&mut input).unwrap();
|
||||
//!
|
||||
//! assert_eq!(digits, "1a2b");
|
||||
//! assert_eq!(input, " Hello");
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! See [`combinator`] for more sequencing parsers.
|
||||
//!
|
||||
//! ## Alternatives
|
||||
//!
|
||||
//! Sometimes, we might want to choose between two parsers; and we're happy with
|
||||
//! either being used.
|
||||
//!
|
||||
//! To retry a parse result, we can save off a [`Stream::checkpoint`] and later restore the parser
|
||||
//! back to that position with [`Stream::reset`]:
|
||||
//! ```rust
|
||||
//! # use winnow::prelude::*;
|
||||
//! # use winnow::Result;
|
||||
//! # use winnow::token::take_while;
|
||||
//! use winnow::stream::Stream;
|
||||
//!
|
||||
//! fn parse_digits<'s>(input: &mut &'s str) -> Result<(&'s str, &'s str)> {
|
||||
//! let start = input.checkpoint();
|
||||
//! if let Ok(output) = ("0b", parse_bin_digits).parse_next(input) {
|
||||
//! return Ok(output);
|
||||
//! }
|
||||
//!
|
||||
//! input.reset(&start);
|
||||
//! if let Ok(output) = ("0o", parse_oct_digits).parse_next(input) {
|
||||
//! return Ok(output);
|
||||
//! }
|
||||
//!
|
||||
//! input.reset(&start);
|
||||
//! if let Ok(output) = ("0d", parse_dec_digits).parse_next(input) {
|
||||
//! return Ok(output);
|
||||
//! }
|
||||
//!
|
||||
//! input.reset(&start);
|
||||
//! ("0x", parse_hex_digits).parse_next(input)
|
||||
//! }
|
||||
//!
|
||||
//! // ...
|
||||
//! # fn parse_bin_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='1'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_oct_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='7'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_dec_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='9'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_hex_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='9'),
|
||||
//! # ('A'..='F'),
|
||||
//! # ('a'..='f'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//!
|
||||
//! fn main() {
|
||||
//! let mut input = "0x1a2b Hello";
|
||||
//!
|
||||
//! let (prefix, digits) = parse_digits.parse_next(&mut input).unwrap();
|
||||
//!
|
||||
//! assert_eq!(input, " Hello");
|
||||
//! assert_eq!(prefix, "0x");
|
||||
//! assert_eq!(digits, "1a2b");
|
||||
//!
|
||||
//! assert!(parse_digits(&mut "ghiWorld").is_err());
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! <div class="warning">
|
||||
//!
|
||||
//! **Warning:** the above example is for illustrative purposes and relying on `Result::Ok` or
|
||||
//! `Result::Err` can lead to incorrect behavior. This will be clarified in later when covering
|
||||
//! [error handling][`chapter_7`#error-cuts]
|
||||
//!
|
||||
//! </div>
|
||||
//!
|
||||
//! [`opt`] is a parser that encapsulates this pattern of "retry on failure":
|
||||
//! ```rust
|
||||
//! # use winnow::prelude::*;
|
||||
//! # use winnow::Result;
|
||||
//! # use winnow::token::take_while;
|
||||
//! use winnow::combinator::opt;
|
||||
//!
|
||||
//! fn parse_digits<'s>(input: &mut &'s str) -> Result<(&'s str, &'s str)> {
|
||||
//! if let Some(output) = opt(("0b", parse_bin_digits)).parse_next(input)? {
|
||||
//! Ok(output)
|
||||
//! } else if let Some(output) = opt(("0o", parse_oct_digits)).parse_next(input)? {
|
||||
//! Ok(output)
|
||||
//! } else if let Some(output) = opt(("0d", parse_dec_digits)).parse_next(input)? {
|
||||
//! Ok(output)
|
||||
//! } else {
|
||||
//! ("0x", parse_hex_digits).parse_next(input)
|
||||
//! }
|
||||
//! }
|
||||
//! #
|
||||
//! # fn parse_bin_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='1'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_oct_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='7'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_dec_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='9'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_hex_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='9'),
|
||||
//! # ('A'..='F'),
|
||||
//! # ('a'..='f'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn main() {
|
||||
//! # let mut input = "0x1a2b Hello";
|
||||
//! #
|
||||
//! # let (prefix, digits) = parse_digits.parse_next(&mut input).unwrap();
|
||||
//! #
|
||||
//! # assert_eq!(input, " Hello");
|
||||
//! # assert_eq!(prefix, "0x");
|
||||
//! # assert_eq!(digits, "1a2b");
|
||||
//! #
|
||||
//! # assert!(parse_digits(&mut "ghiWorld").is_err());
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! [`alt`] encapsulates this if/else-if ladder pattern, with the last case being the "else":
|
||||
//! ```rust
|
||||
//! # use winnow::prelude::*;
|
||||
//! # use winnow::Result;
|
||||
//! # use winnow::token::take_while;
|
||||
//! use winnow::combinator::alt;
|
||||
//!
|
||||
//! fn parse_digits<'s>(input: &mut &'s str) -> Result<(&'s str, &'s str)> {
|
||||
//! alt((
|
||||
//! ("0b", parse_bin_digits),
|
||||
//! ("0o", parse_oct_digits),
|
||||
//! ("0d", parse_dec_digits),
|
||||
//! ("0x", parse_hex_digits),
|
||||
//! )).parse_next(input)
|
||||
//! }
|
||||
//! #
|
||||
//! # fn parse_bin_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='1'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_oct_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='7'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_dec_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='9'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_hex_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='9'),
|
||||
//! # ('A'..='F'),
|
||||
//! # ('a'..='f'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn main() {
|
||||
//! # let mut input = "0x1a2b Hello";
|
||||
//! #
|
||||
//! # let (prefix, digits) = parse_digits.parse_next(&mut input).unwrap();
|
||||
//! #
|
||||
//! # assert_eq!(input, " Hello");
|
||||
//! # assert_eq!(prefix, "0x");
|
||||
//! # assert_eq!(digits, "1a2b");
|
||||
//! #
|
||||
//! # assert!(parse_digits(&mut "ghiWorld").is_err());
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! <div class="warning">
|
||||
//!
|
||||
//! **Note:** [`empty`] and [`fail`] are parsers that might be useful in the "else" case.
|
||||
//!
|
||||
//! </div>
|
||||
//!
|
||||
//! Sometimes a giant if/else-if ladder can be slow and you'd rather have a `match` statement for
|
||||
//! branches of your parser that have unique prefixes. In this case, you can use the
|
||||
//! [`dispatch`] macro:
|
||||
//!
|
||||
//! ```rust
|
||||
//! # use winnow::prelude::*;
|
||||
//! # use winnow::Result;
|
||||
//! # use winnow::token::take_while;
|
||||
//! use winnow::combinator::dispatch;
|
||||
//! use winnow::token::take;
|
||||
//! use winnow::combinator::fail;
|
||||
//!
|
||||
//! fn parse_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! dispatch!(take(2usize);
|
||||
//! "0b" => parse_bin_digits,
|
||||
//! "0o" => parse_oct_digits,
|
||||
//! "0d" => parse_dec_digits,
|
||||
//! "0x" => parse_hex_digits,
|
||||
//! _ => fail,
|
||||
//! ).parse_next(input)
|
||||
//! }
|
||||
//! #
|
||||
//! # fn parse_bin_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='1'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_oct_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='7'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_dec_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='9'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_hex_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='9'),
|
||||
//! # ('A'..='F'),
|
||||
//! # ('a'..='f'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn main() {
|
||||
//! # let mut input = "0x1a2b Hello";
|
||||
//! #
|
||||
//! # let digits = parse_digits.parse_next(&mut input).unwrap();
|
||||
//! #
|
||||
//! # assert_eq!(input, " Hello");
|
||||
//! # assert_eq!(digits, "1a2b");
|
||||
//! #
|
||||
//! # assert!(parse_digits(&mut "ghiWorld").is_err());
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! <div class="warning">
|
||||
//!
|
||||
//! **Note:** [`peek`] may be useful when [`dispatch`]ing from hints from each case's parser.
|
||||
//!
|
||||
//! </div>
|
||||
//!
|
||||
//! See [`combinator`] for more alternative parsers.
|
||||
|
||||
#![allow(unused_imports)]
|
||||
use super::chapter_7;
|
||||
use crate::combinator;
|
||||
use crate::combinator::alt;
|
||||
use crate::combinator::dispatch;
|
||||
use crate::combinator::empty;
|
||||
use crate::combinator::fail;
|
||||
use crate::combinator::opt;
|
||||
use crate::combinator::peek;
|
||||
use crate::combinator::preceded;
|
||||
use crate::stream::Stream;
|
||||
|
||||
pub use super::chapter_2 as previous;
|
||||
pub use super::chapter_4 as next;
|
||||
pub use crate::_tutorial as table_of_contents;
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
//! # Chapter 4: Parsers With Custom Return Types
|
||||
//!
|
||||
//! So far, we have seen mostly functions that take an `&str`, and return a
|
||||
//! [`Result<&str>`]. Splitting strings into smaller strings and characters is certainly
|
||||
//! useful, but it's not the only thing winnow is capable of!
|
||||
//!
|
||||
//! A useful operation when parsing is to convert between types; for example
|
||||
//! parsing from `&str` to another primitive, like [`usize`].
|
||||
//!
|
||||
//! All we need to do for our parser to return a different type is to change
|
||||
//! the type parameter of [`Result`] to the desired return type.
|
||||
//! For example, to return a `usize`, return a `Result<usize>`.
|
||||
//!
|
||||
//! One winnow-native way of doing a type conversion is to use the
|
||||
//! [`Parser::parse_to`] combinator
|
||||
//! to convert from a successful parse to a particular type using [`FromStr`].
|
||||
//!
|
||||
//! The following code converts from a string containing a number to `usize`:
|
||||
//! ```rust
|
||||
//! # use winnow::prelude::*;
|
||||
//! # use winnow::Result;
|
||||
//! # use winnow::ascii::digit1;
|
||||
//! #
|
||||
//! fn parse_digits(input: &mut &str) -> Result<usize> {
|
||||
//! digit1
|
||||
//! .parse_to()
|
||||
//! .parse_next(input)
|
||||
//! }
|
||||
//!
|
||||
//! fn main() {
|
||||
//! let mut input = "1024 Hello";
|
||||
//!
|
||||
//! let output = parse_digits.parse_next(&mut input).unwrap();
|
||||
//! assert_eq!(input, " Hello");
|
||||
//! assert_eq!(output, 1024);
|
||||
//!
|
||||
//! assert!(parse_digits(&mut "Z").is_err());
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! `Parser::parse_to` is just a convenient form of [`Parser::try_map`] which we can use to handle
|
||||
//! all radices of numbers:
|
||||
//! ```rust
|
||||
//! # use winnow::prelude::*;
|
||||
//! # use winnow::Result;
|
||||
//! # use winnow::token::take_while;
|
||||
//! use winnow::combinator::dispatch;
|
||||
//! use winnow::token::take;
|
||||
//! use winnow::combinator::fail;
|
||||
//!
|
||||
//! fn parse_digits(input: &mut &str) -> Result<usize> {
|
||||
//! dispatch!(take(2usize);
|
||||
//! "0b" => parse_bin_digits.try_map(|s| usize::from_str_radix(s, 2)),
|
||||
//! "0o" => parse_oct_digits.try_map(|s| usize::from_str_radix(s, 8)),
|
||||
//! "0d" => parse_dec_digits.try_map(|s| usize::from_str_radix(s, 10)),
|
||||
//! "0x" => parse_hex_digits.try_map(|s| usize::from_str_radix(s, 16)),
|
||||
//! _ => fail,
|
||||
//! ).parse_next(input)
|
||||
//! }
|
||||
//!
|
||||
//! // ...
|
||||
//! # fn parse_bin_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='1'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_oct_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='7'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_dec_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='9'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_hex_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='9'),
|
||||
//! # ('A'..='F'),
|
||||
//! # ('a'..='f'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//!
|
||||
//! fn main() {
|
||||
//! let mut input = "0x1a2b Hello";
|
||||
//!
|
||||
//! let digits = parse_digits.parse_next(&mut input).unwrap();
|
||||
//!
|
||||
//! assert_eq!(input, " Hello");
|
||||
//! assert_eq!(digits, 0x1a2b);
|
||||
//!
|
||||
//! assert!(parse_digits(&mut "ghiWorld").is_err());
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! See also [`Parser`] for more output-modifying parsers.
|
||||
|
||||
#![allow(unused_imports)]
|
||||
use crate::Parser;
|
||||
use crate::Result;
|
||||
use std::str::FromStr;
|
||||
|
||||
pub use super::chapter_3 as previous;
|
||||
pub use super::chapter_5 as next;
|
||||
pub use crate::_tutorial as table_of_contents;
|
||||
+300
@@ -0,0 +1,300 @@
|
||||
//! # Chapter 5: Repetition
|
||||
//!
|
||||
//! In [`chapter_3`], we covered how to sequence different parsers into a tuple but sometimes you need to run a
|
||||
//! single parser multiple times, collecting the result into a container, like `Vec`.
|
||||
//!
|
||||
//! Let's collect the result of `parse_digits`:
|
||||
//! ```rust
|
||||
//! # use winnow::prelude::*;
|
||||
//! # use winnow::Result;
|
||||
//! # use winnow::token::take_while;
|
||||
//! # use winnow::combinator::dispatch;
|
||||
//! # use winnow::token::take;
|
||||
//! # use winnow::combinator::fail;
|
||||
//! use winnow::combinator::opt;
|
||||
//! use winnow::combinator::repeat;
|
||||
//! use winnow::combinator::terminated;
|
||||
//!
|
||||
//! fn parse_list(input: &mut &str) -> Result<Vec<usize>> {
|
||||
//! let mut list = Vec::new();
|
||||
//! while let Some(output) = opt(terminated(parse_digits, opt(','))).parse_next(input)? {
|
||||
//! list.push(output);
|
||||
//! }
|
||||
//! Ok(list)
|
||||
//! }
|
||||
//!
|
||||
//! // ...
|
||||
//! # fn parse_digits(input: &mut &str) -> Result<usize> {
|
||||
//! # dispatch!(take(2usize);
|
||||
//! # "0b" => parse_bin_digits.try_map(|s| usize::from_str_radix(s, 2)),
|
||||
//! # "0o" => parse_oct_digits.try_map(|s| usize::from_str_radix(s, 8)),
|
||||
//! # "0d" => parse_dec_digits.try_map(|s| usize::from_str_radix(s, 10)),
|
||||
//! # "0x" => parse_hex_digits.try_map(|s| usize::from_str_radix(s, 16)),
|
||||
//! # _ => fail,
|
||||
//! # ).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_bin_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='1'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_oct_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='7'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_dec_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='9'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_hex_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='9'),
|
||||
//! # ('A'..='F'),
|
||||
//! # ('a'..='f'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//!
|
||||
//! fn main() {
|
||||
//! let mut input = "0x1a2b,0x3c4d,0x5e6f Hello";
|
||||
//!
|
||||
//! let digits = parse_list.parse_next(&mut input).unwrap();
|
||||
//!
|
||||
//! assert_eq!(input, " Hello");
|
||||
//! assert_eq!(digits, vec![0x1a2b, 0x3c4d, 0x5e6f]);
|
||||
//!
|
||||
//! assert!(parse_digits(&mut "ghiWorld").is_err());
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! We can implement this declaratively with [`repeat`]:
|
||||
//! ```rust
|
||||
//! # use winnow::prelude::*;
|
||||
//! # use winnow::Result;
|
||||
//! # use winnow::token::take_while;
|
||||
//! # use winnow::combinator::dispatch;
|
||||
//! # use winnow::token::take;
|
||||
//! # use winnow::combinator::fail;
|
||||
//! use winnow::combinator::opt;
|
||||
//! use winnow::combinator::repeat;
|
||||
//! use winnow::combinator::terminated;
|
||||
//!
|
||||
//! fn parse_list(input: &mut &str) -> Result<Vec<usize>> {
|
||||
//! repeat(0..,
|
||||
//! terminated(parse_digits, opt(','))
|
||||
//! ).parse_next(input)
|
||||
//! }
|
||||
//! #
|
||||
//! # fn parse_digits(input: &mut &str) -> Result<usize> {
|
||||
//! # dispatch!(take(2usize);
|
||||
//! # "0b" => parse_bin_digits.try_map(|s| usize::from_str_radix(s, 2)),
|
||||
//! # "0o" => parse_oct_digits.try_map(|s| usize::from_str_radix(s, 8)),
|
||||
//! # "0d" => parse_dec_digits.try_map(|s| usize::from_str_radix(s, 10)),
|
||||
//! # "0x" => parse_hex_digits.try_map(|s| usize::from_str_radix(s, 16)),
|
||||
//! # _ => fail,
|
||||
//! # ).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_bin_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='1'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_oct_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='7'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_dec_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='9'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_hex_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='9'),
|
||||
//! # ('A'..='F'),
|
||||
//! # ('a'..='f'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn main() {
|
||||
//! # let mut input = "0x1a2b,0x3c4d,0x5e6f Hello";
|
||||
//! #
|
||||
//! # let digits = parse_list.parse_next(&mut input).unwrap();
|
||||
//! #
|
||||
//! # assert_eq!(input, " Hello");
|
||||
//! # assert_eq!(digits, vec![0x1a2b, 0x3c4d, 0x5e6f]);
|
||||
//! #
|
||||
//! # assert!(parse_digits(&mut "ghiWorld").is_err());
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! You'll notice that the above allows trailing `,`. However, if that's not desired, it
|
||||
//! can easily be fixed by using [`separated`] instead of [`repeat`]:
|
||||
//! ```rust
|
||||
//! # use winnow::prelude::*;
|
||||
//! # use winnow::Result;
|
||||
//! # use winnow::token::take_while;
|
||||
//! # use winnow::combinator::dispatch;
|
||||
//! # use winnow::token::take;
|
||||
//! # use winnow::combinator::fail;
|
||||
//! use winnow::combinator::separated;
|
||||
//!
|
||||
//! fn parse_list(input: &mut &str) -> Result<Vec<usize>> {
|
||||
//! separated(0.., parse_digits, ",").parse_next(input)
|
||||
//! }
|
||||
//!
|
||||
//! // ...
|
||||
//! # fn parse_digits(input: &mut &str) -> Result<usize> {
|
||||
//! # dispatch!(take(2usize);
|
||||
//! # "0b" => parse_bin_digits.try_map(|s| usize::from_str_radix(s, 2)),
|
||||
//! # "0o" => parse_oct_digits.try_map(|s| usize::from_str_radix(s, 8)),
|
||||
//! # "0d" => parse_dec_digits.try_map(|s| usize::from_str_radix(s, 10)),
|
||||
//! # "0x" => parse_hex_digits.try_map(|s| usize::from_str_radix(s, 16)),
|
||||
//! # _ => fail,
|
||||
//! # ).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_bin_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='1'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_oct_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='7'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_dec_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='9'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_hex_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='9'),
|
||||
//! # ('A'..='F'),
|
||||
//! # ('a'..='f'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//!
|
||||
//! fn main() {
|
||||
//! let mut input = "0x1a2b,0x3c4d,0x5e6f Hello";
|
||||
//!
|
||||
//! let digits = parse_list.parse_next(&mut input).unwrap();
|
||||
//!
|
||||
//! assert_eq!(input, " Hello");
|
||||
//! assert_eq!(digits, vec![0x1a2b, 0x3c4d, 0x5e6f]);
|
||||
//!
|
||||
//! assert!(parse_digits(&mut "ghiWorld").is_err());
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! If you look closely at [`separated`] and [`repeat`], they aren't limited to collecting
|
||||
//! the result into a `Vec`, but rather anything that implements the [`Accumulate`] trait.
|
||||
//! [`Accumulate`] is for instance also implemented for [`HashSet`], [`String`] and `()`.
|
||||
//!
|
||||
//! This lets us build more complex parsers than we did in
|
||||
//! [`chapter_2`] by accumulating the results into a `()` and [`take`][Parser::take]-ing
|
||||
//! the consumed input.
|
||||
//!
|
||||
//! `take` works by
|
||||
//! 1. Creating a [`checkpoint`][Stream::checkpoint]
|
||||
//! 2. Running the inner parser, in our case the `parse_list` parser, which will advance the input
|
||||
//! 3. Returning the slice from the first checkpoint to the current position.
|
||||
//!
|
||||
//! Since the result of `parse_list` gets thrown away, we
|
||||
//! accumulates into a `()` to not waste work creating an unused `Vec`.
|
||||
//!
|
||||
//! ```rust
|
||||
//! # use winnow::prelude::*;
|
||||
//! # use winnow::Result;
|
||||
//! # use winnow::token::take_while;
|
||||
//! # use winnow::combinator::dispatch;
|
||||
//! # use winnow::token::take;
|
||||
//! # use winnow::combinator::fail;
|
||||
//! # use winnow::combinator::separated;
|
||||
//! #
|
||||
//! fn take_list<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! parse_list.take().parse_next(input)
|
||||
//! }
|
||||
//!
|
||||
//! fn parse_list(input: &mut &str) -> Result<()> {
|
||||
//! separated(0.., parse_digits, ",").parse_next(input)
|
||||
//! }
|
||||
//!
|
||||
//! # fn parse_digits(input: &mut &str) -> Result<usize> {
|
||||
//! # dispatch!(take(2usize);
|
||||
//! # "0b" => parse_bin_digits.try_map(|s| usize::from_str_radix(s, 2)),
|
||||
//! # "0o" => parse_oct_digits.try_map(|s| usize::from_str_radix(s, 8)),
|
||||
//! # "0d" => parse_dec_digits.try_map(|s| usize::from_str_radix(s, 10)),
|
||||
//! # "0x" => parse_hex_digits.try_map(|s| usize::from_str_radix(s, 16)),
|
||||
//! # _ => fail,
|
||||
//! # ).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_bin_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='1'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_oct_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='7'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_dec_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='9'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_hex_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='9'),
|
||||
//! # ('A'..='F'),
|
||||
//! # ('a'..='f'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//!
|
||||
//! fn main() {
|
||||
//! let mut input = "0x1a2b,0x3c4d,0x5e6f Hello";
|
||||
//!
|
||||
//! let digits = take_list.parse_next(&mut input).unwrap();
|
||||
//!
|
||||
//! assert_eq!(input, " Hello");
|
||||
//! assert_eq!(digits, "0x1a2b,0x3c4d,0x5e6f");
|
||||
//!
|
||||
//! assert!(parse_digits(&mut "ghiWorld").is_err());
|
||||
//! }
|
||||
//! ```
|
||||
//! See [`combinator`] for more repetition parsers.
|
||||
|
||||
#![allow(unused_imports)]
|
||||
use super::chapter_2;
|
||||
use super::chapter_3;
|
||||
use crate::combinator;
|
||||
use crate::combinator::repeat;
|
||||
use crate::combinator::separated;
|
||||
use crate::stream::Accumulate;
|
||||
use crate::stream::Stream;
|
||||
use crate::Parser;
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub use super::chapter_4 as previous;
|
||||
pub use super::chapter_6 as next;
|
||||
pub use crate::_tutorial as table_of_contents;
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
//! # Chapter 6: Integrating the Parser
|
||||
//!
|
||||
//! So far, we've highlighted how to incrementally parse, but how do we bring this all together
|
||||
//! into our application?
|
||||
//!
|
||||
//! Parsers we've been working with look like:
|
||||
//! ```rust
|
||||
//! # use winnow::error::ContextError;
|
||||
//! # use winnow::Parser;
|
||||
//! use winnow::Result;
|
||||
//!
|
||||
//! pub fn parser<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! // ...
|
||||
//! # Ok("")
|
||||
//! }
|
||||
//! ```
|
||||
//! 1. We have to decide what to do about the "remainder" of the `input`.
|
||||
//! 2. The [`Result`] may not be compatible with the rest of the Rust ecosystem.
|
||||
//! Normally, Rust applications want errors that are `std::error::Error + Send + Sync + 'static`
|
||||
//! meaning:
|
||||
//! - They implement the [`std::error::Error`] trait
|
||||
//! - They can be sent across threads
|
||||
//! - They are safe to be referenced across threads
|
||||
//! - They do not borrow
|
||||
//!
|
||||
//! winnow provides [`Parser::parse`] to help with this:
|
||||
//! - Ensures we hit [`eof`]
|
||||
//! - Wraps the error in [`ParseError`]
|
||||
//! - For simple cases, [`ParseError`] provides a [`std::fmt::Display`] impl to render the error.
|
||||
//! - For more involved cases, [`ParseError`] provides the original [`input`][ParseError::input] and the
|
||||
//! [`offset`][ParseError::offset] of where it failed so you can capture this information in
|
||||
//! your error, [rendering it as you wish][chapter_7#error-adaptation-and-rendering].
|
||||
//! - Converts from [`ModalResult`] to [`Result`] (if used, more on this in [`chapter_7`])
|
||||
//!
|
||||
//! However, [`ParseError`] will still need some level of adaptation to integrate with your
|
||||
//! application's error type (like with `?`).
|
||||
//!
|
||||
//! ```rust
|
||||
//! # use winnow::prelude::*;
|
||||
//! # use winnow::Result;
|
||||
//! # use winnow::token::take_while;
|
||||
//! # use winnow::combinator::dispatch;
|
||||
//! # use winnow::token::take;
|
||||
//! # use winnow::combinator::fail;
|
||||
//! use winnow::Parser;
|
||||
//!
|
||||
//! #[derive(Debug, PartialEq, Eq)]
|
||||
//! pub struct Hex(usize);
|
||||
//!
|
||||
//! impl std::str::FromStr for Hex {
|
||||
//! type Err = anyhow::Error;
|
||||
//!
|
||||
//! fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
//! parse_digits
|
||||
//! .map(Hex)
|
||||
//! .parse(input)
|
||||
//! .map_err(|e| anyhow::format_err!("{e}"))
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! // ...
|
||||
//! # fn parse_digits<'s>(input: &mut &'s str) -> Result<usize> {
|
||||
//! # dispatch!(take(2usize);
|
||||
//! # "0b" => parse_bin_digits.try_map(|s| usize::from_str_radix(s, 2)),
|
||||
//! # "0o" => parse_oct_digits.try_map(|s| usize::from_str_radix(s, 8)),
|
||||
//! # "0d" => parse_dec_digits.try_map(|s| usize::from_str_radix(s, 10)),
|
||||
//! # "0x" => parse_hex_digits.try_map(|s| usize::from_str_radix(s, 16)),
|
||||
//! # _ => fail,
|
||||
//! # ).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_bin_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='1'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_oct_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='7'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_dec_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='9'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_hex_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='9'),
|
||||
//! # ('A'..='F'),
|
||||
//! # ('a'..='f'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//!
|
||||
//! fn main() {
|
||||
//! let input = "0x1a2b";
|
||||
//! assert_eq!(input.parse::<Hex>().unwrap(), Hex(0x1a2b));
|
||||
//!
|
||||
//! let input = "0x1a2b Hello";
|
||||
//! assert!(input.parse::<Hex>().is_err());
|
||||
//! let input = "ghiHello";
|
||||
//! assert!(input.parse::<Hex>().is_err());
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
#![allow(unused_imports)]
|
||||
use super::chapter_1;
|
||||
use super::chapter_7;
|
||||
use crate::combinator::eof;
|
||||
use crate::error::ErrMode;
|
||||
use crate::error::ParseError;
|
||||
use crate::ModalResult;
|
||||
use crate::Parser;
|
||||
|
||||
pub use super::chapter_5 as previous;
|
||||
pub use super::chapter_7 as next;
|
||||
pub use crate::_tutorial as table_of_contents;
|
||||
+736
@@ -0,0 +1,736 @@
|
||||
//! # Chapter 7: Error Reporting
|
||||
//!
|
||||
//! ## Context
|
||||
//!
|
||||
//! With [`Parser::parse`] we get errors that point to the failure but don't explain the reason for
|
||||
//! the failure:
|
||||
//! ```rust
|
||||
//! # use winnow::prelude::*;
|
||||
//! # use winnow::Result;
|
||||
//! # use winnow::token::take_while;
|
||||
//! # use winnow::combinator::alt;
|
||||
//! # use winnow::token::take;
|
||||
//! # use winnow::combinator::fail;
|
||||
//! # use winnow::Parser;
|
||||
//! #
|
||||
//! # #[derive(Debug, PartialEq, Eq)]
|
||||
//! # pub struct Hex(usize);
|
||||
//! #
|
||||
//! # impl std::str::FromStr for Hex {
|
||||
//! # type Err = String;
|
||||
//! #
|
||||
//! # fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
//! # parse_digits
|
||||
//! # .try_map(|(t, v)| match t {
|
||||
//! # "0b" => usize::from_str_radix(v, 2),
|
||||
//! # "0o" => usize::from_str_radix(v, 8),
|
||||
//! # "0d" => usize::from_str_radix(v, 10),
|
||||
//! # "0x" => usize::from_str_radix(v, 16),
|
||||
//! # _ => unreachable!("`parse_digits` doesn't return `{t}`"),
|
||||
//! # })
|
||||
//! # .map(Hex)
|
||||
//! # .parse(input)
|
||||
//! # .map_err(|e| e.to_string())
|
||||
//! # }
|
||||
//! # }
|
||||
//! #
|
||||
//! // ...
|
||||
//!
|
||||
//! # fn parse_digits<'s>(input: &mut &'s str) -> Result<(&'s str, &'s str)> {
|
||||
//! # alt((
|
||||
//! # ("0b", parse_bin_digits),
|
||||
//! # ("0o", parse_oct_digits),
|
||||
//! # ("0d", parse_dec_digits),
|
||||
//! # ("0x", parse_hex_digits),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_bin_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='1'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_oct_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='7'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_dec_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='9'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_hex_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='9'),
|
||||
//! # ('A'..='F'),
|
||||
//! # ('a'..='f'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! fn main() {
|
||||
//! let input = "0xZZ";
|
||||
//! let error = "\
|
||||
//! 0xZZ
|
||||
//! ^
|
||||
//! ";
|
||||
//! assert_eq!(input.parse::<Hex>().unwrap_err(), error);
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! Back in [`chapter_1`], we glossed over the `Err` variant of [`Result`]. `Result<O>` is
|
||||
//! actually short for `Result<O, E=ContextError>` where [`ContextError`] is a relatively cheap
|
||||
//! way of building up reasonable errors for humans.
|
||||
//!
|
||||
//! You can use [`Parser::context`] to annotate the error with custom types
|
||||
//! while unwinding to further clarify the error:
|
||||
//! ```rust
|
||||
//! # use winnow::prelude::*;
|
||||
//! # use winnow::Result;
|
||||
//! # use winnow::token::take_while;
|
||||
//! # use winnow::combinator::alt;
|
||||
//! # use winnow::token::take;
|
||||
//! # use winnow::combinator::fail;
|
||||
//! # use winnow::Parser;
|
||||
//! use winnow::error::StrContext;
|
||||
//! use winnow::error::StrContextValue;
|
||||
//!
|
||||
//! #
|
||||
//! # #[derive(Debug, PartialEq, Eq)]
|
||||
//! # pub struct Hex(usize);
|
||||
//! #
|
||||
//! # impl std::str::FromStr for Hex {
|
||||
//! # type Err = String;
|
||||
//! #
|
||||
//! # fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
//! # parse_digits
|
||||
//! # .try_map(|(t, v)| match t {
|
||||
//! # "0b" => usize::from_str_radix(v, 2),
|
||||
//! # "0o" => usize::from_str_radix(v, 8),
|
||||
//! # "0d" => usize::from_str_radix(v, 10),
|
||||
//! # "0x" => usize::from_str_radix(v, 16),
|
||||
//! # _ => unreachable!("`parse_digits` doesn't return `{t}`"),
|
||||
//! # })
|
||||
//! # .map(Hex)
|
||||
//! # .parse(input)
|
||||
//! # .map_err(|e| e.to_string())
|
||||
//! # }
|
||||
//! # }
|
||||
//! #
|
||||
//! fn parse_digits<'s>(input: &mut &'s str) -> Result<(&'s str, &'s str)> {
|
||||
//! alt((
|
||||
//! ("0b", parse_bin_digits)
|
||||
//! .context(StrContext::Label("digit"))
|
||||
//! .context(StrContext::Expected(StrContextValue::Description("binary"))),
|
||||
//! ("0o", parse_oct_digits)
|
||||
//! .context(StrContext::Label("digit"))
|
||||
//! .context(StrContext::Expected(StrContextValue::Description("octal"))),
|
||||
//! ("0d", parse_dec_digits)
|
||||
//! .context(StrContext::Label("digit"))
|
||||
//! .context(StrContext::Expected(StrContextValue::Description("decimal"))),
|
||||
//! ("0x", parse_hex_digits)
|
||||
//! .context(StrContext::Label("digit"))
|
||||
//! .context(StrContext::Expected(StrContextValue::Description("hexadecimal"))),
|
||||
//! )).parse_next(input)
|
||||
//! }
|
||||
//!
|
||||
//! // ...
|
||||
//!
|
||||
//! #
|
||||
//! # fn parse_bin_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='1'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_oct_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='7'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_dec_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='9'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_hex_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='9'),
|
||||
//! # ('A'..='F'),
|
||||
//! # ('a'..='f'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! fn main() {
|
||||
//! let input = "0xZZ";
|
||||
//! let error = "\
|
||||
//! 0xZZ
|
||||
//! ^
|
||||
//! invalid digit
|
||||
//! expected hexadecimal";
|
||||
//! assert_eq!(input.parse::<Hex>().unwrap_err(), error);
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! If you remember back to [`chapter_3`], [`alt`] will only report the last error.
|
||||
//! So if the parsers fail for any reason, like a bad radix, it will be reported as an invalid
|
||||
//! hexadecimal value:
|
||||
//! ```rust
|
||||
//! # use winnow::prelude::*;
|
||||
//! # use winnow::Result;
|
||||
//! # use winnow::token::take_while;
|
||||
//! # use winnow::combinator::alt;
|
||||
//! # use winnow::token::take;
|
||||
//! # use winnow::combinator::fail;
|
||||
//! # use winnow::Parser;
|
||||
//! # use winnow::error::StrContext;
|
||||
//! # use winnow::error::StrContextValue;
|
||||
//! #
|
||||
//! #
|
||||
//! # #[derive(Debug, PartialEq, Eq)]
|
||||
//! # pub struct Hex(usize);
|
||||
//! #
|
||||
//! # impl std::str::FromStr for Hex {
|
||||
//! # type Err = String;
|
||||
//! #
|
||||
//! # fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
//! # parse_digits
|
||||
//! # .try_map(|(t, v)| match t {
|
||||
//! # "0b" => usize::from_str_radix(v, 2),
|
||||
//! # "0o" => usize::from_str_radix(v, 8),
|
||||
//! # "0d" => usize::from_str_radix(v, 10),
|
||||
//! # "0x" => usize::from_str_radix(v, 16),
|
||||
//! # _ => unreachable!("`parse_digits` doesn't return `{t}`"),
|
||||
//! # })
|
||||
//! # .map(Hex)
|
||||
//! # .parse(input)
|
||||
//! # .map_err(|e| e.to_string())
|
||||
//! # }
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_digits<'s>(input: &mut &'s str) -> Result<(&'s str, &'s str)> {
|
||||
//! # alt((
|
||||
//! # ("0b", parse_bin_digits)
|
||||
//! # .context(StrContext::Label("digit"))
|
||||
//! # .context(StrContext::Expected(StrContextValue::Description("binary"))),
|
||||
//! # ("0o", parse_oct_digits)
|
||||
//! # .context(StrContext::Label("digit"))
|
||||
//! # .context(StrContext::Expected(StrContextValue::Description("octal"))),
|
||||
//! # ("0d", parse_dec_digits)
|
||||
//! # .context(StrContext::Label("digit"))
|
||||
//! # .context(StrContext::Expected(StrContextValue::Description("decimal"))),
|
||||
//! # ("0x", parse_hex_digits)
|
||||
//! # .context(StrContext::Label("digit"))
|
||||
//! # .context(StrContext::Expected(StrContextValue::Description("hexadecimal"))),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_bin_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='1'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_oct_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='7'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_dec_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='9'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_hex_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='9'),
|
||||
//! # ('A'..='F'),
|
||||
//! # ('a'..='f'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! fn main() {
|
||||
//! let input = "100";
|
||||
//! let error = "\
|
||||
//! 100
|
||||
//! ^
|
||||
//! invalid digit
|
||||
//! expected hexadecimal";
|
||||
//! assert_eq!(input.parse::<Hex>().unwrap_err(), error);
|
||||
//! }
|
||||
//! ```
|
||||
//! We can improve this with [`fail`]:
|
||||
//! ```rust
|
||||
//! # use winnow::prelude::*;
|
||||
//! # use winnow::Result;
|
||||
//! # use winnow::token::take_while;
|
||||
//! # use winnow::combinator::alt;
|
||||
//! # use winnow::token::take;
|
||||
//! # use winnow::combinator::fail;
|
||||
//! # use winnow::Parser;
|
||||
//! use winnow::error::StrContext;
|
||||
//! use winnow::error::StrContextValue;
|
||||
//!
|
||||
//! #
|
||||
//! # #[derive(Debug, PartialEq, Eq)]
|
||||
//! # pub struct Hex(usize);
|
||||
//! #
|
||||
//! # impl std::str::FromStr for Hex {
|
||||
//! # type Err = String;
|
||||
//! #
|
||||
//! # fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
//! # parse_digits
|
||||
//! # .try_map(|(t, v)| match t {
|
||||
//! # "0b" => usize::from_str_radix(v, 2),
|
||||
//! # "0o" => usize::from_str_radix(v, 8),
|
||||
//! # "0d" => usize::from_str_radix(v, 10),
|
||||
//! # "0x" => usize::from_str_radix(v, 16),
|
||||
//! # _ => unreachable!("`parse_digits` doesn't return `{t}`"),
|
||||
//! # })
|
||||
//! # .map(Hex)
|
||||
//! # .parse(input)
|
||||
//! # .map_err(|e| e.to_string())
|
||||
//! # }
|
||||
//! # }
|
||||
//! #
|
||||
//! fn parse_digits<'s>(input: &mut &'s str) -> Result<(&'s str, &'s str)> {
|
||||
//! alt((
|
||||
//! ("0b", parse_bin_digits)
|
||||
//! .context(StrContext::Label("digit"))
|
||||
//! .context(StrContext::Expected(StrContextValue::Description("binary"))),
|
||||
//! ("0o", parse_oct_digits)
|
||||
//! .context(StrContext::Label("digit"))
|
||||
//! .context(StrContext::Expected(StrContextValue::Description("octal"))),
|
||||
//! ("0d", parse_dec_digits)
|
||||
//! .context(StrContext::Label("digit"))
|
||||
//! .context(StrContext::Expected(StrContextValue::Description("decimal"))),
|
||||
//! ("0x", parse_hex_digits)
|
||||
//! .context(StrContext::Label("digit"))
|
||||
//! .context(StrContext::Expected(StrContextValue::Description("hexadecimal"))),
|
||||
//! fail
|
||||
//! .context(StrContext::Label("radix prefix"))
|
||||
//! .context(StrContext::Expected(StrContextValue::StringLiteral("0b")))
|
||||
//! .context(StrContext::Expected(StrContextValue::StringLiteral("0o")))
|
||||
//! .context(StrContext::Expected(StrContextValue::StringLiteral("0d")))
|
||||
//! .context(StrContext::Expected(StrContextValue::StringLiteral("0x"))),
|
||||
//! )).parse_next(input)
|
||||
//! }
|
||||
//!
|
||||
//! // ...
|
||||
//!
|
||||
//! #
|
||||
//! # fn parse_bin_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='1'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_oct_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='7'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_dec_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='9'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_hex_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='9'),
|
||||
//! # ('A'..='F'),
|
||||
//! # ('a'..='f'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! fn main() {
|
||||
//! let input = "100";
|
||||
//! let error = "\
|
||||
//! 100
|
||||
//! ^
|
||||
//! invalid radix prefix
|
||||
//! expected `0b`, `0o`, `0d`, `0x`";
|
||||
//! assert_eq!(input.parse::<Hex>().unwrap_err(), error);
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Error Cuts
|
||||
//!
|
||||
//! We still have the issue that we are falling-through when the radix is valid but the digits
|
||||
//! don't match it:
|
||||
//! ```rust
|
||||
//! # use winnow::prelude::*;
|
||||
//! # use winnow::Result;
|
||||
//! # use winnow::token::take_while;
|
||||
//! # use winnow::combinator::alt;
|
||||
//! # use winnow::token::take;
|
||||
//! # use winnow::combinator::fail;
|
||||
//! # use winnow::Parser;
|
||||
//! # use winnow::error::StrContext;
|
||||
//! # use winnow::error::StrContextValue;
|
||||
//! #
|
||||
//! #
|
||||
//! # #[derive(Debug, PartialEq, Eq)]
|
||||
//! # pub struct Hex(usize);
|
||||
//! #
|
||||
//! # impl std::str::FromStr for Hex {
|
||||
//! # type Err = String;
|
||||
//! #
|
||||
//! # fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
//! # parse_digits
|
||||
//! # .try_map(|(t, v)| match t {
|
||||
//! # "0b" => usize::from_str_radix(v, 2),
|
||||
//! # "0o" => usize::from_str_radix(v, 8),
|
||||
//! # "0d" => usize::from_str_radix(v, 10),
|
||||
//! # "0x" => usize::from_str_radix(v, 16),
|
||||
//! # _ => unreachable!("`parse_digits` doesn't return `{t}`"),
|
||||
//! # })
|
||||
//! # .map(Hex)
|
||||
//! # .parse(input)
|
||||
//! # .map_err(|e| e.to_string())
|
||||
//! # }
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_digits<'s>(input: &mut &'s str) -> Result<(&'s str, &'s str)> {
|
||||
//! # alt((
|
||||
//! # ("0b", parse_bin_digits)
|
||||
//! # .context(StrContext::Label("digit"))
|
||||
//! # .context(StrContext::Expected(StrContextValue::Description("binary"))),
|
||||
//! # ("0o", parse_oct_digits)
|
||||
//! # .context(StrContext::Label("digit"))
|
||||
//! # .context(StrContext::Expected(StrContextValue::Description("octal"))),
|
||||
//! # ("0d", parse_dec_digits)
|
||||
//! # .context(StrContext::Label("digit"))
|
||||
//! # .context(StrContext::Expected(StrContextValue::Description("decimal"))),
|
||||
//! # ("0x", parse_hex_digits)
|
||||
//! # .context(StrContext::Label("digit"))
|
||||
//! # .context(StrContext::Expected(StrContextValue::Description("hexadecimal"))),
|
||||
//! # fail
|
||||
//! # .context(StrContext::Label("radix prefix"))
|
||||
//! # .context(StrContext::Expected(StrContextValue::StringLiteral("0b")))
|
||||
//! # .context(StrContext::Expected(StrContextValue::StringLiteral("0o")))
|
||||
//! # .context(StrContext::Expected(StrContextValue::StringLiteral("0d")))
|
||||
//! # .context(StrContext::Expected(StrContextValue::StringLiteral("0x"))),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_bin_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='1'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_oct_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='7'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_dec_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='9'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_hex_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='9'),
|
||||
//! # ('A'..='F'),
|
||||
//! # ('a'..='f'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! fn main() {
|
||||
//! let input = "0b5";
|
||||
//! let error = "\
|
||||
//! 0b5
|
||||
//! ^
|
||||
//! invalid radix prefix
|
||||
//! expected `0b`, `0o`, `0d`, `0x`";
|
||||
//! assert_eq!(input.parse::<Hex>().unwrap_err(), error);
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! Winnow provides an error wrapper, [`ErrMode<ContextError>`], so different failure modes can affect parsing.
|
||||
//! [`ErrMode`] is an enum with [`Backtrack`] and [`Cut`] variants (ignore [`Incomplete`] as its only
|
||||
//! relevant for [streaming][_topic::stream]). By default, errors are [`Backtrack`], meaning that
|
||||
//! other parsing branches will be attempted on failure, like the next case of an [`alt`]. [`Cut`]
|
||||
//! shortcircuits all other branches, immediately reporting the error.
|
||||
//!
|
||||
//! To make [`ErrMode`] more convenient, Winnow provides [`ModalResult`]:
|
||||
//! ```rust
|
||||
//! # use winnow::error::ContextError;
|
||||
//! # use winnow::error::ErrMode;
|
||||
//! pub type ModalResult<O, E = ContextError> = Result<O, ErrMode<E>>;
|
||||
//! ```
|
||||
//!
|
||||
//! So we can get the correct `context` by changing to [`ModalResult`] and adding [`cut_err`]:
|
||||
//! ```rust
|
||||
//! # use winnow::prelude::*;
|
||||
//! # use winnow::token::take_while;
|
||||
//! # use winnow::combinator::alt;
|
||||
//! # use winnow::token::take;
|
||||
//! # use winnow::combinator::fail;
|
||||
//! # use winnow::Parser;
|
||||
//! # use winnow::error::StrContext;
|
||||
//! # use winnow::error::StrContextValue;
|
||||
//! use winnow::combinator::cut_err;
|
||||
//!
|
||||
//! #
|
||||
//! # #[derive(Debug, PartialEq, Eq)]
|
||||
//! # pub struct Hex(usize);
|
||||
//! #
|
||||
//! # impl std::str::FromStr for Hex {
|
||||
//! # type Err = String;
|
||||
//! #
|
||||
//! # fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
//! # parse_digits
|
||||
//! # .try_map(|(t, v)| match t {
|
||||
//! # "0b" => usize::from_str_radix(v, 2),
|
||||
//! # "0o" => usize::from_str_radix(v, 8),
|
||||
//! # "0d" => usize::from_str_radix(v, 10),
|
||||
//! # "0x" => usize::from_str_radix(v, 16),
|
||||
//! # _ => unreachable!("`parse_digits` doesn't return `{t}`"),
|
||||
//! # })
|
||||
//! # .map(Hex)
|
||||
//! # .parse(input)
|
||||
//! # .map_err(|e| e.to_string())
|
||||
//! # }
|
||||
//! # }
|
||||
//! #
|
||||
//! fn parse_digits<'s>(input: &mut &'s str) -> ModalResult<(&'s str, &'s str)> {
|
||||
//! alt((
|
||||
//! ("0b", cut_err(parse_bin_digits))
|
||||
//! .context(StrContext::Label("digit"))
|
||||
//! .context(StrContext::Expected(StrContextValue::Description("binary"))),
|
||||
//! ("0o", cut_err(parse_oct_digits))
|
||||
//! .context(StrContext::Label("digit"))
|
||||
//! .context(StrContext::Expected(StrContextValue::Description("octal"))),
|
||||
//! ("0d", cut_err(parse_dec_digits))
|
||||
//! .context(StrContext::Label("digit"))
|
||||
//! .context(StrContext::Expected(StrContextValue::Description("decimal"))),
|
||||
//! ("0x", cut_err(parse_hex_digits))
|
||||
//! .context(StrContext::Label("digit"))
|
||||
//! .context(StrContext::Expected(StrContextValue::Description("hexadecimal"))),
|
||||
//! fail
|
||||
//! .context(StrContext::Label("radix prefix"))
|
||||
//! .context(StrContext::Expected(StrContextValue::StringLiteral("0b")))
|
||||
//! .context(StrContext::Expected(StrContextValue::StringLiteral("0o")))
|
||||
//! .context(StrContext::Expected(StrContextValue::StringLiteral("0d")))
|
||||
//! .context(StrContext::Expected(StrContextValue::StringLiteral("0x"))),
|
||||
//! )).parse_next(input)
|
||||
//! }
|
||||
//!
|
||||
//! // ...
|
||||
//!
|
||||
//! #
|
||||
//! # fn parse_bin_digits<'s>(input: &mut &'s str) -> ModalResult<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='1'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_oct_digits<'s>(input: &mut &'s str) -> ModalResult<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='7'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_dec_digits<'s>(input: &mut &'s str) -> ModalResult<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='9'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_hex_digits<'s>(input: &mut &'s str) -> ModalResult<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='9'),
|
||||
//! # ('A'..='F'),
|
||||
//! # ('a'..='f'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! fn main() {
|
||||
//! let input = "0b5";
|
||||
//! let error = "\
|
||||
//! 0b5
|
||||
//! ^
|
||||
//! invalid digit
|
||||
//! expected binary";
|
||||
//! assert_eq!(input.parse::<Hex>().unwrap_err(), error);
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Error Adaptation and Rendering
|
||||
//!
|
||||
//! While Winnow can provide basic rendering of errors, your application can have various demands
|
||||
//! beyond the basics provided like
|
||||
//! - Correctly reporting columns with unicode
|
||||
//! - Conforming to a specific layout
|
||||
//!
|
||||
//! For example, to get rustc-like errors with [`annotate-snippets`](https://crates.io/crates/annotate-snippets):
|
||||
//! ```rust
|
||||
//! # use winnow::prelude::*;
|
||||
//! # use winnow::token::take_while;
|
||||
//! # use winnow::combinator::alt;
|
||||
//! # use winnow::token::take;
|
||||
//! # use winnow::combinator::fail;
|
||||
//! # use winnow::Parser;
|
||||
//! # use winnow::error::ParseError;
|
||||
//! # use winnow::error::ContextError;
|
||||
//! # use winnow::error::StrContext;
|
||||
//! # use winnow::error::StrContextValue;
|
||||
//! # use winnow::combinator::cut_err;
|
||||
//! #
|
||||
//! #
|
||||
//! #[derive(Debug, PartialEq, Eq)]
|
||||
//! pub struct Hex(usize);
|
||||
//!
|
||||
//! impl std::str::FromStr for Hex {
|
||||
//! type Err = HexError;
|
||||
//!
|
||||
//! fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
//! // ...
|
||||
//! # parse_digits
|
||||
//! # .try_map(|(t, v)| match t {
|
||||
//! # "0b" => usize::from_str_radix(v, 2),
|
||||
//! # "0o" => usize::from_str_radix(v, 8),
|
||||
//! # "0d" => usize::from_str_radix(v, 10),
|
||||
//! # "0x" => usize::from_str_radix(v, 16),
|
||||
//! # _ => unreachable!("`parse_digits` doesn't return `{t}`"),
|
||||
//! # })
|
||||
//! # .map(Hex)
|
||||
//! .parse(input)
|
||||
//! .map_err(|e| HexError::from_parse(e))
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! #[derive(Debug)]
|
||||
//! pub struct HexError {
|
||||
//! message: String,
|
||||
//! // Byte spans are tracked, rather than line and column.
|
||||
//! // This makes it easier to operate on programmatically
|
||||
//! // and doesn't limit us to one definition for column count
|
||||
//! // which can depend on the output medium and application.
|
||||
//! span: std::ops::Range<usize>,
|
||||
//! input: String,
|
||||
//! }
|
||||
//!
|
||||
//! impl HexError {
|
||||
//! // Avoiding `From` so `winnow` types don't become part of our public API
|
||||
//! fn from_parse(error: ParseError<&str, ContextError>) -> Self {
|
||||
//! // The default renderer for `ContextError` is still used but that can be
|
||||
//! // customized as well to better fit your needs.
|
||||
//! let message = error.inner().to_string();
|
||||
//! let input = (*error.input()).to_owned();
|
||||
//! // Assume the error span is only for the first `char`.
|
||||
//! let span = error.char_span();
|
||||
//! Self {
|
||||
//! message,
|
||||
//! span,
|
||||
//! input,
|
||||
//! }
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! impl std::fmt::Display for HexError {
|
||||
//! fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
//! let message = annotate_snippets::Level::Error.title(&self.message)
|
||||
//! .snippet(annotate_snippets::Snippet::source(&self.input)
|
||||
//! .fold(true)
|
||||
//! .annotation(annotate_snippets::Level::Error.span(self.span.clone()))
|
||||
//! );
|
||||
//! let renderer = annotate_snippets::Renderer::plain();
|
||||
//! let rendered = renderer.render(message);
|
||||
//! rendered.fmt(f)
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! impl std::error::Error for HexError {}
|
||||
//!
|
||||
//! # fn parse_digits<'s>(input: &mut &'s str) -> ModalResult<(&'s str, &'s str)> {
|
||||
//! # alt((
|
||||
//! # ("0b", cut_err(parse_bin_digits))
|
||||
//! # .context(StrContext::Label("digit"))
|
||||
//! # .context(StrContext::Expected(StrContextValue::Description("binary"))),
|
||||
//! # ("0o", cut_err(parse_oct_digits))
|
||||
//! # .context(StrContext::Label("digit"))
|
||||
//! # .context(StrContext::Expected(StrContextValue::Description("octal"))),
|
||||
//! # ("0d", cut_err(parse_dec_digits))
|
||||
//! # .context(StrContext::Label("digit"))
|
||||
//! # .context(StrContext::Expected(StrContextValue::Description("decimal"))),
|
||||
//! # ("0x", cut_err(parse_hex_digits))
|
||||
//! # .context(StrContext::Label("digit"))
|
||||
//! # .context(StrContext::Expected(StrContextValue::Description("hexadecimal"))),
|
||||
//! # fail
|
||||
//! # .context(StrContext::Label("radix prefix"))
|
||||
//! # .context(StrContext::Expected(StrContextValue::StringLiteral("0b")))
|
||||
//! # .context(StrContext::Expected(StrContextValue::StringLiteral("0o")))
|
||||
//! # .context(StrContext::Expected(StrContextValue::StringLiteral("0d")))
|
||||
//! # .context(StrContext::Expected(StrContextValue::StringLiteral("0x"))),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_bin_digits<'s>(input: &mut &'s str) -> ModalResult<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='1'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_oct_digits<'s>(input: &mut &'s str) -> ModalResult<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='7'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_dec_digits<'s>(input: &mut &'s str) -> ModalResult<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='9'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn parse_hex_digits<'s>(input: &mut &'s str) -> ModalResult<&'s str> {
|
||||
//! # take_while(1.., (
|
||||
//! # ('0'..='9'),
|
||||
//! # ('A'..='F'),
|
||||
//! # ('a'..='f'),
|
||||
//! # )).parse_next(input)
|
||||
//! # }
|
||||
//! fn main() {
|
||||
//! let input = "0b5";
|
||||
//! let error = "\
|
||||
//! error: invalid digit
|
||||
//! expected binary
|
||||
//! |
|
||||
//! 1 | 0b5
|
||||
//! | ^
|
||||
//! |";
|
||||
//! assert_eq!(input.parse::<Hex>().unwrap_err().to_string(), error);
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! To add spans to your parsed data for inclusion in semantic errors, see [`Parser::with_span`].
|
||||
//!
|
||||
//! For richer syntactic errors with spans,
|
||||
//! consider separating lexing and parsing and annotating your tokens with [`Parser::with_span`].
|
||||
|
||||
#![allow(unused_imports)]
|
||||
use super::chapter_1;
|
||||
use super::chapter_3;
|
||||
use crate::combinator::alt;
|
||||
use crate::combinator::cut_err;
|
||||
use crate::combinator::fail;
|
||||
use crate::error::ContextError;
|
||||
use crate::error::ErrMode;
|
||||
use crate::error::ErrMode::*;
|
||||
use crate::ModalResult;
|
||||
use crate::Parser;
|
||||
use crate::Result;
|
||||
use crate::_topic;
|
||||
|
||||
pub use super::chapter_6 as previous;
|
||||
pub use super::chapter_8 as next;
|
||||
pub use crate::_tutorial as table_of_contents;
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
//! # Chapter 8: Debugging
|
||||
//!
|
||||
//! When things inevitably go wrong, you can introspect the parsing state by running your test case
|
||||
//! with `--features winnow/debug`.
|
||||
//!
|
||||
//! For example, the trace output of an [escaped string parser][crate::_topic::language#escaped-strings]:
|
||||
//! 
|
||||
//!
|
||||
//! You can extend your own parsers to show up by wrapping their body with
|
||||
//! [`trace`][crate::combinator::trace]. Going back to [`do_nothing_parser`][super::chapter_1].
|
||||
//! ```rust
|
||||
//! # use winnow::ModalResult;
|
||||
//! # use winnow::Parser;
|
||||
//! use winnow::combinator::trace;
|
||||
//!
|
||||
//! pub fn do_nothing_parser<'s>(input: &mut &'s str) -> ModalResult<&'s str> {
|
||||
//! trace(
|
||||
//! "do_nothing_parser",
|
||||
//! |i: &mut _| Ok("")
|
||||
//! ).parse_next(input)
|
||||
//! }
|
||||
//! #
|
||||
//! # fn main() {
|
||||
//! # let mut input = "0x1a2b Hello";
|
||||
//! #
|
||||
//! # let output = do_nothing_parser.parse_next(&mut input).unwrap();
|
||||
//! # // Same as:
|
||||
//! # // let output = do_nothing_parser(&mut input).unwrap();
|
||||
//! #
|
||||
//! # assert_eq!(input, "0x1a2b Hello");
|
||||
//! # assert_eq!(output, "");
|
||||
//! # }
|
||||
//! ```
|
||||
|
||||
pub use super::chapter_7 as previous;
|
||||
pub use crate::_topic as next;
|
||||
pub use crate::_tutorial as table_of_contents;
|
||||
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
//! # Tutorial
|
||||
//!
|
||||
//! Table of Contents
|
||||
#![allow(clippy::std_instead_of_core)]
|
||||
|
||||
pub mod chapter_0;
|
||||
pub mod chapter_1;
|
||||
pub mod chapter_2;
|
||||
pub mod chapter_3;
|
||||
pub mod chapter_4;
|
||||
pub mod chapter_5;
|
||||
pub mod chapter_6;
|
||||
pub mod chapter_7;
|
||||
pub mod chapter_8;
|
||||
Vendored
+207
@@ -0,0 +1,207 @@
|
||||
use crate::error::ParserError;
|
||||
use crate::stream::{Compare, CompareResult};
|
||||
use crate::stream::{SliceLen, Stream, StreamIsPartial};
|
||||
use crate::Parser;
|
||||
use crate::Result;
|
||||
|
||||
/// Mark a value as case-insensitive for ASCII characters
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use winnow::prelude::*;
|
||||
/// # use winnow::ascii::Caseless;
|
||||
///
|
||||
/// fn parser<'s>(s: &mut &'s str) -> ModalResult<&'s str> {
|
||||
/// Caseless("hello").parse_next(s)
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(parser.parse_peek("Hello, World!"), Ok((", World!", "Hello")));
|
||||
/// assert_eq!(parser.parse_peek("hello, World!"), Ok((", World!", "hello")));
|
||||
/// assert_eq!(parser.parse_peek("HeLlo, World!"), Ok((", World!", "HeLlo")));
|
||||
/// assert!(parser.parse_peek("Some").is_err());
|
||||
/// assert!(parser.parse_peek("").is_err());
|
||||
/// ```
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Caseless<T>(pub T);
|
||||
|
||||
impl Caseless<&str> {
|
||||
/// Get the byte-representation of this case-insensitive value
|
||||
#[inline(always)]
|
||||
pub fn as_bytes(&self) -> Caseless<&[u8]> {
|
||||
Caseless(self.0.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: SliceLen> SliceLen for Caseless<S> {
|
||||
#[inline(always)]
|
||||
fn slice_len(&self) -> usize {
|
||||
self.0.slice_len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'b> Compare<Caseless<&'b [u8]>> for &[u8] {
|
||||
#[inline]
|
||||
fn compare(&self, t: Caseless<&'b [u8]>) -> CompareResult {
|
||||
if t.0
|
||||
.iter()
|
||||
.zip(*self)
|
||||
.any(|(a, b)| !a.eq_ignore_ascii_case(b))
|
||||
{
|
||||
CompareResult::Error
|
||||
} else if self.len() < t.slice_len() {
|
||||
CompareResult::Incomplete
|
||||
} else {
|
||||
CompareResult::Ok(t.slice_len())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<const LEN: usize> Compare<Caseless<[u8; LEN]>> for &[u8] {
|
||||
#[inline(always)]
|
||||
fn compare(&self, t: Caseless<[u8; LEN]>) -> CompareResult {
|
||||
self.compare(Caseless(&t.0[..]))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'b, const LEN: usize> Compare<Caseless<&'b [u8; LEN]>> for &[u8] {
|
||||
#[inline(always)]
|
||||
fn compare(&self, t: Caseless<&'b [u8; LEN]>) -> CompareResult {
|
||||
self.compare(Caseless(&t.0[..]))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'b> Compare<Caseless<&'b str>> for &[u8] {
|
||||
#[inline(always)]
|
||||
fn compare(&self, t: Caseless<&'b str>) -> CompareResult {
|
||||
self.compare(Caseless(t.0.as_bytes()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Compare<Caseless<u8>> for &[u8] {
|
||||
#[inline]
|
||||
fn compare(&self, t: Caseless<u8>) -> CompareResult {
|
||||
match self.first() {
|
||||
Some(c) if t.0.eq_ignore_ascii_case(c) => CompareResult::Ok(t.slice_len()),
|
||||
Some(_) => CompareResult::Error,
|
||||
None => CompareResult::Incomplete,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Compare<Caseless<char>> for &[u8] {
|
||||
#[inline(always)]
|
||||
fn compare(&self, t: Caseless<char>) -> CompareResult {
|
||||
self.compare(Caseless(t.0.encode_utf8(&mut [0; 4]).as_bytes()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'b> Compare<Caseless<&'b str>> for &str {
|
||||
#[inline(always)]
|
||||
fn compare(&self, t: Caseless<&'b str>) -> CompareResult {
|
||||
self.as_bytes().compare(t.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl Compare<Caseless<char>> for &str {
|
||||
#[inline(always)]
|
||||
fn compare(&self, t: Caseless<char>) -> CompareResult {
|
||||
self.as_bytes().compare(t)
|
||||
}
|
||||
}
|
||||
|
||||
/// This is a shortcut for [`literal`][crate::token::literal].
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use winnow::prelude::*;
|
||||
/// # use winnow::{error::ErrMode, error::ContextError, error::Needed};
|
||||
/// # use winnow::combinator::alt;
|
||||
/// # use winnow::token::take;
|
||||
/// use winnow::ascii::Caseless;
|
||||
///
|
||||
/// fn parser<'s>(s: &mut &'s [u8]) -> ModalResult<&'s [u8]> {
|
||||
/// alt((Caseless(&"hello"[..]), take(5usize))).parse_next(s)
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(parser.parse_peek(&b"Hello, World!"[..]), Ok((&b", World!"[..], &b"Hello"[..])));
|
||||
/// assert_eq!(parser.parse_peek(&b"hello, World!"[..]), Ok((&b", World!"[..], &b"hello"[..])));
|
||||
/// assert_eq!(parser.parse_peek(&b"HeLlo, World!"[..]), Ok((&b", World!"[..], &b"HeLlo"[..])));
|
||||
/// assert_eq!(parser.parse_peek(&b"Something"[..]), Ok((&b"hing"[..], &b"Somet"[..])));
|
||||
/// assert!(parser.parse_peek(&b"Some"[..]).is_err());
|
||||
/// assert!(parser.parse_peek(&b""[..]).is_err());
|
||||
/// ```
|
||||
impl<'s, I, E: ParserError<I>> Parser<I, <I as Stream>::Slice, E> for Caseless<&'s [u8]>
|
||||
where
|
||||
I: Compare<Caseless<&'s [u8]>> + StreamIsPartial,
|
||||
I: Stream,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn parse_next(&mut self, i: &mut I) -> Result<<I as Stream>::Slice, E> {
|
||||
crate::token::literal(*self).parse_next(i)
|
||||
}
|
||||
}
|
||||
|
||||
/// This is a shortcut for [`literal`][crate::token::literal].
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use winnow::prelude::*;
|
||||
/// # use winnow::{error::ErrMode, error::ContextError, error::Needed};
|
||||
/// # use winnow::combinator::alt;
|
||||
/// # use winnow::token::take;
|
||||
/// use winnow::ascii::Caseless;
|
||||
///
|
||||
/// fn parser<'s>(s: &mut &'s [u8]) -> ModalResult<&'s [u8]> {
|
||||
/// alt((Caseless(b"hello"), take(5usize))).parse_next(s)
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(parser.parse_peek(&b"Hello, World!"[..]), Ok((&b", World!"[..], &b"Hello"[..])));
|
||||
/// assert_eq!(parser.parse_peek(&b"hello, World!"[..]), Ok((&b", World!"[..], &b"hello"[..])));
|
||||
/// assert_eq!(parser.parse_peek(&b"HeLlo, World!"[..]), Ok((&b", World!"[..], &b"HeLlo"[..])));
|
||||
/// assert_eq!(parser.parse_peek(&b"Something"[..]), Ok((&b"hing"[..], &b"Somet"[..])));
|
||||
/// assert!(parser.parse_peek(&b"Some"[..]).is_err());
|
||||
/// assert!(parser.parse_peek(&b""[..]).is_err());
|
||||
/// ```
|
||||
impl<'s, I, E: ParserError<I>, const N: usize> Parser<I, <I as Stream>::Slice, E>
|
||||
for Caseless<&'s [u8; N]>
|
||||
where
|
||||
I: Compare<Caseless<&'s [u8; N]>> + StreamIsPartial,
|
||||
I: Stream,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn parse_next(&mut self, i: &mut I) -> Result<<I as Stream>::Slice, E> {
|
||||
crate::token::literal(*self).parse_next(i)
|
||||
}
|
||||
}
|
||||
|
||||
/// This is a shortcut for [`literal`][crate::token::literal].
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use winnow::prelude::*;
|
||||
/// # use winnow::{error::ErrMode, error::ContextError};
|
||||
/// # use winnow::combinator::alt;
|
||||
/// # use winnow::token::take;
|
||||
/// # use winnow::ascii::Caseless;
|
||||
///
|
||||
/// fn parser<'s>(s: &mut &'s str) -> ModalResult<&'s str> {
|
||||
/// alt((Caseless("hello"), take(5usize))).parse_next(s)
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(parser.parse_peek("Hello, World!"), Ok((", World!", "Hello")));
|
||||
/// assert_eq!(parser.parse_peek("hello, World!"), Ok((", World!", "hello")));
|
||||
/// assert_eq!(parser.parse_peek("HeLlo, World!"), Ok((", World!", "HeLlo")));
|
||||
/// assert_eq!(parser.parse_peek("Something"), Ok(("hing", "Somet")));
|
||||
/// assert!(parser.parse_peek("Some").is_err());
|
||||
/// assert!(parser.parse_peek("").is_err());
|
||||
/// ```
|
||||
impl<'s, I, E: ParserError<I>> Parser<I, <I as Stream>::Slice, E> for Caseless<&'s str>
|
||||
where
|
||||
I: Compare<Caseless<&'s str>> + StreamIsPartial,
|
||||
I: Stream,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn parse_next(&mut self, i: &mut I) -> Result<<I as Stream>::Slice, E> {
|
||||
crate::token::literal(*self).parse_next(i)
|
||||
}
|
||||
}
|
||||
Vendored
+1844
File diff suppressed because it is too large.
Load diff
Vendored
+4956
File diff suppressed because it is too large.
Load diff
Vendored
+405
@@ -0,0 +1,405 @@
|
||||
//! Bit level parsers
|
||||
//!
|
||||
|
||||
mod stream;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub use self::stream::BitOffsets;
|
||||
pub use self::stream::Bits;
|
||||
|
||||
use crate::combinator::trace;
|
||||
use crate::error::{ErrorConvert, Needed, ParserError};
|
||||
use crate::stream::{Stream, StreamIsPartial, ToUsize};
|
||||
use crate::{Parser, Result};
|
||||
use core::ops::{AddAssign, Div, Shl, Shr};
|
||||
|
||||
/// Number of bits in a byte
|
||||
const BYTE: usize = u8::BITS as usize;
|
||||
|
||||
/// Converts a byte-level input to a bit-level input
|
||||
///
|
||||
/// See [`bytes`] to convert it back.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use winnow::prelude::*;
|
||||
/// # use winnow::Bytes;
|
||||
/// # use winnow::binary::bits::{bits, take};
|
||||
/// # use winnow::error::ContextError;
|
||||
/// # use winnow::error::ErrMode;
|
||||
/// type Stream<'i> = &'i Bytes;
|
||||
///
|
||||
/// fn stream(b: &[u8]) -> Stream<'_> {
|
||||
/// Bytes::new(b)
|
||||
/// }
|
||||
///
|
||||
/// fn parse(input: &mut Stream<'_>) -> ModalResult<(u8, u8)> {
|
||||
/// bits::<_, _, ErrMode<ContextError>, _, _>((take(4usize), take(8usize))).parse_next(input)
|
||||
/// }
|
||||
///
|
||||
/// let input = stream(&[0x12, 0x34, 0xff, 0xff]);
|
||||
///
|
||||
/// let output = parse.parse_peek(input).expect("We take 1.5 bytes and the input is longer than 2 bytes");
|
||||
///
|
||||
/// // The first byte is consumed, the second byte is partially consumed and dropped.
|
||||
/// let remaining = output.0;
|
||||
/// assert_eq!(remaining, stream(&[0xff, 0xff]));
|
||||
///
|
||||
/// let parsed = output.1;
|
||||
/// assert_eq!(parsed.0, 0x01);
|
||||
/// assert_eq!(parsed.1, 0x23);
|
||||
/// ```
|
||||
pub fn bits<Input, Output, BitError, ByteError, ParseNext>(
|
||||
mut parser: ParseNext,
|
||||
) -> impl Parser<Input, Output, ByteError>
|
||||
where
|
||||
BitError: ParserError<Bits<Input>> + ErrorConvert<ByteError>,
|
||||
ByteError: ParserError<Input>,
|
||||
Bits<Input>: Stream,
|
||||
Input: Stream + Clone,
|
||||
ParseNext: Parser<Bits<Input>, Output, BitError>,
|
||||
{
|
||||
trace("bits", move |input: &mut Input| {
|
||||
let mut bit_input = Bits(input.clone(), 0);
|
||||
match parser.parse_next(&mut bit_input) {
|
||||
Ok(result) => {
|
||||
let Bits(mut rest, offset) = bit_input;
|
||||
// If the next byte has been partially read, it will be sliced away as well.
|
||||
// The parser functions might already slice away all fully read bytes.
|
||||
// That's why `offset / BYTE` isn't necessarily needed at all times.
|
||||
let remaining_bytes_index = offset / BYTE + if offset % BYTE == 0 { 0 } else { 1 };
|
||||
let _ = rest.next_slice(remaining_bytes_index);
|
||||
*input = rest;
|
||||
Ok(result)
|
||||
}
|
||||
Err(e) => match e.needed() {
|
||||
Some(n) => Err(ParserError::incomplete(
|
||||
input,
|
||||
n.map(|u| u.get() / BYTE + 1),
|
||||
)),
|
||||
None => Err(ErrorConvert::convert(e)),
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Convert a [`bits`] stream back into a byte stream
|
||||
///
|
||||
/// <div class="warning">
|
||||
///
|
||||
/// **Warning:** A partial byte remaining in the input will be ignored and the given parser will
|
||||
/// start parsing at the next full byte.
|
||||
///
|
||||
/// </div>
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use winnow::prelude::*;
|
||||
/// # use winnow::Bytes;
|
||||
/// # use winnow::token::rest;
|
||||
/// # use winnow::error::ContextError;
|
||||
/// # use winnow::error::ErrMode;
|
||||
/// use winnow::binary::bits::{bits, bytes, take};
|
||||
///
|
||||
/// type Stream<'i> = &'i Bytes;
|
||||
///
|
||||
/// fn stream(b: &[u8]) -> Stream<'_> {
|
||||
/// Bytes::new(b)
|
||||
/// }
|
||||
///
|
||||
/// fn parse<'i>(input: &mut Stream<'i>) -> ModalResult<(u8, u8, &'i [u8])> {
|
||||
/// bits::<_, _, ErrMode<ContextError>, _, _>((
|
||||
/// take(4usize),
|
||||
/// take(8usize),
|
||||
/// bytes::<_, _, ErrMode<ContextError>, _, _>(rest)
|
||||
/// )).parse_next(input)
|
||||
/// }
|
||||
///
|
||||
/// let input = stream(&[0x12, 0x34, 0xff, 0xff]);
|
||||
///
|
||||
/// assert_eq!(parse.parse_peek(input), Ok(( stream(&[]), (0x01, 0x23, &[0xff, 0xff][..]) )));
|
||||
/// ```
|
||||
pub fn bytes<Input, Output, ByteError, BitError, ParseNext>(
|
||||
mut parser: ParseNext,
|
||||
) -> impl Parser<Bits<Input>, Output, BitError>
|
||||
where
|
||||
ByteError: ParserError<Input> + ErrorConvert<BitError>,
|
||||
BitError: ParserError<Bits<Input>>,
|
||||
Input: Stream<Token = u8> + Clone,
|
||||
ParseNext: Parser<Input, Output, ByteError>,
|
||||
{
|
||||
trace("bytes", move |bit_input: &mut Bits<Input>| {
|
||||
let Bits(mut input, offset) = bit_input.clone();
|
||||
let _ = if offset % BYTE != 0 {
|
||||
input.next_slice(1 + offset / BYTE)
|
||||
} else {
|
||||
input.next_slice(offset / BYTE)
|
||||
};
|
||||
match parser.parse_next(&mut input) {
|
||||
Ok(res) => {
|
||||
*bit_input = Bits(input, 0);
|
||||
Ok(res)
|
||||
}
|
||||
Err(e) => match e.needed() {
|
||||
Some(Needed::Unknown) => Err(ParserError::incomplete(bit_input, Needed::Unknown)),
|
||||
Some(Needed::Size(sz)) => Err(match sz.get().checked_mul(BYTE) {
|
||||
Some(v) => ParserError::incomplete(bit_input, Needed::new(v)),
|
||||
None => ParserError::assert(
|
||||
bit_input,
|
||||
"overflow in turning needed bytes into needed bits",
|
||||
),
|
||||
}),
|
||||
None => Err(ErrorConvert::convert(e)),
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse taking `count` bits
|
||||
///
|
||||
/// # Effective Signature
|
||||
///
|
||||
/// Assuming you are parsing a [`Bits<&[u8]>`][Bits] bit [Stream]:
|
||||
/// ```rust
|
||||
/// # use winnow::prelude::*;;
|
||||
/// # use winnow::error::ContextError;
|
||||
/// # use winnow::binary::bits::Bits;
|
||||
/// pub fn take<'i>(count: usize) -> impl Parser<Bits<&'i [u8]>, u8, ContextError>
|
||||
/// # {
|
||||
/// # winnow::binary::bits::take(count)
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use winnow::prelude::*;
|
||||
/// # use winnow::Bytes;
|
||||
/// # use winnow::error::ContextError;
|
||||
/// use winnow::binary::bits::{Bits, take};
|
||||
///
|
||||
/// type Stream<'i> = &'i Bytes;
|
||||
///
|
||||
/// fn stream(b: &[u8]) -> Stream<'_> {
|
||||
/// Bytes::new(b)
|
||||
/// }
|
||||
///
|
||||
/// // Consumes 0 bits, returns 0
|
||||
/// assert_eq!(take::<_, usize, _, ContextError>(0usize).parse_peek(Bits(stream(&[0b00010010]), 0)), Ok((Bits(stream(&[0b00010010]), 0), 0)));
|
||||
///
|
||||
/// // Consumes 4 bits, returns their values and increase offset to 4
|
||||
/// assert_eq!(take::<_, usize, _, ContextError>(4usize).parse_peek(Bits(stream(&[0b00010010]), 0)), Ok((Bits(stream(&[0b00010010]), 4), 0b00000001)));
|
||||
///
|
||||
/// // Consumes 4 bits, offset is 4, returns their values and increase offset to 0 of next byte
|
||||
/// assert_eq!(take::<_, usize, _, ContextError>(4usize).parse_peek(Bits(stream(&[0b00010010]), 4)), Ok((Bits(stream(&[]), 0), 0b00000010)));
|
||||
///
|
||||
/// // Tries to consume 12 bits but only 8 are available
|
||||
/// assert!(take::<_, usize, _, ContextError>(12usize).parse_peek(Bits(stream(&[0b00010010]), 0)).is_err());
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn take<Input, Output, Count, Error>(count: Count) -> impl Parser<Bits<Input>, Output, Error>
|
||||
where
|
||||
Input: Stream<Token = u8> + StreamIsPartial + Clone,
|
||||
Output: From<u8> + AddAssign + Shl<usize, Output = Output> + Shr<usize, Output = Output>,
|
||||
Count: ToUsize,
|
||||
Error: ParserError<Bits<Input>>,
|
||||
{
|
||||
let count = count.to_usize();
|
||||
trace("take", move |input: &mut Bits<Input>| {
|
||||
if <Input as StreamIsPartial>::is_partial_supported() {
|
||||
take_::<_, _, _, true>(input, count)
|
||||
} else {
|
||||
take_::<_, _, _, false>(input, count)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn take_<I, O, E: ParserError<Bits<I>>, const PARTIAL: bool>(
|
||||
bit_input: &mut Bits<I>,
|
||||
count: usize,
|
||||
) -> Result<O, E>
|
||||
where
|
||||
I: StreamIsPartial,
|
||||
I: Stream<Token = u8> + Clone,
|
||||
O: From<u8> + AddAssign + Shl<usize, Output = O> + Shr<usize, Output = O>,
|
||||
{
|
||||
if count == 0 {
|
||||
Ok(0u8.into())
|
||||
} else {
|
||||
let Bits(mut input, bit_offset) = bit_input.clone();
|
||||
if input.eof_offset() * BYTE < count + bit_offset {
|
||||
if PARTIAL && input.is_partial() {
|
||||
Err(ParserError::incomplete(bit_input, Needed::new(count)))
|
||||
} else {
|
||||
Err(ParserError::from_input(&Bits(input, bit_offset)))
|
||||
}
|
||||
} else {
|
||||
let cnt = (count + bit_offset).div(BYTE);
|
||||
let mut acc: O = 0_u8.into();
|
||||
let mut offset: usize = bit_offset;
|
||||
let mut remaining: usize = count;
|
||||
let mut end_offset: usize = 0;
|
||||
|
||||
for (_, byte) in input.iter_offsets().take(cnt + 1) {
|
||||
if remaining == 0 {
|
||||
break;
|
||||
}
|
||||
let val: O = if offset == 0 {
|
||||
byte.into()
|
||||
} else {
|
||||
(byte << offset >> offset).into()
|
||||
};
|
||||
|
||||
if remaining < BYTE - offset {
|
||||
acc += val >> (BYTE - offset - remaining);
|
||||
end_offset = remaining + offset;
|
||||
break;
|
||||
} else {
|
||||
acc += val << (remaining - (BYTE - offset));
|
||||
remaining -= BYTE - offset;
|
||||
offset = 0;
|
||||
}
|
||||
}
|
||||
let _ = input.next_slice(cnt);
|
||||
*bit_input = Bits(input, end_offset);
|
||||
Ok(acc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse taking `count` bits and comparing them to `pattern`
|
||||
///
|
||||
/// # Effective Signature
|
||||
///
|
||||
/// Assuming you are parsing a [`Bits<&[u8]>`][Bits] bit [Stream]:
|
||||
/// ```rust
|
||||
/// # use winnow::prelude::*;;
|
||||
/// # use winnow::error::ContextError;
|
||||
/// # use winnow::binary::bits::Bits;
|
||||
/// pub fn pattern<'i>(pattern: u8, count: usize) -> impl Parser<Bits<&'i [u8]>, u8, ContextError>
|
||||
/// # {
|
||||
/// # winnow::binary::bits::pattern(pattern, count)
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use winnow::prelude::*;
|
||||
/// # use winnow::Bytes;
|
||||
/// # use winnow::error::ContextError;
|
||||
/// use winnow::binary::bits::pattern;
|
||||
///
|
||||
/// type Stream<'i> = &'i Bytes;
|
||||
///
|
||||
/// fn stream(b: &[u8]) -> Stream<'_> {
|
||||
/// Bytes::new(b)
|
||||
/// }
|
||||
///
|
||||
/// /// Compare the lowest `count` bits of `input` against the lowest `count` bits of `pattern`.
|
||||
/// /// Return Ok and the matching section of `input` if there's a match.
|
||||
/// /// Return Err if there's no match.
|
||||
/// # use winnow::binary::bits::Bits;
|
||||
/// fn parser(bits: u8, count: u8, input: &mut Bits<Stream<'_>>) -> ModalResult<u8> {
|
||||
/// pattern(bits, count).parse_next(input)
|
||||
/// }
|
||||
///
|
||||
/// // The lowest 4 bits of 0b00001111 match the lowest 4 bits of 0b11111111.
|
||||
/// assert_eq!(
|
||||
/// pattern::<_, usize, _, ContextError>(0b0000_1111, 4usize).parse_peek(Bits(stream(&[0b1111_1111]), 0)),
|
||||
/// Ok((Bits(stream(&[0b1111_1111]), 4), 0b0000_1111))
|
||||
/// );
|
||||
///
|
||||
/// // The lowest bit of 0b00001111 matches the lowest bit of 0b11111111 (both are 1).
|
||||
/// assert_eq!(
|
||||
/// pattern::<_, usize, _, ContextError>(0b00000001, 1usize).parse_peek(Bits(stream(&[0b11111111]), 0)),
|
||||
/// Ok((Bits(stream(&[0b11111111]), 1), 0b00000001))
|
||||
/// );
|
||||
///
|
||||
/// // The lowest 2 bits of 0b11111111 and 0b00000001 are different.
|
||||
/// assert!(pattern::<_, usize, _, ContextError>(0b000000_01, 2usize).parse_peek(Bits(stream(&[0b111111_11]), 0)).is_err());
|
||||
///
|
||||
/// // The lowest 8 bits of 0b11111111 and 0b11111110 are different.
|
||||
/// assert!(pattern::<_, usize, _, ContextError>(0b11111110, 8usize).parse_peek(Bits(stream(&[0b11111111]), 0)).is_err());
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
#[doc(alias = "literal")]
|
||||
#[doc(alias = "just")]
|
||||
#[doc(alias = "tag")]
|
||||
pub fn pattern<Input, Output, Count, Error: ParserError<Bits<Input>>>(
|
||||
pattern: Output,
|
||||
count: Count,
|
||||
) -> impl Parser<Bits<Input>, Output, Error>
|
||||
where
|
||||
Input: Stream<Token = u8> + StreamIsPartial + Clone,
|
||||
Count: ToUsize,
|
||||
Output: From<u8>
|
||||
+ AddAssign
|
||||
+ Shl<usize, Output = Output>
|
||||
+ Shr<usize, Output = Output>
|
||||
+ PartialEq,
|
||||
{
|
||||
let count = count.to_usize();
|
||||
trace("pattern", move |input: &mut Bits<Input>| {
|
||||
let start = input.checkpoint();
|
||||
|
||||
take(count).parse_next(input).and_then(|o| {
|
||||
if pattern == o {
|
||||
Ok(o)
|
||||
} else {
|
||||
input.reset(&start);
|
||||
Err(ParserError::from_input(input))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Parses one specific bit as a bool.
|
||||
///
|
||||
/// # Effective Signature
|
||||
///
|
||||
/// Assuming you are parsing a [`Bits<&[u8]>`][Bits] bit [Stream]:
|
||||
/// ```rust
|
||||
/// # use winnow::prelude::*;;
|
||||
/// # use winnow::error::ContextError;
|
||||
/// # use winnow::binary::bits::Bits;
|
||||
/// pub fn bool(input: &mut Bits<&[u8]>) -> ModalResult<bool>
|
||||
/// # {
|
||||
/// # winnow::binary::bits::bool.parse_next(input)
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use winnow::prelude::*;
|
||||
/// # use winnow::Bytes;
|
||||
/// # use winnow::error::InputError;
|
||||
/// use winnow::binary::bits::bool;
|
||||
///
|
||||
/// type Stream<'i> = &'i Bytes;
|
||||
///
|
||||
/// fn stream(b: &[u8]) -> Stream<'_> {
|
||||
/// Bytes::new(b)
|
||||
/// }
|
||||
///
|
||||
/// # use winnow::binary::bits::Bits;
|
||||
/// fn parse(input: &mut Bits<Stream<'_>>) -> ModalResult<bool> {
|
||||
/// bool.parse_next(input)
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(parse.parse_peek(Bits(stream(&[0b10000000]), 0)), Ok((Bits(stream(&[0b10000000]), 1), true)));
|
||||
/// assert_eq!(parse.parse_peek(Bits(stream(&[0b10000000]), 1)), Ok((Bits(stream(&[0b10000000]), 2), false)));
|
||||
/// ```
|
||||
#[doc(alias = "any")]
|
||||
pub fn bool<Input, Error: ParserError<Bits<Input>>>(input: &mut Bits<Input>) -> Result<bool, Error>
|
||||
where
|
||||
Input: Stream<Token = u8> + StreamIsPartial + Clone,
|
||||
{
|
||||
trace("bool", |input: &mut Bits<Input>| {
|
||||
let bit: u32 = take(1usize).parse_next(input)?;
|
||||
Ok(bit != 0)
|
||||
})
|
||||
.parse_next(input)
|
||||
}
|
||||
+250
@@ -0,0 +1,250 @@
|
||||
use core::num::NonZeroUsize;
|
||||
|
||||
use crate::error::Needed;
|
||||
#[cfg(feature = "unstable-recover")]
|
||||
#[cfg(feature = "std")]
|
||||
use crate::stream::Recover;
|
||||
use crate::stream::{Checkpoint, Offset, Stream, StreamIsPartial};
|
||||
|
||||
/// Bit-level stream state over a byte stream.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Bits<I>(pub I, pub usize);
|
||||
|
||||
impl<I> Stream for Bits<I>
|
||||
where
|
||||
I: Stream<Token = u8> + Clone,
|
||||
{
|
||||
type Token = bool;
|
||||
type Slice = (I::Slice, usize, usize);
|
||||
|
||||
type IterOffsets = BitOffsets<I>;
|
||||
type Checkpoint = Checkpoint<Bits<I::Checkpoint>, Self>;
|
||||
|
||||
#[inline(always)]
|
||||
fn iter_offsets(&self) -> Self::IterOffsets {
|
||||
BitOffsets {
|
||||
i: self.clone(),
|
||||
o: 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn eof_offset(&self) -> usize {
|
||||
let offset = self.0.eof_offset() * 8;
|
||||
if offset == 0 {
|
||||
0
|
||||
} else {
|
||||
offset - self.1
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn next_token(&mut self) -> Option<Self::Token> {
|
||||
next_bit(self)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn peek_token(&self) -> Option<Self::Token> {
|
||||
peek_bit(self)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn offset_for<P>(&self, predicate: P) -> Option<usize>
|
||||
where
|
||||
P: Fn(Self::Token) -> bool,
|
||||
{
|
||||
self.iter_offsets()
|
||||
.find_map(|(o, b)| predicate(b).then_some(o))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn offset_at(&self, tokens: usize) -> Result<usize, Needed> {
|
||||
if let Some(needed) = tokens
|
||||
.checked_sub(self.eof_offset())
|
||||
.and_then(NonZeroUsize::new)
|
||||
{
|
||||
Err(Needed::Size(needed))
|
||||
} else {
|
||||
Ok(tokens)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn next_slice(&mut self, offset: usize) -> Self::Slice {
|
||||
let byte_offset = (offset + self.1) / 8;
|
||||
let end_offset = (offset + self.1) % 8;
|
||||
let s = self.0.next_slice(byte_offset);
|
||||
let start_offset = self.1;
|
||||
self.1 = end_offset;
|
||||
(s, start_offset, end_offset)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn peek_slice(&self, offset: usize) -> Self::Slice {
|
||||
let byte_offset = (offset + self.1) / 8;
|
||||
let end_offset = (offset + self.1) % 8;
|
||||
let s = self.0.peek_slice(byte_offset);
|
||||
let start_offset = self.1;
|
||||
(s, start_offset, end_offset)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn checkpoint(&self) -> Self::Checkpoint {
|
||||
Checkpoint::<_, Self>::new(Bits(self.0.checkpoint(), self.1))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn reset(&mut self, checkpoint: &Self::Checkpoint) {
|
||||
self.0.reset(&checkpoint.inner.0);
|
||||
self.1 = checkpoint.inner.1;
|
||||
}
|
||||
|
||||
fn trace(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "{self:#?}")
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterator for [bit][crate::binary::bits] stream ([`Bits`])
|
||||
pub struct BitOffsets<I> {
|
||||
i: Bits<I>,
|
||||
o: usize,
|
||||
}
|
||||
|
||||
impl<I> Iterator for BitOffsets<I>
|
||||
where
|
||||
I: Stream<Token = u8> + Clone,
|
||||
{
|
||||
type Item = (usize, bool);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let b = next_bit(&mut self.i)?;
|
||||
let o = self.o;
|
||||
self.o += 1;
|
||||
Some((o, b))
|
||||
}
|
||||
}
|
||||
|
||||
fn next_bit<I>(i: &mut Bits<I>) -> Option<bool>
|
||||
where
|
||||
I: Stream<Token = u8> + Clone,
|
||||
{
|
||||
if i.eof_offset() == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let offset = i.1;
|
||||
let mut next_i = i.0.clone();
|
||||
let byte = next_i.next_token()?;
|
||||
let bit = (byte >> offset) & 0x1 == 0x1;
|
||||
|
||||
let next_offset = offset + 1;
|
||||
if next_offset == 8 {
|
||||
i.0 = next_i;
|
||||
i.1 = 0;
|
||||
} else {
|
||||
i.1 = next_offset;
|
||||
}
|
||||
Some(bit)
|
||||
}
|
||||
|
||||
fn peek_bit<I>(i: &Bits<I>) -> Option<bool>
|
||||
where
|
||||
I: Stream<Token = u8> + Clone,
|
||||
{
|
||||
if i.eof_offset() == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let offset = i.1;
|
||||
let mut next_i = i.0.clone();
|
||||
let byte = next_i.next_token()?;
|
||||
Some((byte >> offset) & 0x1 == 0x1)
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable-recover")]
|
||||
#[cfg(feature = "std")]
|
||||
impl<I, E> Recover<E> for Bits<I>
|
||||
where
|
||||
I: Recover<E>,
|
||||
I: Stream<Token = u8> + Clone,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn record_err(
|
||||
&mut self,
|
||||
_token_start: &Self::Checkpoint,
|
||||
_err_start: &Self::Checkpoint,
|
||||
err: E,
|
||||
) -> Result<(), E> {
|
||||
Err(err)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn is_recovery_supported() -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> StreamIsPartial for Bits<I>
|
||||
where
|
||||
I: StreamIsPartial,
|
||||
{
|
||||
type PartialState = I::PartialState;
|
||||
|
||||
#[inline]
|
||||
fn complete(&mut self) -> Self::PartialState {
|
||||
self.0.complete()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn restore_partial(&mut self, state: Self::PartialState) {
|
||||
self.0.restore_partial(state);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn is_partial_supported() -> bool {
|
||||
I::is_partial_supported()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn is_partial(&self) -> bool {
|
||||
self.0.is_partial()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Offset for Bits<I>
|
||||
where
|
||||
I: Offset,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn offset_from(&self, start: &Self) -> usize {
|
||||
self.0.offset_from(&start.0) * 8 + self.1 - start.1
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Offset<<Bits<I> as Stream>::Checkpoint> for Bits<I>
|
||||
where
|
||||
I: Stream<Token = u8> + Clone,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn offset_from(&self, other: &<Bits<I> as Stream>::Checkpoint) -> usize {
|
||||
self.checkpoint().offset_from(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: Clone> crate::error::ErrorConvert<crate::error::InputError<Bits<I>>>
|
||||
for crate::error::InputError<I>
|
||||
{
|
||||
#[inline]
|
||||
fn convert(self) -> crate::error::InputError<Bits<I>> {
|
||||
self.map_input(|i| Bits(i, 0))
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: Clone> crate::error::ErrorConvert<crate::error::InputError<I>>
|
||||
for crate::error::InputError<Bits<I>>
|
||||
{
|
||||
#[inline]
|
||||
fn convert(self) -> crate::error::InputError<I> {
|
||||
self.map_input(|Bits(i, _o)| i)
|
||||
}
|
||||
}
|
||||
+282
@@ -0,0 +1,282 @@
|
||||
use super::*;
|
||||
use crate::error::ErrMode;
|
||||
use crate::error::InputError;
|
||||
use crate::prelude::*;
|
||||
use crate::stream::{Offset, Stream};
|
||||
use crate::Partial;
|
||||
#[cfg(feature = "std")]
|
||||
use proptest::prelude::*;
|
||||
|
||||
#[test]
|
||||
/// Take the `bits` function and assert that remaining bytes are correctly returned, if the
|
||||
/// previous bytes are fully consumed
|
||||
fn test_complete_byte_consumption_bits() {
|
||||
let input = &[0x12, 0x34, 0x56, 0x78][..];
|
||||
|
||||
// Take 3 bit slices with sizes [4, 8, 4].
|
||||
#[allow(clippy::type_complexity)]
|
||||
let result: ModalResult<(&[u8], (u8, u8, u8)), InputError<_>> =
|
||||
bits::<_, _, ErrMode<InputError<Bits<&[u8]>>>, _, _>((
|
||||
take(4usize),
|
||||
take(8usize),
|
||||
take(4usize),
|
||||
))
|
||||
.parse_peek(input);
|
||||
|
||||
let output = result.expect("We take 2 bytes and the input is longer than 2 bytes");
|
||||
|
||||
let remaining = output.0;
|
||||
assert_eq!(remaining, [0x56, 0x78]);
|
||||
|
||||
let parsed = output.1;
|
||||
assert_eq!(parsed.0, 0x01);
|
||||
assert_eq!(parsed.1, 0x23);
|
||||
assert_eq!(parsed.2, 0x04);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Take the `bits` function and assert that remaining bytes are correctly returned, if the
|
||||
/// previous bytes are NOT fully consumed. Partially consumed bytes are supposed to be dropped.
|
||||
/// I.e. if we consume 1.5 bytes of 4 bytes, 2 bytes will be returned, bits 13-16 will be
|
||||
/// dropped.
|
||||
fn test_partial_byte_consumption_bits() {
|
||||
let input = &[0x12, 0x34, 0x56, 0x78][..];
|
||||
|
||||
// Take bit slices with sizes [4, 8].
|
||||
let result: ModalResult<(&[u8], (u8, u8)), InputError<_>> =
|
||||
bits::<_, _, ErrMode<InputError<Bits<&[u8]>>>, _, _>((take(4usize), take(8usize)))
|
||||
.parse_peek(input);
|
||||
|
||||
let output = result.expect("We take 1.5 bytes and the input is longer than 2 bytes");
|
||||
|
||||
let remaining = output.0;
|
||||
assert_eq!(remaining, [0x56, 0x78]);
|
||||
|
||||
let parsed = output.1;
|
||||
assert_eq!(parsed.0, 0x01);
|
||||
assert_eq!(parsed.1, 0x23);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "std")]
|
||||
/// Ensure that in Incomplete error is thrown, if too few bytes are passed for a given parser.
|
||||
fn test_incomplete_bits() {
|
||||
let input = Partial::new(&[0x12][..]);
|
||||
|
||||
// Take bit slices with sizes [4, 8].
|
||||
let result: ModalResult<(_, (u8, u8)), InputError<_>> =
|
||||
bits::<_, _, ErrMode<InputError<Bits<_>>>, _, _>((take(4usize), take(8usize)))
|
||||
.parse_peek(input);
|
||||
|
||||
assert!(result.is_err());
|
||||
let error = result.err().unwrap();
|
||||
assert_eq!("Parsing requires 2 more data", error.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_take_complete_0() {
|
||||
let input = &[0b00010010][..];
|
||||
let count = 0usize;
|
||||
assert_eq!(count, 0usize);
|
||||
let offset = 0usize;
|
||||
|
||||
let result: ModalResult<(Bits<&[u8]>, usize), InputError<_>> =
|
||||
take(count).parse_peek(Bits(input, offset));
|
||||
|
||||
assert_eq!(result, Ok((Bits(input, offset), 0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_take_complete_eof() {
|
||||
let input = &[0b00010010][..];
|
||||
|
||||
let result: ModalResult<(Bits<&[u8]>, usize), InputError<_>> =
|
||||
take(1usize).parse_peek(Bits(input, 8));
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
Err(crate::error::ErrMode::Backtrack(InputError::at(Bits(
|
||||
input, 8
|
||||
),)))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_take_complete_span_over_multiple_bytes() {
|
||||
let input = &[0b00010010, 0b00110100, 0b11111111, 0b11111111][..];
|
||||
|
||||
let result: ModalResult<(Bits<&[u8]>, usize), InputError<_>> =
|
||||
take(24usize).parse_peek(Bits(input, 4));
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
Ok((Bits([0b11111111].as_ref(), 4), 0b1000110100111111111111))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_take_partial_0() {
|
||||
let input = Partial::new(&[][..]);
|
||||
let count = 0usize;
|
||||
assert_eq!(count, 0usize);
|
||||
let offset = 0usize;
|
||||
|
||||
let result: ModalResult<(Bits<_>, usize), InputError<_>> =
|
||||
take(count).parse_peek(Bits(input, offset));
|
||||
|
||||
assert_eq!(result, Ok((Bits(input, offset), 0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pattern_partial_ok() {
|
||||
let input = Partial::new(&[0b00011111][..]);
|
||||
let offset = 0usize;
|
||||
let bits_to_take = 4usize;
|
||||
let value_to_pattern = 0b0001;
|
||||
|
||||
let result: ModalResult<(Bits<_>, usize), InputError<_>> =
|
||||
pattern(value_to_pattern, bits_to_take).parse_peek(Bits(input, offset));
|
||||
|
||||
assert_eq!(result, Ok((Bits(input, bits_to_take), value_to_pattern)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pattern_partial_err() {
|
||||
let input = Partial::new(&[0b00011111][..]);
|
||||
let offset = 0usize;
|
||||
let bits_to_take = 4usize;
|
||||
let value_to_pattern = 0b1111;
|
||||
|
||||
let result: ModalResult<(Bits<_>, usize), InputError<_>> =
|
||||
pattern(value_to_pattern, bits_to_take).parse_peek(Bits(input, offset));
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
Err(crate::error::ErrMode::Backtrack(InputError::at(Bits(
|
||||
input, offset
|
||||
),)))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bool_0_complete() {
|
||||
let input = [0b10000000].as_ref();
|
||||
|
||||
let result: ModalResult<(Bits<&[u8]>, bool), InputError<_>> = bool.parse_peek(Bits(input, 0));
|
||||
|
||||
assert_eq!(result, Ok((Bits(input, 1), true)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bool_eof_complete() {
|
||||
let input = [0b10000000].as_ref();
|
||||
|
||||
let result: ModalResult<(Bits<&[u8]>, bool), InputError<_>> = bool.parse_peek(Bits(input, 8));
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
Err(crate::error::ErrMode::Backtrack(InputError::at(Bits(
|
||||
input, 8
|
||||
),)))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bool_0_partial() {
|
||||
let input = Partial::new([0b10000000].as_ref());
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
let result: ModalResult<(Bits<Partial<&[u8]>>, bool), InputError<_>> =
|
||||
bool.parse_peek(Bits(input, 0));
|
||||
|
||||
assert_eq!(result, Ok((Bits(input, 1), true)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bool_eof_partial() {
|
||||
let input = Partial::new([0b10000000].as_ref());
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
let result: ModalResult<(Bits<Partial<&[u8]>>, bool), InputError<_>> =
|
||||
bool.parse_peek(Bits(input, 8));
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
Err(crate::error::ErrMode::Incomplete(Needed::new(1)))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "alloc")]
|
||||
fn test_bit_stream_empty() {
|
||||
let i = Bits(&b""[..], 0);
|
||||
|
||||
let actual = i.iter_offsets().collect::<alloc::vec::Vec<_>>();
|
||||
assert_eq!(actual, vec![]);
|
||||
|
||||
let actual = i.eof_offset();
|
||||
assert_eq!(actual, 0);
|
||||
|
||||
let actual = i.peek_token();
|
||||
assert_eq!(actual, None);
|
||||
|
||||
let actual = i.offset_for(|b| b);
|
||||
assert_eq!(actual, None);
|
||||
|
||||
let actual = i.offset_at(1);
|
||||
assert_eq!(actual, Err(Needed::new(1)));
|
||||
|
||||
let actual_slice = i.peek_slice(0);
|
||||
assert_eq!(actual_slice, (&b""[..], 0, 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "alloc")]
|
||||
fn test_bit_offset_empty() {
|
||||
let i = Bits(&b""[..], 0);
|
||||
|
||||
let actual = i.offset_from(&i);
|
||||
assert_eq!(actual, 0);
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
proptest! {
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore)] // See https://github.com/AltSysrq/proptest/issues/253
|
||||
fn bit_stream(byte_len in 0..20usize, start in 0..160usize) {
|
||||
bit_stream_inner(byte_len, start);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
fn bit_stream_inner(byte_len: usize, start: usize) {
|
||||
let start = start.min(byte_len * 8);
|
||||
let start_byte = start / 8;
|
||||
let start_bit = start % 8;
|
||||
|
||||
let bytes = vec![0b1010_1010; byte_len];
|
||||
let i = Bits(&bytes[start_byte..], start_bit);
|
||||
|
||||
let mut curr_i = i;
|
||||
let mut curr_offset = 0;
|
||||
while let Some(_token) = curr_i.peek_token() {
|
||||
let to_offset = curr_i.offset_from(&i);
|
||||
assert_eq!(curr_offset, to_offset);
|
||||
|
||||
let actual_slice = i.peek_slice(curr_offset);
|
||||
let expected_slice = i.peek_slice(curr_offset);
|
||||
assert_eq!(actual_slice, expected_slice);
|
||||
|
||||
let at_offset = i.offset_at(curr_offset).unwrap();
|
||||
assert_eq!(curr_offset, at_offset);
|
||||
|
||||
let eof_offset = curr_i.eof_offset();
|
||||
let eof_slice = curr_i.peek_slice(eof_offset);
|
||||
let eof_slice_i = Bits(eof_slice.0, eof_slice.1);
|
||||
assert_eq!(eof_slice_i, curr_i);
|
||||
|
||||
curr_offset += 1;
|
||||
let _ = curr_i.next_token();
|
||||
}
|
||||
assert_eq!(i.eof_offset(), curr_offset);
|
||||
}
|
||||
Vendored
+2533
File diff suppressed because it is too large.
Load diff
Vendored
+3721
File diff suppressed because it is too large.
Load diff
+185
@@ -0,0 +1,185 @@
|
||||
use crate::combinator::trace;
|
||||
use crate::error::ParserError;
|
||||
use crate::stream::Stream;
|
||||
use crate::{Parser, Result};
|
||||
|
||||
#[doc(inline)]
|
||||
pub use crate::dispatch;
|
||||
|
||||
/// Helper trait for the [`alt()`] combinator.
|
||||
///
|
||||
/// This trait is implemented for tuples of up to 21 elements
|
||||
pub trait Alt<I, O, E> {
|
||||
/// Tests each parser in the tuple and returns the result of the first one that succeeds
|
||||
fn choice(&mut self, input: &mut I) -> Result<O, E>;
|
||||
}
|
||||
|
||||
/// Pick the first successful parser
|
||||
///
|
||||
/// To stop on an error, rather than trying further cases, see
|
||||
/// [`cut_err`][crate::combinator::cut_err] ([example][crate::_tutorial::chapter_7]).
|
||||
///
|
||||
/// For tight control over the error when no match is found, add a final case using [`fail`][crate::combinator::fail].
|
||||
/// Alternatively, with a [custom error type][crate::_topic::error], it is possible to track all
|
||||
/// errors or return the error of the parser that went the farthest in the input data.
|
||||
///
|
||||
/// When the alternative cases have unique prefixes, [`dispatch`] can offer better performance.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(feature = "ascii")] {
|
||||
/// # use winnow::{error::ErrMode, error::Needed};
|
||||
/// # use winnow::prelude::*;
|
||||
/// use winnow::ascii::{alpha1, digit1};
|
||||
/// use winnow::combinator::alt;
|
||||
/// fn parser<'i>(input: &mut &'i str) -> ModalResult<&'i str> {
|
||||
/// alt((alpha1, digit1)).parse_next(input)
|
||||
/// };
|
||||
///
|
||||
/// // the first parser, alpha1, takes the input
|
||||
/// assert_eq!(parser.parse_peek("abc"), Ok(("", "abc")));
|
||||
///
|
||||
/// // the first parser returns an error, so alt tries the second one
|
||||
/// assert_eq!(parser.parse_peek("123456"), Ok(("", "123456")));
|
||||
///
|
||||
/// // both parsers failed, and with the default error type, alt will return the last error
|
||||
/// assert!(parser.parse_peek(" ").is_err());
|
||||
/// # }
|
||||
/// ```
|
||||
#[doc(alias = "choice")]
|
||||
#[inline(always)]
|
||||
pub fn alt<Input: Stream, Output, Error, Alternatives>(
|
||||
mut alternatives: Alternatives,
|
||||
) -> impl Parser<Input, Output, Error>
|
||||
where
|
||||
Alternatives: Alt<Input, Output, Error>,
|
||||
Error: ParserError<Input>,
|
||||
{
|
||||
trace("alt", move |i: &mut Input| alternatives.choice(i))
|
||||
}
|
||||
|
||||
impl<const N: usize, I: Stream, O, E: ParserError<I>, P: Parser<I, O, E>> Alt<I, O, E> for [P; N] {
|
||||
fn choice(&mut self, input: &mut I) -> Result<O, E> {
|
||||
let mut error: Option<E> = None;
|
||||
|
||||
let start = input.checkpoint();
|
||||
for branch in self {
|
||||
input.reset(&start);
|
||||
match branch.parse_next(input) {
|
||||
Err(e) if e.is_backtrack() => {
|
||||
error = match error {
|
||||
Some(error) => Some(error.or(e)),
|
||||
None => Some(e),
|
||||
};
|
||||
}
|
||||
res => return res,
|
||||
}
|
||||
}
|
||||
|
||||
match error {
|
||||
Some(e) => Err(e.append(input, &start)),
|
||||
None => Err(ParserError::assert(
|
||||
input,
|
||||
"`alt` needs at least one parser",
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: Stream, O, E: ParserError<I>, P: Parser<I, O, E>> Alt<I, O, E> for &mut [P] {
|
||||
fn choice(&mut self, input: &mut I) -> Result<O, E> {
|
||||
let mut error: Option<E> = None;
|
||||
|
||||
let start = input.checkpoint();
|
||||
for branch in self.iter_mut() {
|
||||
input.reset(&start);
|
||||
match branch.parse_next(input) {
|
||||
Err(e) if e.is_backtrack() => {
|
||||
error = match error {
|
||||
Some(error) => Some(error.or(e)),
|
||||
None => Some(e),
|
||||
};
|
||||
}
|
||||
res => return res,
|
||||
}
|
||||
}
|
||||
|
||||
match error {
|
||||
Some(e) => Err(e.append(input, &start)),
|
||||
None => Err(ParserError::assert(
|
||||
input,
|
||||
"`alt` needs at least one parser",
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! alt_trait(
|
||||
($first:ident $second:ident $($id: ident)+) => (
|
||||
alt_trait!(__impl $first $second; $($id)+);
|
||||
);
|
||||
(__impl $($current:ident)*; $head:ident $($id: ident)+) => (
|
||||
alt_trait_impl!($($current)*);
|
||||
|
||||
alt_trait!(__impl $($current)* $head; $($id)+);
|
||||
);
|
||||
(__impl $($current:ident)*; $head:ident) => (
|
||||
alt_trait_impl!($($current)*);
|
||||
alt_trait_impl!($($current)* $head);
|
||||
);
|
||||
);
|
||||
|
||||
macro_rules! alt_trait_impl(
|
||||
($($id:ident)+) => (
|
||||
impl<
|
||||
I: Stream, Output, Error: ParserError<I>,
|
||||
$($id: Parser<I, Output, Error>),+
|
||||
> Alt<I, Output, Error> for ( $($id),+ ) {
|
||||
|
||||
fn choice(&mut self, input: &mut I) -> Result<Output, Error> {
|
||||
let start = input.checkpoint();
|
||||
match self.0.parse_next(input) {
|
||||
Err(e) if e.is_backtrack() => alt_trait_inner!(1, self, input, start, e, $($id)+),
|
||||
res => res,
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
);
|
||||
|
||||
macro_rules! succ (
|
||||
(1, $submac:ident ! ($($rest:tt)*)) => ($submac!(2, $($rest)*));
|
||||
(2, $submac:ident ! ($($rest:tt)*)) => ($submac!(3, $($rest)*));
|
||||
(3, $submac:ident ! ($($rest:tt)*)) => ($submac!(4, $($rest)*));
|
||||
(4, $submac:ident ! ($($rest:tt)*)) => ($submac!(5, $($rest)*));
|
||||
(5, $submac:ident ! ($($rest:tt)*)) => ($submac!(6, $($rest)*));
|
||||
(6, $submac:ident ! ($($rest:tt)*)) => ($submac!(7, $($rest)*));
|
||||
(7, $submac:ident ! ($($rest:tt)*)) => ($submac!(8, $($rest)*));
|
||||
(8, $submac:ident ! ($($rest:tt)*)) => ($submac!(9, $($rest)*));
|
||||
);
|
||||
|
||||
macro_rules! alt_trait_inner(
|
||||
($it:tt, $self:expr, $input:expr, $start:ident, $err:expr, $head:ident $($id:ident)+) => ({
|
||||
$input.reset(&$start);
|
||||
match $self.$it.parse_next($input) {
|
||||
Err(e) if e.is_backtrack() => {
|
||||
let err = $err.or(e);
|
||||
succ!($it, alt_trait_inner!($self, $input, $start, err, $($id)+))
|
||||
}
|
||||
res => res,
|
||||
}
|
||||
});
|
||||
($it:tt, $self:expr, $input:expr, $start:ident, $err:expr, $head:ident) => ({
|
||||
Err($err.append($input, &$start))
|
||||
});
|
||||
);
|
||||
|
||||
alt_trait!(Alt2 Alt3 Alt4 Alt5 Alt6 Alt7 Alt8 Alt9 Alt10);
|
||||
|
||||
// Manually implement Alt for (A,), the 1-tuple type
|
||||
impl<I: Stream, O, E: ParserError<I>, A: Parser<I, O, E>> Alt<I, O, E> for (A,) {
|
||||
fn choice(&mut self, input: &mut I) -> Result<O, E> {
|
||||
self.0.parse_next(input)
|
||||
}
|
||||
}
|
||||
Vendored
+400
@@ -0,0 +1,400 @@
|
||||
use crate::combinator::trace;
|
||||
use crate::error::{ModalError, ParserError};
|
||||
use crate::stream::Stream;
|
||||
use crate::{Parser, Result};
|
||||
|
||||
/// Apply a [`Parser`], producing `None` on [`ErrMode::Backtrack`][crate::error::ErrMode::Backtrack].
|
||||
///
|
||||
/// To chain an error up, see [`cut_err`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(feature = "ascii")] {
|
||||
/// # use winnow::prelude::*;
|
||||
/// use winnow::combinator::opt;
|
||||
/// use winnow::ascii::alpha1;
|
||||
///
|
||||
/// fn parser<'i>(i: &mut &'i str) -> ModalResult<Option<&'i str>> {
|
||||
/// opt(alpha1).parse_next(i)
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(parser.parse_peek("abcd;"), Ok((";", Some("abcd"))));
|
||||
/// assert_eq!(parser.parse_peek("123;"), Ok(("123;", None)));
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn opt<Input: Stream, Output, Error, ParseNext>(
|
||||
mut parser: ParseNext,
|
||||
) -> impl Parser<Input, Option<Output>, Error>
|
||||
where
|
||||
ParseNext: Parser<Input, Output, Error>,
|
||||
Error: ParserError<Input>,
|
||||
{
|
||||
trace("opt", move |input: &mut Input| {
|
||||
let start = input.checkpoint();
|
||||
match parser.parse_next(input) {
|
||||
Ok(o) => Ok(Some(o)),
|
||||
Err(e) if e.is_backtrack() => {
|
||||
input.reset(&start);
|
||||
Ok(None)
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Calls the parser if the condition is met.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(feature = "ascii")] {
|
||||
/// # use winnow::prelude::*;
|
||||
/// # use winnow::combinator::opt;
|
||||
/// use winnow::combinator::cond;
|
||||
/// use winnow::ascii::alpha1;
|
||||
///
|
||||
/// fn parser<'i>(i: &mut &'i str) -> ModalResult<Option<&'i str>> {
|
||||
/// let prefix = opt("-").parse_next(i)?;
|
||||
/// let condition = prefix.is_some();
|
||||
/// cond(condition, alpha1).parse_next(i)
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(parser.parse_peek("-abcd;"), Ok((";", Some("abcd"))));
|
||||
/// assert_eq!(parser.parse_peek("abcd;"), Ok(("abcd;", None)));
|
||||
/// assert!(parser.parse_peek("-123;").is_err());
|
||||
/// assert_eq!(parser.parse_peek("123;"), Ok(("123;", None)));
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn cond<Input, Output, Error, ParseNext>(
|
||||
cond: bool,
|
||||
mut parser: ParseNext,
|
||||
) -> impl Parser<Input, Option<Output>, Error>
|
||||
where
|
||||
Input: Stream,
|
||||
ParseNext: Parser<Input, Output, Error>,
|
||||
Error: ParserError<Input>,
|
||||
{
|
||||
trace("cond", move |input: &mut Input| {
|
||||
if cond {
|
||||
parser.parse_next(input).map(Some)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Apply the parser without advancing the input.
|
||||
///
|
||||
/// To lookahead and only advance on success, see [`opt`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(feature = "ascii")] {
|
||||
/// # use winnow::prelude::*;
|
||||
/// use winnow::combinator::peek;
|
||||
/// use winnow::ascii::alpha1;
|
||||
///
|
||||
/// fn parser<'i>(input: &mut &'i str) -> ModalResult<&'i str> {
|
||||
/// peek(alpha1).parse_next(input)
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(parser.parse_peek("abcd;"), Ok(("abcd;", "abcd")));
|
||||
/// assert!(parser.parse_peek("123;").is_err());
|
||||
/// # }
|
||||
/// ```
|
||||
#[doc(alias = "look_ahead")]
|
||||
#[doc(alias = "rewind")]
|
||||
pub fn peek<Input, Output, Error, ParseNext>(
|
||||
mut parser: ParseNext,
|
||||
) -> impl Parser<Input, Output, Error>
|
||||
where
|
||||
Input: Stream,
|
||||
Error: ParserError<Input>,
|
||||
ParseNext: Parser<Input, Output, Error>,
|
||||
{
|
||||
trace("peek", move |input: &mut Input| {
|
||||
let start = input.checkpoint();
|
||||
let res = parser.parse_next(input);
|
||||
input.reset(&start);
|
||||
res
|
||||
})
|
||||
}
|
||||
|
||||
/// Match the end of the [`Stream`]
|
||||
///
|
||||
/// Otherwise, it will error.
|
||||
///
|
||||
/// # Effective Signature
|
||||
///
|
||||
/// Assuming you are parsing a `&str` [Stream]:
|
||||
/// ```rust
|
||||
/// # use winnow::prelude::*;;
|
||||
/// pub fn eof<'i>(input: &mut &'i str) -> ModalResult<&'i str>
|
||||
/// # {
|
||||
/// # winnow::combinator::eof.parse_next(input)
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use std::str;
|
||||
/// # use winnow::combinator::eof;
|
||||
/// # use winnow::prelude::*;
|
||||
///
|
||||
/// fn parser<'i>(input: &mut &'i str) -> ModalResult<&'i str> {
|
||||
/// eof.parse_next(input)
|
||||
/// }
|
||||
/// assert!(parser.parse_peek("abc").is_err());
|
||||
/// assert_eq!(parser.parse_peek(""), Ok(("", "")));
|
||||
/// ```
|
||||
#[doc(alias = "end")]
|
||||
#[doc(alias = "eoi")]
|
||||
pub fn eof<Input, Error>(input: &mut Input) -> Result<<Input as Stream>::Slice, Error>
|
||||
where
|
||||
Input: Stream,
|
||||
Error: ParserError<Input>,
|
||||
{
|
||||
trace("eof", move |input: &mut Input| {
|
||||
if input.eof_offset() == 0 {
|
||||
Ok(input.next_slice(0))
|
||||
} else {
|
||||
Err(ParserError::from_input(input))
|
||||
}
|
||||
})
|
||||
.parse_next(input)
|
||||
}
|
||||
|
||||
/// Succeeds if the child parser returns an error.
|
||||
///
|
||||
/// <div class="warning">
|
||||
///
|
||||
/// **Note:** This does not advance the [`Stream`]
|
||||
///
|
||||
/// </div>
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(feature = "ascii")] {
|
||||
/// # use winnow::prelude::*;
|
||||
/// use winnow::combinator::not;
|
||||
/// use winnow::ascii::alpha1;
|
||||
///
|
||||
/// fn parser<'i>(input: &mut &'i str) -> ModalResult<()> {
|
||||
/// not(alpha1).parse_next(input)
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(parser.parse_peek("123"), Ok(("123", ())));
|
||||
/// assert!(parser.parse_peek("abcd").is_err());
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn not<Input, Output, Error, ParseNext>(mut parser: ParseNext) -> impl Parser<Input, (), Error>
|
||||
where
|
||||
Input: Stream,
|
||||
Error: ParserError<Input>,
|
||||
ParseNext: Parser<Input, Output, Error>,
|
||||
{
|
||||
trace("not", move |input: &mut Input| {
|
||||
let start = input.checkpoint();
|
||||
let res = parser.parse_next(input);
|
||||
input.reset(&start);
|
||||
match res {
|
||||
Ok(_) => Err(ParserError::from_input(input)),
|
||||
Err(e) if e.is_backtrack() => Ok(()),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Transforms an [`ErrMode::Backtrack`][crate::error::ErrMode::Backtrack] (recoverable) to [`ErrMode::Cut`][crate::error::ErrMode::Cut] (unrecoverable)
|
||||
///
|
||||
/// This commits the parse result, preventing alternative branch paths like with
|
||||
/// [`winnow::combinator::alt`][crate::combinator::alt].
|
||||
///
|
||||
/// See the [tutorial][crate::_tutorial::chapter_7] for more details.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Without `cut_err`:
|
||||
/// ```rust
|
||||
/// # #[cfg(feature = "ascii")] {
|
||||
/// # use winnow::token::one_of;
|
||||
/// # use winnow::token::rest;
|
||||
/// # use winnow::ascii::digit1;
|
||||
/// # use winnow::combinator::alt;
|
||||
/// # use winnow::combinator::preceded;
|
||||
/// # use winnow::prelude::*;
|
||||
///
|
||||
/// fn parser<'i>(input: &mut &'i str) -> ModalResult<&'i str> {
|
||||
/// alt((
|
||||
/// preceded(one_of(['+', '-']), digit1),
|
||||
/// rest
|
||||
/// )).parse_next(input)
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(parser.parse_peek("+10 ab"), Ok((" ab", "10")));
|
||||
/// assert_eq!(parser.parse_peek("ab"), Ok(("", "ab")));
|
||||
/// assert_eq!(parser.parse_peek("+"), Ok(("", "+")));
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// With `cut_err`:
|
||||
/// ```rust
|
||||
/// # #[cfg(feature = "ascii")] {
|
||||
/// # use winnow::{error::ErrMode, error::ContextError};
|
||||
/// # use winnow::prelude::*;
|
||||
/// # use winnow::token::one_of;
|
||||
/// # use winnow::token::rest;
|
||||
/// # use winnow::ascii::digit1;
|
||||
/// # use winnow::combinator::alt;
|
||||
/// # use winnow::combinator::preceded;
|
||||
/// use winnow::combinator::cut_err;
|
||||
///
|
||||
/// fn parser<'i>(input: &mut &'i str) -> ModalResult<&'i str> {
|
||||
/// alt((
|
||||
/// preceded(one_of(['+', '-']), cut_err(digit1)),
|
||||
/// rest
|
||||
/// )).parse_next(input)
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(parser.parse_peek("+10 ab"), Ok((" ab", "10")));
|
||||
/// assert_eq!(parser.parse_peek("ab"), Ok(("", "ab")));
|
||||
/// assert_eq!(parser.parse_peek("+"), Err(ErrMode::Cut(ContextError::new())));
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn cut_err<Input, Output, Error, ParseNext>(
|
||||
mut parser: ParseNext,
|
||||
) -> impl Parser<Input, Output, Error>
|
||||
where
|
||||
Input: Stream,
|
||||
Error: ParserError<Input> + ModalError,
|
||||
ParseNext: Parser<Input, Output, Error>,
|
||||
{
|
||||
trace("cut_err", move |input: &mut Input| {
|
||||
parser.parse_next(input).map_err(|e| e.cut())
|
||||
})
|
||||
}
|
||||
|
||||
/// Transforms an [`ErrMode::Cut`][crate::error::ErrMode::Cut] (unrecoverable) to [`ErrMode::Backtrack`][crate::error::ErrMode::Backtrack] (recoverable)
|
||||
///
|
||||
/// This attempts the parse, allowing other parsers to be tried on failure, like with
|
||||
/// [`winnow::combinator::alt`][crate::combinator::alt].
|
||||
pub fn backtrack_err<Input, Output, Error, ParseNext>(
|
||||
mut parser: ParseNext,
|
||||
) -> impl Parser<Input, Output, Error>
|
||||
where
|
||||
Input: Stream,
|
||||
Error: ParserError<Input> + ModalError,
|
||||
ParseNext: Parser<Input, Output, Error>,
|
||||
{
|
||||
trace("backtrack_err", move |input: &mut Input| {
|
||||
parser.parse_next(input).map_err(|e| e.backtrack())
|
||||
})
|
||||
}
|
||||
|
||||
/// A placeholder for a not-yet-implemented [`Parser`]
|
||||
///
|
||||
/// This is analogous to the [`todo!`] macro and helps with prototyping.
|
||||
///
|
||||
/// # Panic
|
||||
///
|
||||
/// This will panic when parsing
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use winnow::prelude::*;
|
||||
/// # use winnow::combinator::todo;
|
||||
///
|
||||
/// fn parser(input: &mut &str) -> ModalResult<u64> {
|
||||
/// todo(input)
|
||||
/// }
|
||||
/// ```
|
||||
#[track_caller]
|
||||
pub fn todo<Input, Output, Error>(input: &mut Input) -> Result<Output, Error>
|
||||
where
|
||||
Input: Stream,
|
||||
Error: ParserError<Input>,
|
||||
{
|
||||
#![allow(clippy::todo)]
|
||||
trace("todo", move |_input: &mut Input| {
|
||||
todo!("unimplemented parse")
|
||||
})
|
||||
.parse_next(input)
|
||||
}
|
||||
|
||||
/// Succeed, consuming no input
|
||||
///
|
||||
/// For example, it can be used as the last alternative in `alt` to
|
||||
/// specify the default case.
|
||||
///
|
||||
/// Useful with:
|
||||
/// - [`Parser::value`]
|
||||
/// - [`Parser::default_value`]
|
||||
/// - [`Parser::map`]
|
||||
///
|
||||
/// <div class="warning">
|
||||
///
|
||||
/// **Note:** This never advances the [`Stream`]
|
||||
///
|
||||
/// </div>
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use winnow::prelude::*;
|
||||
/// use winnow::combinator::alt;
|
||||
/// use winnow::combinator::empty;
|
||||
///
|
||||
/// fn sign(input: &mut &str) -> ModalResult<isize> {
|
||||
/// alt((
|
||||
/// '-'.value(-1),
|
||||
/// '+'.value(1),
|
||||
/// empty.value(1)
|
||||
/// )).parse_next(input)
|
||||
/// }
|
||||
/// assert_eq!(sign.parse_peek("+10"), Ok(("10", 1)));
|
||||
/// assert_eq!(sign.parse_peek("-10"), Ok(("10", -1)));
|
||||
/// assert_eq!(sign.parse_peek("10"), Ok(("10", 1)));
|
||||
/// ```
|
||||
#[doc(alias = "value")]
|
||||
#[doc(alias = "success")]
|
||||
#[inline]
|
||||
pub fn empty<Input, Error>(_input: &mut Input) -> Result<(), Error>
|
||||
where
|
||||
Input: Stream,
|
||||
Error: ParserError<Input>,
|
||||
{
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A parser which always fails.
|
||||
///
|
||||
/// For example, it can be used as the last alternative in `alt` to
|
||||
/// control the error message given.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use winnow::{error::ErrMode, error::InputError};
|
||||
/// # use winnow::prelude::*;
|
||||
/// use winnow::combinator::fail;
|
||||
///
|
||||
/// fn parser<'i>(input: &mut &'i str) -> ModalResult<(), InputError<&'i str>> {
|
||||
/// fail.parse_next(input)
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(parser.parse_peek("string"), Err(ErrMode::Backtrack(InputError::at("string"))));
|
||||
/// ```
|
||||
#[doc(alias = "unexpected")]
|
||||
#[inline]
|
||||
pub fn fail<Input, Output, Error>(i: &mut Input) -> Result<Output, Error>
|
||||
where
|
||||
Input: Stream,
|
||||
Error: ParserError<Input>,
|
||||
{
|
||||
trace("fail", |i: &mut Input| Err(ParserError::from_input(i))).parse_next(i)
|
||||
}
|
||||
+298
@@ -0,0 +1,298 @@
|
||||
#![cfg(feature = "std")]
|
||||
|
||||
use std::io::Write;
|
||||
|
||||
use crate::error::ParserError;
|
||||
use crate::stream::Stream;
|
||||
use crate::{Parser, Result};
|
||||
|
||||
pub(crate) struct Trace<P, D, I, O, E>
|
||||
where
|
||||
P: Parser<I, O, E>,
|
||||
I: Stream,
|
||||
D: std::fmt::Display,
|
||||
E: ParserError<I>,
|
||||
{
|
||||
parser: P,
|
||||
name: D,
|
||||
call_count: usize,
|
||||
marker: core::marker::PhantomData<(I, O, E)>,
|
||||
}
|
||||
|
||||
impl<P, D, I, O, E> Trace<P, D, I, O, E>
|
||||
where
|
||||
P: Parser<I, O, E>,
|
||||
I: Stream,
|
||||
D: std::fmt::Display,
|
||||
E: ParserError<I>,
|
||||
{
|
||||
#[inline(always)]
|
||||
pub(crate) fn new(parser: P, name: D) -> Self {
|
||||
Self {
|
||||
parser,
|
||||
name,
|
||||
call_count: 0,
|
||||
marker: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, D, I, O, E> Parser<I, O, E> for Trace<P, D, I, O, E>
|
||||
where
|
||||
P: Parser<I, O, E>,
|
||||
I: Stream,
|
||||
D: std::fmt::Display,
|
||||
E: ParserError<I>,
|
||||
{
|
||||
#[inline]
|
||||
fn parse_next(&mut self, i: &mut I) -> Result<O, E> {
|
||||
let depth = Depth::new();
|
||||
let original = i.checkpoint();
|
||||
start(*depth, &self.name, self.call_count, i);
|
||||
|
||||
let res = self.parser.parse_next(i);
|
||||
|
||||
let consumed = i.offset_from(&original);
|
||||
let severity = Severity::with_result(&res);
|
||||
end(*depth, &self.name, self.call_count, consumed, severity);
|
||||
self.call_count += 1;
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Depth {
|
||||
depth: usize,
|
||||
inc: bool,
|
||||
}
|
||||
|
||||
impl Depth {
|
||||
pub(crate) fn new() -> Self {
|
||||
let depth = DEPTH.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
||||
let inc = true;
|
||||
Self { depth, inc }
|
||||
}
|
||||
|
||||
pub(crate) fn existing() -> Self {
|
||||
let depth = DEPTH.load(std::sync::atomic::Ordering::SeqCst);
|
||||
let inc = false;
|
||||
Self { depth, inc }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Depth {
|
||||
fn drop(&mut self) {
|
||||
if self.inc {
|
||||
let _ = DEPTH.fetch_sub(1, std::sync::atomic::Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<usize> for Depth {
|
||||
#[inline(always)]
|
||||
fn as_ref(&self) -> &usize {
|
||||
&self.depth
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::Deref for Depth {
|
||||
type Target = usize;
|
||||
|
||||
#[inline(always)]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.depth
|
||||
}
|
||||
}
|
||||
|
||||
static DEPTH: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
|
||||
|
||||
pub(crate) enum Severity {
|
||||
Success,
|
||||
Backtrack,
|
||||
Cut,
|
||||
Incomplete,
|
||||
}
|
||||
|
||||
impl Severity {
|
||||
pub(crate) fn with_result<T, I: Stream, E: ParserError<I>>(result: &Result<T, E>) -> Self {
|
||||
match result {
|
||||
Ok(_) => Self::Success,
|
||||
Err(e) if e.is_backtrack() => Self::Backtrack,
|
||||
Err(e) if e.is_incomplete() => Self::Incomplete,
|
||||
_ => Self::Cut,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn start<I: Stream>(
|
||||
depth: usize,
|
||||
name: &dyn core::fmt::Display,
|
||||
count: usize,
|
||||
input: &I,
|
||||
) {
|
||||
let gutter_style = anstyle::Style::new().bold();
|
||||
let input_style = anstyle::Style::new().underline();
|
||||
let eof_style = anstyle::Style::new().fg_color(Some(anstyle::AnsiColor::Cyan.into()));
|
||||
|
||||
let (call_width, input_width) = column_widths();
|
||||
|
||||
let count = if 0 < count {
|
||||
format!(":{count}")
|
||||
} else {
|
||||
"".to_owned()
|
||||
};
|
||||
let call_column = format!("{:depth$}> {name}{count}", "");
|
||||
|
||||
// The debug version of `slice` might be wider, either due to rendering one byte as two nibbles or
|
||||
// escaping in strings.
|
||||
let mut debug_slice = format!("{:?}", crate::util::from_fn(|f| input.trace(f)));
|
||||
let (debug_slice, eof) = if let Some(debug_offset) = debug_slice
|
||||
.char_indices()
|
||||
.enumerate()
|
||||
.find_map(|(pos, (offset, _))| (input_width <= pos).then_some(offset))
|
||||
{
|
||||
debug_slice.truncate(debug_offset);
|
||||
let eof = "";
|
||||
(debug_slice, eof)
|
||||
} else {
|
||||
let eof = if debug_slice.chars().count() < input_width {
|
||||
"∅"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
(debug_slice, eof)
|
||||
};
|
||||
|
||||
let writer = anstream::stderr();
|
||||
let mut writer = writer.lock();
|
||||
let _ = writeln!(
|
||||
writer,
|
||||
"{call_column:call_width$} {gutter_style}|{gutter_reset} {input_style}{debug_slice}{input_reset}{eof_style}{eof}{eof_reset}",
|
||||
gutter_style=gutter_style.render(),
|
||||
gutter_reset=gutter_style.render_reset(),
|
||||
input_style=input_style.render(),
|
||||
input_reset=input_style.render_reset(),
|
||||
eof_style=eof_style.render(),
|
||||
eof_reset=eof_style.render_reset(),
|
||||
);
|
||||
}
|
||||
|
||||
pub(crate) fn end(
|
||||
depth: usize,
|
||||
name: &dyn core::fmt::Display,
|
||||
count: usize,
|
||||
consumed: usize,
|
||||
severity: Severity,
|
||||
) {
|
||||
let gutter_style = anstyle::Style::new().bold();
|
||||
|
||||
let (call_width, _) = column_widths();
|
||||
|
||||
let count = if 0 < count {
|
||||
format!(":{count}")
|
||||
} else {
|
||||
"".to_owned()
|
||||
};
|
||||
let call_column = format!("{:depth$}< {name}{count}", "");
|
||||
|
||||
let (status_style, status) = match severity {
|
||||
Severity::Success => {
|
||||
let style = anstyle::Style::new().fg_color(Some(anstyle::AnsiColor::Green.into()));
|
||||
let status = format!("+{consumed}");
|
||||
(style, status)
|
||||
}
|
||||
Severity::Backtrack => (
|
||||
anstyle::Style::new().fg_color(Some(anstyle::AnsiColor::Yellow.into())),
|
||||
"backtrack".to_owned(),
|
||||
),
|
||||
Severity::Cut => (
|
||||
anstyle::Style::new().fg_color(Some(anstyle::AnsiColor::Red.into())),
|
||||
"cut".to_owned(),
|
||||
),
|
||||
Severity::Incomplete => (
|
||||
anstyle::Style::new().fg_color(Some(anstyle::AnsiColor::Red.into())),
|
||||
"incomplete".to_owned(),
|
||||
),
|
||||
};
|
||||
|
||||
let writer = anstream::stderr();
|
||||
let mut writer = writer.lock();
|
||||
let _ = writeln!(
|
||||
writer,
|
||||
"{status_style}{call_column:call_width$}{status_reset} {gutter_style}|{gutter_reset} {status_style}{status}{status_reset}",
|
||||
gutter_style=gutter_style.render(),
|
||||
gutter_reset=gutter_style.render_reset(),
|
||||
status_style=status_style.render(),
|
||||
status_reset=status_style.render_reset(),
|
||||
);
|
||||
}
|
||||
|
||||
pub(crate) fn result(depth: usize, name: &dyn core::fmt::Display, severity: Severity) {
|
||||
let gutter_style = anstyle::Style::new().bold();
|
||||
|
||||
let (call_width, _) = column_widths();
|
||||
|
||||
let call_column = format!("{:depth$}| {name}", "");
|
||||
|
||||
let (status_style, status) = match severity {
|
||||
Severity::Success => (
|
||||
anstyle::Style::new().fg_color(Some(anstyle::AnsiColor::Green.into())),
|
||||
"",
|
||||
),
|
||||
Severity::Backtrack => (
|
||||
anstyle::Style::new().fg_color(Some(anstyle::AnsiColor::Yellow.into())),
|
||||
"backtrack",
|
||||
),
|
||||
Severity::Cut => (
|
||||
anstyle::Style::new().fg_color(Some(anstyle::AnsiColor::Red.into())),
|
||||
"cut",
|
||||
),
|
||||
Severity::Incomplete => (
|
||||
anstyle::Style::new().fg_color(Some(anstyle::AnsiColor::Red.into())),
|
||||
"incomplete",
|
||||
),
|
||||
};
|
||||
|
||||
let writer = anstream::stderr();
|
||||
let mut writer = writer.lock();
|
||||
let _ = writeln!(
|
||||
writer,
|
||||
"{status_style}{call_column:call_width$}{status_reset} {gutter_style}|{gutter_reset} {status_style}{status}{status_reset}",
|
||||
gutter_style=gutter_style.render(),
|
||||
gutter_reset=gutter_style.render_reset(),
|
||||
status_style=status_style.render(),
|
||||
status_reset=status_style.render_reset(),
|
||||
);
|
||||
}
|
||||
|
||||
fn column_widths() -> (usize, usize) {
|
||||
let term_width = term_width();
|
||||
|
||||
let min_call_width = 40;
|
||||
let min_input_width = 20;
|
||||
let decor_width = 3;
|
||||
let extra_width = term_width.saturating_sub(min_call_width + min_input_width + decor_width);
|
||||
let call_width = min_call_width + 2 * extra_width / 3;
|
||||
let input_width = min_input_width + extra_width / 3;
|
||||
|
||||
(call_width, input_width)
|
||||
}
|
||||
|
||||
fn term_width() -> usize {
|
||||
columns_env().or_else(query_width).unwrap_or(80)
|
||||
}
|
||||
|
||||
fn query_width() -> Option<usize> {
|
||||
use is_terminal_polyfill::IsTerminal;
|
||||
if std::io::stderr().is_terminal() {
|
||||
terminal_size::terminal_size().map(|(w, _h)| w.0.into())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn columns_env() -> Option<usize> {
|
||||
std::env::var("COLUMNS")
|
||||
.ok()
|
||||
.and_then(|c| c.parse::<usize>().ok())
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
#![cfg_attr(feature = "debug", allow(clippy::std_instead_of_core))]
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
mod internals;
|
||||
|
||||
use crate::error::ParserError;
|
||||
use crate::stream::Stream;
|
||||
use crate::Parser;
|
||||
|
||||
/// Trace the execution of the parser
|
||||
///
|
||||
/// Note that [`Parser::context`] also provides high level trace information.
|
||||
///
|
||||
/// See [tutorial][crate::_tutorial::chapter_8] for more details.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use winnow::{error::ErrMode, error::Needed};
|
||||
/// # use winnow::token::take_while;
|
||||
/// # use winnow::stream::AsChar;
|
||||
/// # use winnow::prelude::*;
|
||||
/// use winnow::combinator::trace;
|
||||
///
|
||||
/// fn short_alpha<'s>(s: &mut &'s [u8]) -> ModalResult<&'s [u8]> {
|
||||
/// trace("short_alpha",
|
||||
/// take_while(3..=6, AsChar::is_alpha)
|
||||
/// ).parse_next(s)
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(short_alpha.parse_peek(b"latin123"), Ok((&b"123"[..], &b"latin"[..])));
|
||||
/// assert_eq!(short_alpha.parse_peek(b"lengthy"), Ok((&b"y"[..], &b"length"[..])));
|
||||
/// assert_eq!(short_alpha.parse_peek(b"latin"), Ok((&b""[..], &b"latin"[..])));
|
||||
/// assert!(short_alpha.parse_peek(b"ed").is_err());
|
||||
/// assert!(short_alpha.parse_peek(b"12345").is_err());
|
||||
/// ```
|
||||
#[cfg_attr(not(feature = "debug"), allow(unused_variables))]
|
||||
#[cfg_attr(not(feature = "debug"), allow(unused_mut))]
|
||||
#[cfg_attr(not(feature = "debug"), inline(always))]
|
||||
pub fn trace<I: Stream, O, E: ParserError<I>>(
|
||||
name: impl core::fmt::Display,
|
||||
parser: impl Parser<I, O, E>,
|
||||
) -> impl Parser<I, O, E> {
|
||||
#[cfg(feature = "debug")]
|
||||
{
|
||||
internals::Trace::new(parser, name)
|
||||
}
|
||||
#[cfg(not(feature = "debug"))]
|
||||
{
|
||||
parser
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(not(feature = "debug"), allow(unused_variables))]
|
||||
pub(crate) fn trace_result<T, I: Stream, E: ParserError<I>>(
|
||||
name: impl core::fmt::Display,
|
||||
res: &Result<T, E>,
|
||||
) {
|
||||
#[cfg(feature = "debug")]
|
||||
{
|
||||
let depth = internals::Depth::existing();
|
||||
let severity = internals::Severity::with_result(res);
|
||||
internals::result(*depth, &name, severity);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct DisplayDebug<D>(pub(crate) D);
|
||||
|
||||
impl<D: core::fmt::Debug> core::fmt::Display for DisplayDebug<D> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "{:?}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "std")]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
#[cfg(unix)]
|
||||
#[cfg(feature = "debug")]
|
||||
fn example() {
|
||||
use term_transcript::{test::TestConfig, ShellOptions};
|
||||
|
||||
let path = snapbox::cmd::compile_example("string", ["--features=debug"]).unwrap();
|
||||
|
||||
let current_dir = path.parent().unwrap();
|
||||
let cmd = path.file_name().unwrap();
|
||||
// HACK: term_transcript doesn't allow non-UTF8 paths
|
||||
let cmd = format!("./{}", cmd.to_string_lossy());
|
||||
|
||||
TestConfig::new(
|
||||
ShellOptions::default()
|
||||
.with_current_dir(current_dir)
|
||||
.with_env("CLICOLOR_FORCE", "1"),
|
||||
)
|
||||
.test("assets/trace.svg", [format!(r#"{cmd} '"abc"'"#).as_str()]);
|
||||
}
|
||||
+561
@@ -0,0 +1,561 @@
|
||||
use crate::combinator::empty;
|
||||
use crate::combinator::fail;
|
||||
use crate::combinator::opt;
|
||||
use crate::combinator::trace;
|
||||
use crate::error::ParserError;
|
||||
use crate::stream::Stream;
|
||||
use crate::stream::StreamIsPartial;
|
||||
use crate::Parser;
|
||||
use crate::Result;
|
||||
|
||||
/// Parses an expression based on operator precedence.
|
||||
///
|
||||
/// It uses a Pratt parsing algorithm, where operators are
|
||||
/// associated with a binding power. The higher the power,
|
||||
/// the more tightly an operator will bind to its operands.
|
||||
///
|
||||
/// This method returns an [`Expression`], which configures
|
||||
/// the Pratt parser.
|
||||
///
|
||||
/// Each operator type is configured with [`Prefix`], [`Postfix`],
|
||||
/// and [`Infix`]. These describe the operator's binding power,
|
||||
/// a function that applies the operator to its operand, and the
|
||||
/// operator's associativity (infix only).
|
||||
///
|
||||
/// For a more full-featured example, look at the [C-style Expressions][crate::_topic::language#c-style-expressions]
|
||||
/// topic.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Parsing a simple arithmetic expression without parenthesis.
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(feature = "ascii")] {
|
||||
/// # use winnow::prelude::*;
|
||||
/// # use winnow::error::ContextError;
|
||||
/// # use winnow::ascii::digit1;
|
||||
/// # use winnow::combinator::{dispatch, fail};
|
||||
/// # use winnow::token::any;
|
||||
/// use winnow::combinator::expression;
|
||||
/// use winnow::combinator::{Prefix, Postfix, Infix};
|
||||
///
|
||||
/// fn parser<'i>() -> impl Parser<&'i str, i32, ContextError> {
|
||||
/// move |i: &mut &str| {
|
||||
/// use Infix::*;
|
||||
/// expression(digit1.parse_to::<i32>()) // operands are 32-bit integers
|
||||
/// .prefix(dispatch! {any;
|
||||
/// '-' => Prefix(12, |_, a: i32| Ok(-a)),
|
||||
/// _ => fail,
|
||||
/// })
|
||||
/// .infix(dispatch! {any;
|
||||
/// '+' => Left(5, |_, a, b| Ok(a + b)),
|
||||
/// '-' => Left(5, |_, a, b| Ok(a - b)),
|
||||
/// '*' => Left(7, |_, a, b| Ok(a * b)),
|
||||
/// '/' => Left(7, |_, a: i32, b| Ok(a.checked_div(b).unwrap_or_default())),
|
||||
/// _ => fail,
|
||||
/// })
|
||||
/// .postfix(dispatch! {any;
|
||||
/// '!' => Postfix(15, |_, a| if a < 1 { Ok(1) } else { Ok((1..=a).fold(1, |acc, a| acc*a)) }),
|
||||
/// _ => fail,
|
||||
/// })
|
||||
/// .parse_next(i)
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(parser().parse("1+1"), Ok(2));
|
||||
/// assert_eq!(parser().parse("0!"), Ok(1));
|
||||
/// assert_eq!(parser().parse("-1*5*2*10+30/3!"), Ok(-95));
|
||||
/// # }
|
||||
/// ```
|
||||
#[doc(alias = "pratt")]
|
||||
#[doc(alias = "separated")]
|
||||
#[doc(alias = "shunting_yard")]
|
||||
#[doc(alias = "precedence_climbing")]
|
||||
#[inline(always)]
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn expression<I, ParseOperand, O, E>(
|
||||
parse_operand: ParseOperand,
|
||||
) -> Expression<
|
||||
I,
|
||||
O,
|
||||
ParseOperand,
|
||||
impl Parser<I, Prefix<I, O, E>, E>,
|
||||
impl Parser<I, Postfix<I, O, E>, E>,
|
||||
impl Parser<I, Infix<I, O, E>, E>,
|
||||
E,
|
||||
>
|
||||
where
|
||||
I: Stream + StreamIsPartial,
|
||||
ParseOperand: Parser<I, O, E>,
|
||||
E: ParserError<I>,
|
||||
{
|
||||
Expression {
|
||||
precedence_level: 0,
|
||||
parse_operand,
|
||||
parse_prefix: fail,
|
||||
parse_postfix: fail,
|
||||
parse_infix: fail,
|
||||
marker: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// A helper struct for [`expression()`].
|
||||
///
|
||||
/// Holds the configuration for the Pratt parser, including
|
||||
/// the operator and operand parsers. A precedence level can
|
||||
/// also be set, which is useful to disambiguate parse trees
|
||||
/// based on the parent operator's precedence.
|
||||
///
|
||||
/// Implements [`Parser`]. When parsing an input, it applies
|
||||
/// the Pratt parser.
|
||||
pub struct Expression<I, O, ParseOperand, Pre, Post, Pix, E>
|
||||
where
|
||||
I: Stream + StreamIsPartial,
|
||||
ParseOperand: Parser<I, O, E>,
|
||||
E: ParserError<I>,
|
||||
{
|
||||
precedence_level: i64,
|
||||
parse_operand: ParseOperand,
|
||||
parse_prefix: Pre,
|
||||
parse_postfix: Post,
|
||||
parse_infix: Pix,
|
||||
marker: core::marker::PhantomData<(I, O, E)>,
|
||||
}
|
||||
|
||||
impl<I, O, ParseOperand, Pre, Post, Pix, E> Expression<I, O, ParseOperand, Pre, Post, Pix, E>
|
||||
where
|
||||
ParseOperand: Parser<I, O, E>,
|
||||
I: Stream + StreamIsPartial,
|
||||
E: ParserError<I>,
|
||||
{
|
||||
/// Sets the prefix operator parser.
|
||||
///
|
||||
/// The parser should parse the input to a [`Prefix`],
|
||||
/// which contains the operator's binding power and
|
||||
/// a fold function which applies the operator to its
|
||||
/// operands.
|
||||
#[inline(always)]
|
||||
pub fn prefix<NewParsePrefix>(
|
||||
self,
|
||||
parser: NewParsePrefix,
|
||||
) -> Expression<I, O, ParseOperand, NewParsePrefix, Post, Pix, E>
|
||||
where
|
||||
NewParsePrefix: Parser<I, Prefix<I, O, E>, E>,
|
||||
{
|
||||
Expression {
|
||||
precedence_level: self.precedence_level,
|
||||
parse_operand: self.parse_operand,
|
||||
parse_prefix: parser,
|
||||
parse_postfix: self.parse_postfix,
|
||||
parse_infix: self.parse_infix,
|
||||
marker: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the postfix operator parser.
|
||||
///
|
||||
/// The parser should parse the input to a [`Postfix`],
|
||||
/// which contains the operator's binding power and
|
||||
/// a fold function which applies the operator to its
|
||||
/// operands.
|
||||
#[inline(always)]
|
||||
pub fn postfix<NewParsePostfix>(
|
||||
self,
|
||||
parser: NewParsePostfix,
|
||||
) -> Expression<I, O, ParseOperand, Pre, NewParsePostfix, Pix, E>
|
||||
where
|
||||
NewParsePostfix: Parser<I, Postfix<I, O, E>, E>,
|
||||
{
|
||||
Expression {
|
||||
precedence_level: self.precedence_level,
|
||||
parse_operand: self.parse_operand,
|
||||
parse_prefix: self.parse_prefix,
|
||||
parse_postfix: parser,
|
||||
parse_infix: self.parse_infix,
|
||||
marker: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the infix operator parser.
|
||||
///
|
||||
/// The parser should parse the input to a [`Infix`],
|
||||
/// which contains the operator's binding power and
|
||||
/// a fold function which applies the operator to its
|
||||
/// operands.
|
||||
#[inline(always)]
|
||||
pub fn infix<NewParseInfix>(
|
||||
self,
|
||||
parser: NewParseInfix,
|
||||
) -> Expression<I, O, ParseOperand, Pre, Post, NewParseInfix, E>
|
||||
where
|
||||
NewParseInfix: Parser<I, Infix<I, O, E>, E>,
|
||||
{
|
||||
Expression {
|
||||
precedence_level: self.precedence_level,
|
||||
parse_operand: self.parse_operand,
|
||||
parse_prefix: self.parse_prefix,
|
||||
parse_postfix: self.parse_postfix,
|
||||
parse_infix: parser,
|
||||
marker: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the precedence level for the current instance of the parser.
|
||||
///
|
||||
/// It defaults to 0, which is traditionally treated as the "lowest"
|
||||
/// possible precedence when parsing an expression.
|
||||
///
|
||||
/// This is useful to disambiguate grammars based on the parent operator's
|
||||
/// precedence. This comes up primarily when parsing recursive expressions.
|
||||
///
|
||||
/// The parsing machinery underpinning [`Expression`] assumes that a "more
|
||||
/// tightly binding" operator is numerically large, while a "more loosely
|
||||
/// binding" operator is numerically small. For example, `13` is a higher
|
||||
/// precedence level than `1` because `13 > 1`.
|
||||
///
|
||||
/// Other ways of describing this relationship:
|
||||
/// - `13` has a higher precedence compared to `1`
|
||||
/// - `13` has a higher binding power compared to `1`
|
||||
///
|
||||
/// Note: Binding power and precedence both refer to the same concept and
|
||||
/// may be used interchangeably.
|
||||
///
|
||||
/// # Motivation
|
||||
///
|
||||
/// If you don't understand why this is useful to have, this section tries
|
||||
/// to explain in more detail.
|
||||
///
|
||||
/// The [C-style Expressions][crate::_topic::arithmetic#c-style-expression]
|
||||
/// example has source code for parsing the expression described below, and
|
||||
/// can provide a clearer usage example.
|
||||
///
|
||||
/// Consider the following expression in the C language:
|
||||
///
|
||||
/// ```c
|
||||
/// int x = (1 == 1 ? 0 : 1, -123); // <-- let's parse this
|
||||
/// printf("%d\n", x); // -123
|
||||
/// ```
|
||||
///
|
||||
/// Let's look at the right-hand side of the expression on the first line,
|
||||
/// and replace some of the sub-expressions with symbols:
|
||||
///
|
||||
/// ```text
|
||||
/// (1 == 1 ? 0 : 1, -123) // rhs
|
||||
/// (a ? b : c, d ) // symbolic
|
||||
/// (a ? b : c, d) // remove whitespace
|
||||
/// (, (? a b c) d) // prefix notation
|
||||
/// ```
|
||||
///
|
||||
/// Written symbolically:
|
||||
/// - `a` is the condition, like `1 == 1`
|
||||
/// - `b` is the value when the condition is true
|
||||
/// - `c` is the value when the condition is false
|
||||
/// - `d` is a secondary expression unrelated to the ternary
|
||||
///
|
||||
/// In prefix notation, it's easier to see the specific operators and what
|
||||
/// they bind to:
|
||||
/// - COMMA (`,`) binds to `(? a b c)` and `d`
|
||||
/// - TERNARY (`?`) binds to `a`, `b`, and `c`
|
||||
///
|
||||
/// ## Parsing `c` and `d`
|
||||
///
|
||||
/// Let's focus on parsing the sub-expressions `c` and `d`, as that
|
||||
/// motivates why a parser precedence level is necessary.
|
||||
///
|
||||
/// To parse `c`, we would really like to re-use the parser produced by
|
||||
/// [`expression()`], because `c` is really *any* valid expression that
|
||||
/// can be parsed by `expression()` already.
|
||||
///
|
||||
/// However, we can't re-use the parser naively. When parsing `c`, we need
|
||||
/// to "escape" from the inner parser when encountering the comma separating
|
||||
/// `c` from `d`.
|
||||
///
|
||||
/// The reason we have to "escape" is because of how operator precedence is
|
||||
/// defined in the C language: the comma operator has the lowest precedence
|
||||
/// among all the operators. When we're parsing `c`, we're in the context of
|
||||
/// the ternary operator. We don't want to parse any valid expression! Just
|
||||
/// what the ternary operator captures.
|
||||
///
|
||||
/// That's where the precedence level comes in: you specify the minimum
|
||||
/// precedence this parser is willing to accept. If you come across an
|
||||
/// expression in the top-level with a lower binding power than the starting
|
||||
/// precedence, you know to stop parsing.
|
||||
///
|
||||
/// The parsing machinery inside of [`Expression`] handles most of this for
|
||||
/// you, but it can't determine what the precedence level should be for a
|
||||
/// given expression. That's a language-specific detail, and it depends on
|
||||
/// what you want to parse.
|
||||
#[inline(always)]
|
||||
pub fn current_precedence_level(
|
||||
mut self,
|
||||
level: i64,
|
||||
) -> Expression<I, O, ParseOperand, Pre, Post, Pix, E> {
|
||||
self.precedence_level = level;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, O, Pop, Pre, Post, Pix, E> Parser<I, O, E> for Expression<I, O, Pop, Pre, Post, Pix, E>
|
||||
where
|
||||
I: Stream + StreamIsPartial,
|
||||
Pop: Parser<I, O, E>,
|
||||
Pix: Parser<I, Infix<I, O, E>, E>,
|
||||
Pre: Parser<I, Prefix<I, O, E>, E>,
|
||||
Post: Parser<I, Postfix<I, O, E>, E>,
|
||||
E: ParserError<I>,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn parse_next(&mut self, input: &mut I) -> Result<O, E> {
|
||||
trace("expression", move |i: &mut I| {
|
||||
expression_impl(
|
||||
i,
|
||||
&mut self.parse_operand,
|
||||
&mut self.parse_prefix,
|
||||
&mut self.parse_postfix,
|
||||
&mut self.parse_infix,
|
||||
self.precedence_level,
|
||||
)
|
||||
})
|
||||
.parse_next(input)
|
||||
}
|
||||
}
|
||||
|
||||
/// Opaque implementation of the Pratt parser.
|
||||
fn expression_impl<I, O, Pop, Pre, Post, Pix, E>(
|
||||
i: &mut I,
|
||||
parse_operand: &mut Pop,
|
||||
prefix: &mut Pre,
|
||||
postfix: &mut Post,
|
||||
infix: &mut Pix,
|
||||
min_power: i64,
|
||||
) -> Result<O, E>
|
||||
where
|
||||
I: Stream + StreamIsPartial,
|
||||
Pop: Parser<I, O, E>,
|
||||
Pix: Parser<I, Infix<I, O, E>, E>,
|
||||
Pre: Parser<I, Prefix<I, O, E>, E>,
|
||||
Post: Parser<I, Postfix<I, O, E>, E>,
|
||||
E: ParserError<I>,
|
||||
{
|
||||
let operand = opt(trace("operand", parse_operand.by_ref())).parse_next(i)?;
|
||||
let mut operand = if let Some(operand) = operand {
|
||||
operand
|
||||
} else {
|
||||
// Prefix unary operators
|
||||
let len = i.eof_offset();
|
||||
let Prefix(power, fold_prefix) = trace("prefix", prefix.by_ref()).parse_next(i)?;
|
||||
// infinite loop check: the parser must always consume
|
||||
if i.eof_offset() == len {
|
||||
return Err(E::assert(i, "`prefix` parsers must always consume"));
|
||||
}
|
||||
let operand = expression_impl(i, parse_operand, prefix, postfix, infix, power)?;
|
||||
fold_prefix(i, operand)?
|
||||
};
|
||||
|
||||
// A variable to stop the `'parse` loop when `Assoc::Neither` with the same
|
||||
// precedence is encountered e.g. `a == b == c`. `Assoc::Neither` has similar
|
||||
// associativity rules as `Assoc::Left`, but we stop parsing when the next operator
|
||||
// is the same as the current one.
|
||||
let mut prev_op_is_neither = None;
|
||||
'parse: while i.eof_offset() > 0 {
|
||||
// Postfix unary operators
|
||||
let start = i.checkpoint();
|
||||
if let Some(Postfix(power, fold_postfix)) =
|
||||
opt(trace("postfix", postfix.by_ref())).parse_next(i)?
|
||||
{
|
||||
// control precedence over the prefix e.g.:
|
||||
// `--(i++)` or `(--i)++`
|
||||
if power < min_power {
|
||||
i.reset(&start);
|
||||
break 'parse;
|
||||
}
|
||||
operand = fold_postfix(i, operand)?;
|
||||
|
||||
continue 'parse;
|
||||
}
|
||||
|
||||
// Infix binary operators
|
||||
let start = i.checkpoint();
|
||||
let parse_result = opt(trace("infix", infix.by_ref())).parse_next(i)?;
|
||||
if let Some(infix_op) = parse_result {
|
||||
let mut is_neither = None;
|
||||
let (lpower, rpower, fold_infix) = match infix_op {
|
||||
Infix::Right(p, f) => (p, p - 1, f),
|
||||
Infix::Left(p, f) => (p, p + 1, f),
|
||||
Infix::Neither(p, f) => {
|
||||
is_neither = Some(p);
|
||||
(p, p + 1, f)
|
||||
}
|
||||
};
|
||||
if lpower < min_power
|
||||
// MSRV: `is_some_and`
|
||||
|| match prev_op_is_neither {
|
||||
None => false,
|
||||
Some(p) => lpower == p,
|
||||
}
|
||||
{
|
||||
i.reset(&start);
|
||||
break 'parse;
|
||||
}
|
||||
prev_op_is_neither = is_neither;
|
||||
let rhs = expression_impl(i, parse_operand, prefix, postfix, infix, rpower)?;
|
||||
operand = fold_infix(i, operand, rhs)?;
|
||||
|
||||
continue 'parse;
|
||||
}
|
||||
|
||||
break 'parse;
|
||||
}
|
||||
|
||||
Ok(operand)
|
||||
}
|
||||
|
||||
/// Define an [`expression()`]'s prefix operator
|
||||
///
|
||||
/// It requires an operator binding power, as well as a
|
||||
/// fold function which applies the operator.
|
||||
pub struct Prefix<I, O, E>(
|
||||
/// Binding power
|
||||
pub i64,
|
||||
/// Unary operator
|
||||
pub fn(&mut I, O) -> Result<O, E>,
|
||||
);
|
||||
|
||||
impl<I, O, E> Clone for Prefix<I, O, E> {
|
||||
#[inline(always)]
|
||||
fn clone(&self) -> Self {
|
||||
Prefix(self.0, self.1)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: Stream, O, E: ParserError<I>> Parser<I, Prefix<I, O, E>, E> for Prefix<I, O, E> {
|
||||
#[inline(always)]
|
||||
fn parse_next(&mut self, input: &mut I) -> Result<Prefix<I, O, E>, E> {
|
||||
empty.value(self.clone()).parse_next(input)
|
||||
}
|
||||
}
|
||||
|
||||
/// Define an [`expression()`]'s postfix operator
|
||||
///
|
||||
/// It requires an operator binding power, as well as a
|
||||
/// fold function which applies the operator.
|
||||
pub struct Postfix<I, O, E>(
|
||||
/// Binding power
|
||||
pub i64,
|
||||
/// Unary operator
|
||||
pub fn(&mut I, O) -> Result<O, E>,
|
||||
);
|
||||
|
||||
impl<I, O, E> Clone for Postfix<I, O, E> {
|
||||
#[inline(always)]
|
||||
fn clone(&self) -> Self {
|
||||
Postfix(self.0, self.1)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: Stream, O, E: ParserError<I>> Parser<I, Postfix<I, O, E>, E> for Postfix<I, O, E> {
|
||||
#[inline(always)]
|
||||
fn parse_next(&mut self, input: &mut I) -> Result<Postfix<I, O, E>, E> {
|
||||
empty.value(self.clone()).parse_next(input)
|
||||
}
|
||||
}
|
||||
|
||||
/// Define an [`expression()`]'s infix operator
|
||||
///
|
||||
/// It requires an operator binding power, as well as a
|
||||
/// fold function which applies the operator.
|
||||
pub enum Infix<I, O, E> {
|
||||
/// Left-associative operator
|
||||
///
|
||||
/// The operators will bind more tightly to their rightmost operands.
|
||||
///
|
||||
/// e.g `A op B op C` -> `(A op B) op C`
|
||||
Left(
|
||||
/// Binding power
|
||||
i64,
|
||||
/// Binary operator
|
||||
fn(&mut I, O, O) -> Result<O, E>,
|
||||
),
|
||||
/// Right-associative operator
|
||||
///
|
||||
/// The operators will bind more tightly to their leftmost operands.
|
||||
///
|
||||
/// e.g `A op B op C` -> `A op (B op C)`
|
||||
Right(
|
||||
/// Binding power
|
||||
i64,
|
||||
/// Binary operator
|
||||
fn(&mut I, O, O) -> Result<O, E>,
|
||||
),
|
||||
/// Neither left or right associative
|
||||
///
|
||||
/// `Infix::Neither` has similar associativity rules as `Assoc::Left`, but we stop
|
||||
/// parsing when the next operator is the same as the current one.
|
||||
///
|
||||
/// e.g. `a == b == c` -> `(a == b)`, fail: `(== c)`
|
||||
Neither(
|
||||
/// Binding power
|
||||
i64,
|
||||
/// Binary operator
|
||||
fn(&mut I, O, O) -> Result<O, E>,
|
||||
),
|
||||
}
|
||||
|
||||
impl<I, O, E> Clone for Infix<I, O, E> {
|
||||
#[inline(always)]
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
Infix::Left(p, f) => Infix::Left(*p, *f),
|
||||
Infix::Right(p, f) => Infix::Right(*p, *f),
|
||||
Infix::Neither(p, f) => Infix::Neither(*p, *f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: Stream, O, E: ParserError<I>> Parser<I, Infix<I, O, E>, E> for Infix<I, O, E> {
|
||||
#[inline(always)]
|
||||
fn parse_next(&mut self, input: &mut I) -> Result<Infix<I, O, E>, E> {
|
||||
empty.value(self.clone()).parse_next(input)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "ascii"))]
|
||||
mod tests {
|
||||
use crate::ascii::digit1;
|
||||
use crate::combinator::fail;
|
||||
use crate::dispatch;
|
||||
use crate::error::ContextError;
|
||||
use crate::token::any;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn parser<'i>() -> impl Parser<&'i str, i32, ContextError> {
|
||||
move |i: &mut &str| {
|
||||
use Infix::*;
|
||||
expression(digit1.parse_to::<i32>())
|
||||
.current_precedence_level(0)
|
||||
.prefix(dispatch! {any;
|
||||
'+' => Prefix(12, |_, a| Ok(a)),
|
||||
'-' => Prefix(12, |_, a: i32| Ok(-a)),
|
||||
_ => fail
|
||||
})
|
||||
.infix(dispatch! {any;
|
||||
'+' => Left(5, |_, a, b| Ok(a + b)),
|
||||
'-' => Left(5, |_, a, b| Ok(a - b)),
|
||||
'*' => Left(7, |_, a, b| Ok(a * b)),
|
||||
'/' => Left(7, |_, a, b| Ok(a / b)),
|
||||
'%' => Left(7, |_, a, b| Ok(a % b)),
|
||||
'^' => Left(9, |_, a, b| Ok(a ^ b)),
|
||||
_ => fail
|
||||
})
|
||||
.parse_next(i)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expression() {
|
||||
assert_eq!(parser().parse("-3+-3*4"), Ok(-15));
|
||||
assert_eq!(parser().parse("+2+3*4"), Ok(14));
|
||||
assert_eq!(parser().parse("2*3+4"), Ok(10));
|
||||
}
|
||||
}
|
||||
+794
@@ -0,0 +1,794 @@
|
||||
//! Opaque implementations of [`Parser`]
|
||||
|
||||
use crate::combinator::trace;
|
||||
use crate::combinator::trace_result;
|
||||
use crate::combinator::DisplayDebug;
|
||||
#[cfg(feature = "unstable-recover")]
|
||||
#[cfg(feature = "std")]
|
||||
use crate::error::FromRecoverableError;
|
||||
use crate::error::ParseError;
|
||||
use crate::error::{AddContext, FromExternalError, ParserError};
|
||||
#[cfg(feature = "unstable-recover")]
|
||||
#[cfg(feature = "std")]
|
||||
use crate::stream::Recover;
|
||||
use crate::stream::StreamIsPartial;
|
||||
use crate::stream::{Location, Stream};
|
||||
use crate::{Parser, Result};
|
||||
use core::borrow::Borrow;
|
||||
use core::ops::Range;
|
||||
|
||||
/// Iterator implementation for [`Parser::parse_iter`]
|
||||
pub struct ParseIter<'p, P, I, O, E>
|
||||
where
|
||||
I: Stream,
|
||||
{
|
||||
pub(crate) parser: &'p mut P,
|
||||
pub(crate) input: Option<I>,
|
||||
pub(crate) start: Option<I::Checkpoint>,
|
||||
pub(crate) marker: core::marker::PhantomData<(O, E)>,
|
||||
}
|
||||
|
||||
impl<'p, I, O, E, P> Iterator for ParseIter<'p, P, I, O, E>
|
||||
where
|
||||
P: Parser<I, O, E>,
|
||||
I: Stream,
|
||||
// Force users to deal with `Incomplete` when `StreamIsPartial<true>`
|
||||
I: StreamIsPartial,
|
||||
E: ParserError<I>,
|
||||
<E as ParserError<I>>::Inner: ParserError<I>,
|
||||
{
|
||||
type Item = Result<O, ParseError<I, <E as ParserError<I>>::Inner>>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let input = self.input.as_mut()?;
|
||||
let len = input.eof_offset();
|
||||
if len == 0 {
|
||||
self.input = None;
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut output = self.parser.parse_next(input);
|
||||
// infinite loop check: the parser must always consume
|
||||
if output.is_ok() && input.eof_offset() == len {
|
||||
let err = <E as ParserError<I>>::assert(
|
||||
input,
|
||||
"`Parser::parse_iter` parsers must always consume",
|
||||
);
|
||||
output = Err(err);
|
||||
}
|
||||
|
||||
match output {
|
||||
Ok(output) => Some(Ok(output)),
|
||||
Err(err) => {
|
||||
let err = err.into_inner().unwrap_or_else(|_err| {
|
||||
panic!("complete parsers should not report `ErrMode::Incomplete(_)`")
|
||||
});
|
||||
let input = self.input.take()?;
|
||||
let start = self.start.take()?;
|
||||
Some(Err(ParseError::new(input, start, err)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// [`Parser`] implementation for [`Parser::by_ref`]
|
||||
pub struct ByRef<'p, P, I, O, E> {
|
||||
pub(crate) p: &'p mut P,
|
||||
pub(crate) marker: core::marker::PhantomData<(I, O, E)>,
|
||||
}
|
||||
|
||||
impl<I, O, E, P> Parser<I, O, E> for ByRef<'_, P, I, O, E>
|
||||
where
|
||||
P: Parser<I, O, E>,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn parse_next(&mut self, i: &mut I) -> Result<O, E> {
|
||||
self.p.parse_next(i)
|
||||
}
|
||||
}
|
||||
|
||||
/// [`Parser`] implementation for [`Parser::map`]
|
||||
pub struct Map<F, G, I, O, O2, E>
|
||||
where
|
||||
F: Parser<I, O, E>,
|
||||
G: FnMut(O) -> O2,
|
||||
{
|
||||
pub(crate) parser: F,
|
||||
pub(crate) map: G,
|
||||
pub(crate) marker: core::marker::PhantomData<(I, O, E, O2)>,
|
||||
}
|
||||
|
||||
impl<F, G, I, O, O2, E> Parser<I, O2, E> for Map<F, G, I, O, O2, E>
|
||||
where
|
||||
F: Parser<I, O, E>,
|
||||
G: FnMut(O) -> O2,
|
||||
{
|
||||
#[inline]
|
||||
fn parse_next(&mut self, i: &mut I) -> Result<O2, E> {
|
||||
match self.parser.parse_next(i) {
|
||||
Err(e) => Err(e),
|
||||
Ok(o) => Ok((self.map)(o)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// [`Parser`] implementation for [`Parser::try_map`]
|
||||
pub struct TryMap<F, G, I, O, O2, E, E2>
|
||||
where
|
||||
F: Parser<I, O, E>,
|
||||
G: FnMut(O) -> Result<O2, E2>,
|
||||
I: Stream,
|
||||
E: FromExternalError<I, E2>,
|
||||
E: ParserError<I>,
|
||||
{
|
||||
pub(crate) parser: F,
|
||||
pub(crate) map: G,
|
||||
pub(crate) marker: core::marker::PhantomData<(I, O, O2, E, E2)>,
|
||||
}
|
||||
|
||||
impl<F, G, I, O, O2, E, E2> Parser<I, O2, E> for TryMap<F, G, I, O, O2, E, E2>
|
||||
where
|
||||
F: Parser<I, O, E>,
|
||||
G: FnMut(O) -> Result<O2, E2>,
|
||||
I: Stream,
|
||||
E: FromExternalError<I, E2>,
|
||||
E: ParserError<I>,
|
||||
{
|
||||
#[inline]
|
||||
fn parse_next(&mut self, input: &mut I) -> Result<O2, E> {
|
||||
let start = input.checkpoint();
|
||||
let o = self.parser.parse_next(input)?;
|
||||
let res = (self.map)(o).map_err(|err| {
|
||||
input.reset(&start);
|
||||
E::from_external_error(input, err)
|
||||
});
|
||||
trace_result("verify", &res);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
/// [`Parser`] implementation for [`Parser::verify_map`]
|
||||
pub struct VerifyMap<F, G, I, O, O2, E>
|
||||
where
|
||||
F: Parser<I, O, E>,
|
||||
G: FnMut(O) -> Option<O2>,
|
||||
I: Stream,
|
||||
E: ParserError<I>,
|
||||
{
|
||||
pub(crate) parser: F,
|
||||
pub(crate) map: G,
|
||||
pub(crate) marker: core::marker::PhantomData<(I, O, E, O2)>,
|
||||
}
|
||||
|
||||
impl<F, G, I, O, O2, E> Parser<I, O2, E> for VerifyMap<F, G, I, O, O2, E>
|
||||
where
|
||||
F: Parser<I, O, E>,
|
||||
G: FnMut(O) -> Option<O2>,
|
||||
I: Stream,
|
||||
E: ParserError<I>,
|
||||
{
|
||||
#[inline]
|
||||
fn parse_next(&mut self, input: &mut I) -> Result<O2, E> {
|
||||
let start = input.checkpoint();
|
||||
let o = self.parser.parse_next(input)?;
|
||||
let res = (self.map)(o).ok_or_else(|| {
|
||||
input.reset(&start);
|
||||
ParserError::from_input(input)
|
||||
});
|
||||
trace_result("verify", &res);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
/// [`Parser`] implementation for [`Parser::and_then`]
|
||||
pub struct AndThen<F, G, I, O, O2, E>
|
||||
where
|
||||
F: Parser<I, O, E>,
|
||||
G: Parser<O, O2, E>,
|
||||
O: StreamIsPartial,
|
||||
I: Stream,
|
||||
{
|
||||
pub(crate) outer: F,
|
||||
pub(crate) inner: G,
|
||||
pub(crate) marker: core::marker::PhantomData<(I, O, O2, E)>,
|
||||
}
|
||||
|
||||
impl<F, G, I, O, O2, E> Parser<I, O2, E> for AndThen<F, G, I, O, O2, E>
|
||||
where
|
||||
F: Parser<I, O, E>,
|
||||
G: Parser<O, O2, E>,
|
||||
O: StreamIsPartial,
|
||||
I: Stream,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn parse_next(&mut self, i: &mut I) -> Result<O2, E> {
|
||||
let start = i.checkpoint();
|
||||
let mut o = self.outer.parse_next(i)?;
|
||||
let _ = o.complete();
|
||||
let o2 = self.inner.parse_next(&mut o).map_err(|err| {
|
||||
i.reset(&start);
|
||||
err
|
||||
})?;
|
||||
Ok(o2)
|
||||
}
|
||||
}
|
||||
|
||||
/// [`Parser`] implementation for [`Parser::parse_to`]
|
||||
pub struct ParseTo<P, I, O, O2, E>
|
||||
where
|
||||
P: Parser<I, O, E>,
|
||||
I: Stream,
|
||||
O: crate::stream::ParseSlice<O2>,
|
||||
E: ParserError<I>,
|
||||
{
|
||||
pub(crate) p: P,
|
||||
pub(crate) marker: core::marker::PhantomData<(I, O, O2, E)>,
|
||||
}
|
||||
|
||||
impl<P, I, O, O2, E> Parser<I, O2, E> for ParseTo<P, I, O, O2, E>
|
||||
where
|
||||
P: Parser<I, O, E>,
|
||||
I: Stream,
|
||||
O: crate::stream::ParseSlice<O2>,
|
||||
E: ParserError<I>,
|
||||
{
|
||||
#[inline]
|
||||
fn parse_next(&mut self, i: &mut I) -> Result<O2, E> {
|
||||
let start = i.checkpoint();
|
||||
let o = self.p.parse_next(i)?;
|
||||
let res = o.parse_slice().ok_or_else(|| {
|
||||
i.reset(&start);
|
||||
ParserError::from_input(i)
|
||||
});
|
||||
trace_result("verify", &res);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
/// [`Parser`] implementation for [`Parser::flat_map`]
|
||||
pub struct FlatMap<F, G, H, I, O, O2, E>
|
||||
where
|
||||
F: Parser<I, O, E>,
|
||||
G: FnMut(O) -> H,
|
||||
H: Parser<I, O2, E>,
|
||||
{
|
||||
pub(crate) f: F,
|
||||
pub(crate) g: G,
|
||||
pub(crate) marker: core::marker::PhantomData<(H, I, O, O2, E)>,
|
||||
}
|
||||
|
||||
impl<F, G, H, I, O, O2, E> Parser<I, O2, E> for FlatMap<F, G, H, I, O, O2, E>
|
||||
where
|
||||
F: Parser<I, O, E>,
|
||||
G: FnMut(O) -> H,
|
||||
H: Parser<I, O2, E>,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn parse_next(&mut self, i: &mut I) -> Result<O2, E> {
|
||||
let o = self.f.parse_next(i)?;
|
||||
(self.g)(o).parse_next(i)
|
||||
}
|
||||
}
|
||||
|
||||
/// [`Parser`] implementation for [`Parser::complete_err`]
|
||||
pub struct CompleteErr<P, I, O, E> {
|
||||
pub(crate) p: P,
|
||||
pub(crate) marker: core::marker::PhantomData<(I, O, E)>,
|
||||
}
|
||||
|
||||
impl<P, I, O, E> Parser<I, O, E> for CompleteErr<P, I, O, E>
|
||||
where
|
||||
P: Parser<I, O, E>,
|
||||
I: Stream,
|
||||
E: ParserError<I>,
|
||||
{
|
||||
#[inline]
|
||||
fn parse_next(&mut self, input: &mut I) -> Result<O, E> {
|
||||
trace("complete_err", |input: &mut I| {
|
||||
match (self.p).parse_next(input) {
|
||||
Err(err) => match err.needed() {
|
||||
Some(_) => Err(ParserError::from_input(input)),
|
||||
None => Err(err),
|
||||
},
|
||||
rest => rest,
|
||||
}
|
||||
})
|
||||
.parse_next(input)
|
||||
}
|
||||
}
|
||||
|
||||
/// [`Parser`] implementation for [`Parser::verify`]
|
||||
pub struct Verify<F, G, I, O, O2, E>
|
||||
where
|
||||
F: Parser<I, O, E>,
|
||||
G: FnMut(&O2) -> bool,
|
||||
I: Stream,
|
||||
O: Borrow<O2>,
|
||||
O2: ?Sized,
|
||||
E: ParserError<I>,
|
||||
{
|
||||
pub(crate) parser: F,
|
||||
pub(crate) filter: G,
|
||||
pub(crate) marker: core::marker::PhantomData<(I, O, E, O2)>,
|
||||
}
|
||||
|
||||
impl<F, G, I, O, O2, E> Parser<I, O, E> for Verify<F, G, I, O, O2, E>
|
||||
where
|
||||
F: Parser<I, O, E>,
|
||||
G: FnMut(&O2) -> bool,
|
||||
I: Stream,
|
||||
O: Borrow<O2>,
|
||||
O2: ?Sized,
|
||||
E: ParserError<I>,
|
||||
{
|
||||
#[inline]
|
||||
fn parse_next(&mut self, input: &mut I) -> Result<O, E> {
|
||||
let start = input.checkpoint();
|
||||
let o = self.parser.parse_next(input)?;
|
||||
let res = (self.filter)(o.borrow()).then_some(o).ok_or_else(|| {
|
||||
input.reset(&start);
|
||||
ParserError::from_input(input)
|
||||
});
|
||||
trace_result("verify", &res);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
/// [`Parser`] implementation for [`Parser::value`]
|
||||
pub struct Value<F, I, O, O2, E>
|
||||
where
|
||||
F: Parser<I, O, E>,
|
||||
O2: Clone,
|
||||
{
|
||||
pub(crate) parser: F,
|
||||
pub(crate) val: O2,
|
||||
pub(crate) marker: core::marker::PhantomData<(I, O, E)>,
|
||||
}
|
||||
|
||||
impl<F, I, O, O2, E> Parser<I, O2, E> for Value<F, I, O, O2, E>
|
||||
where
|
||||
F: Parser<I, O, E>,
|
||||
O2: Clone,
|
||||
{
|
||||
#[inline]
|
||||
fn parse_next(&mut self, input: &mut I) -> Result<O2, E> {
|
||||
(self.parser).parse_next(input).map(|_| self.val.clone())
|
||||
}
|
||||
}
|
||||
|
||||
/// [`Parser`] implementation for [`Parser::default_value`]
|
||||
pub struct DefaultValue<F, I, O, O2, E>
|
||||
where
|
||||
F: Parser<I, O, E>,
|
||||
O2: core::default::Default,
|
||||
{
|
||||
pub(crate) parser: F,
|
||||
pub(crate) marker: core::marker::PhantomData<(O2, I, O, E)>,
|
||||
}
|
||||
|
||||
impl<F, I, O, O2, E> Parser<I, O2, E> for DefaultValue<F, I, O, O2, E>
|
||||
where
|
||||
F: Parser<I, O, E>,
|
||||
O2: core::default::Default,
|
||||
{
|
||||
#[inline]
|
||||
fn parse_next(&mut self, input: &mut I) -> Result<O2, E> {
|
||||
(self.parser).parse_next(input).map(|_| O2::default())
|
||||
}
|
||||
}
|
||||
|
||||
/// [`Parser`] implementation for [`Parser::void`]
|
||||
pub struct Void<F, I, O, E>
|
||||
where
|
||||
F: Parser<I, O, E>,
|
||||
{
|
||||
pub(crate) parser: F,
|
||||
pub(crate) marker: core::marker::PhantomData<(I, O, E)>,
|
||||
}
|
||||
|
||||
impl<F, I, O, E> Parser<I, (), E> for Void<F, I, O, E>
|
||||
where
|
||||
F: Parser<I, O, E>,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn parse_next(&mut self, input: &mut I) -> Result<(), E> {
|
||||
(self.parser).parse_next(input).map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
/// [`Parser`] implementation for [`Parser::take`]
|
||||
pub struct Take<F, I, O, E>
|
||||
where
|
||||
F: Parser<I, O, E>,
|
||||
I: Stream,
|
||||
{
|
||||
pub(crate) parser: F,
|
||||
pub(crate) marker: core::marker::PhantomData<(I, O, E)>,
|
||||
}
|
||||
|
||||
impl<I, O, E, F> Parser<I, <I as Stream>::Slice, E> for Take<F, I, O, E>
|
||||
where
|
||||
F: Parser<I, O, E>,
|
||||
I: Stream,
|
||||
{
|
||||
#[inline]
|
||||
fn parse_next(&mut self, input: &mut I) -> Result<<I as Stream>::Slice, E> {
|
||||
let checkpoint = input.checkpoint();
|
||||
match (self.parser).parse_next(input) {
|
||||
Ok(_) => {
|
||||
let offset = input.offset_from(&checkpoint);
|
||||
input.reset(&checkpoint);
|
||||
let taken = input.next_slice(offset);
|
||||
Ok(taken)
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// [`Parser`] implementation for [`Parser::with_taken`]
|
||||
pub struct WithTaken<F, I, O, E>
|
||||
where
|
||||
F: Parser<I, O, E>,
|
||||
I: Stream,
|
||||
{
|
||||
pub(crate) parser: F,
|
||||
pub(crate) marker: core::marker::PhantomData<(I, O, E)>,
|
||||
}
|
||||
|
||||
impl<F, I, O, E> Parser<I, (O, <I as Stream>::Slice), E> for WithTaken<F, I, O, E>
|
||||
where
|
||||
F: Parser<I, O, E>,
|
||||
I: Stream,
|
||||
{
|
||||
#[inline]
|
||||
fn parse_next(&mut self, input: &mut I) -> Result<(O, <I as Stream>::Slice), E> {
|
||||
let checkpoint = input.checkpoint();
|
||||
match (self.parser).parse_next(input) {
|
||||
Ok(result) => {
|
||||
let offset = input.offset_from(&checkpoint);
|
||||
input.reset(&checkpoint);
|
||||
let taken = input.next_slice(offset);
|
||||
Ok((result, taken))
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// [`Parser`] implementation for [`Parser::span`]
|
||||
pub struct Span<F, I, O, E>
|
||||
where
|
||||
F: Parser<I, O, E>,
|
||||
I: Stream + Location,
|
||||
{
|
||||
pub(crate) parser: F,
|
||||
pub(crate) marker: core::marker::PhantomData<(I, O, E)>,
|
||||
}
|
||||
|
||||
impl<I, O, E, F> Parser<I, Range<usize>, E> for Span<F, I, O, E>
|
||||
where
|
||||
F: Parser<I, O, E>,
|
||||
I: Stream + Location,
|
||||
{
|
||||
#[inline]
|
||||
fn parse_next(&mut self, input: &mut I) -> Result<Range<usize>, E> {
|
||||
let start = input.current_token_start();
|
||||
self.parser.parse_next(input).map(move |_| {
|
||||
let end = input.previous_token_end();
|
||||
start..end
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// [`Parser`] implementation for [`Parser::with_span`]
|
||||
pub struct WithSpan<F, I, O, E>
|
||||
where
|
||||
F: Parser<I, O, E>,
|
||||
I: Stream + Location,
|
||||
{
|
||||
pub(crate) parser: F,
|
||||
pub(crate) marker: core::marker::PhantomData<(I, O, E)>,
|
||||
}
|
||||
|
||||
impl<F, I, O, E> Parser<I, (O, Range<usize>), E> for WithSpan<F, I, O, E>
|
||||
where
|
||||
F: Parser<I, O, E>,
|
||||
I: Stream + Location,
|
||||
{
|
||||
#[inline]
|
||||
fn parse_next(&mut self, input: &mut I) -> Result<(O, Range<usize>), E> {
|
||||
let start = input.current_token_start();
|
||||
self.parser.parse_next(input).map(move |output| {
|
||||
let end = input.previous_token_end();
|
||||
(output, (start..end))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// [`Parser`] implementation for [`Parser::output_into`]
|
||||
pub struct OutputInto<F, I, O, O2, E>
|
||||
where
|
||||
F: Parser<I, O, E>,
|
||||
O: Into<O2>,
|
||||
{
|
||||
pub(crate) parser: F,
|
||||
pub(crate) marker: core::marker::PhantomData<(I, O, O2, E)>,
|
||||
}
|
||||
|
||||
impl<F, I, O, O2, E> Parser<I, O2, E> for OutputInto<F, I, O, O2, E>
|
||||
where
|
||||
F: Parser<I, O, E>,
|
||||
O: Into<O2>,
|
||||
{
|
||||
#[inline]
|
||||
fn parse_next(&mut self, i: &mut I) -> Result<O2, E> {
|
||||
self.parser.parse_next(i).map(|o| o.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// [`Parser`] implementation for [`Parser::err_into`]
|
||||
pub struct ErrInto<F, I, O, E, E2>
|
||||
where
|
||||
F: Parser<I, O, E>,
|
||||
E: Into<E2>,
|
||||
{
|
||||
pub(crate) parser: F,
|
||||
pub(crate) marker: core::marker::PhantomData<(I, O, E, E2)>,
|
||||
}
|
||||
|
||||
impl<F, I, O, E, E2> Parser<I, O, E2> for ErrInto<F, I, O, E, E2>
|
||||
where
|
||||
F: Parser<I, O, E>,
|
||||
E: Into<E2>,
|
||||
{
|
||||
#[inline]
|
||||
fn parse_next(&mut self, i: &mut I) -> Result<O, E2> {
|
||||
self.parser.parse_next(i).map_err(|err| err.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// [`Parser`] implementation for [`Parser::context`]
|
||||
pub struct Context<F, I, O, E, C>
|
||||
where
|
||||
F: Parser<I, O, E>,
|
||||
I: Stream,
|
||||
E: AddContext<I, C>,
|
||||
E: ParserError<I>,
|
||||
C: Clone + core::fmt::Debug,
|
||||
{
|
||||
pub(crate) parser: F,
|
||||
pub(crate) context: C,
|
||||
pub(crate) marker: core::marker::PhantomData<(I, O, E)>,
|
||||
}
|
||||
|
||||
impl<F, I, O, E, C> Parser<I, O, E> for Context<F, I, O, E, C>
|
||||
where
|
||||
F: Parser<I, O, E>,
|
||||
I: Stream,
|
||||
E: AddContext<I, C>,
|
||||
E: ParserError<I>,
|
||||
C: Clone + core::fmt::Debug,
|
||||
{
|
||||
#[inline]
|
||||
fn parse_next(&mut self, i: &mut I) -> Result<O, E> {
|
||||
let context = self.context.clone();
|
||||
trace(DisplayDebug(self.context.clone()), move |i: &mut I| {
|
||||
let start = i.checkpoint();
|
||||
(self.parser)
|
||||
.parse_next(i)
|
||||
.map_err(|err| err.add_context(i, &start, context.clone()))
|
||||
})
|
||||
.parse_next(i)
|
||||
}
|
||||
}
|
||||
|
||||
/// [`Parser`] implementation for [`Parser::context`]
|
||||
pub struct ContextWith<P, I, O, E, F, C, FI>
|
||||
where
|
||||
P: Parser<I, O, E>,
|
||||
I: Stream,
|
||||
E: AddContext<I, C>,
|
||||
E: ParserError<I>,
|
||||
F: Fn() -> FI + Clone,
|
||||
C: core::fmt::Debug,
|
||||
FI: Iterator<Item = C>,
|
||||
{
|
||||
pub(crate) parser: P,
|
||||
pub(crate) context: F,
|
||||
pub(crate) marker: core::marker::PhantomData<(I, O, E, C, FI)>,
|
||||
}
|
||||
|
||||
impl<P, I, O, E, F, C, FI> Parser<I, O, E> for ContextWith<P, I, O, E, F, C, FI>
|
||||
where
|
||||
P: Parser<I, O, E>,
|
||||
I: Stream,
|
||||
E: AddContext<I, C>,
|
||||
E: ParserError<I>,
|
||||
F: Fn() -> FI + Clone,
|
||||
C: core::fmt::Debug,
|
||||
FI: Iterator<Item = C>,
|
||||
{
|
||||
#[inline]
|
||||
fn parse_next(&mut self, i: &mut I) -> Result<O, E> {
|
||||
let context = self.context.clone();
|
||||
let start = i.checkpoint();
|
||||
(self.parser).parse_next(i).map_err(|mut err| {
|
||||
for context in context() {
|
||||
err = err.add_context(i, &start, context);
|
||||
}
|
||||
err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// [`Parser`] implementation for [`Parser::map_err`]
|
||||
pub struct MapErr<F, G, I, O, E, E2>
|
||||
where
|
||||
F: Parser<I, O, E>,
|
||||
G: FnMut(E) -> E2,
|
||||
{
|
||||
pub(crate) parser: F,
|
||||
pub(crate) map: G,
|
||||
pub(crate) marker: core::marker::PhantomData<(I, O, E, E2)>,
|
||||
}
|
||||
|
||||
impl<F, G, I, O, E, E2> Parser<I, O, E2> for MapErr<F, G, I, O, E, E2>
|
||||
where
|
||||
F: Parser<I, O, E>,
|
||||
G: FnMut(E) -> E2,
|
||||
{
|
||||
#[inline]
|
||||
fn parse_next(&mut self, i: &mut I) -> Result<O, E2> {
|
||||
match self.parser.parse_next(i) {
|
||||
Err(e) => Err((self.map)(e)),
|
||||
Ok(o) => Ok(o),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// [`Parser`] implementation for [`Parser::retry_after`]
|
||||
#[cfg(feature = "unstable-recover")]
|
||||
#[cfg(feature = "std")]
|
||||
pub struct RetryAfter<P, R, I, O, E>
|
||||
where
|
||||
P: Parser<I, O, E>,
|
||||
R: Parser<I, (), E>,
|
||||
I: Stream,
|
||||
I: Recover<E>,
|
||||
E: ParserError<I> + FromRecoverableError<I, E>,
|
||||
{
|
||||
pub(crate) parser: P,
|
||||
pub(crate) recover: R,
|
||||
pub(crate) marker: core::marker::PhantomData<(I, O, E)>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable-recover")]
|
||||
#[cfg(feature = "std")]
|
||||
impl<P, R, I, O, E> Parser<I, O, E> for RetryAfter<P, R, I, O, E>
|
||||
where
|
||||
P: Parser<I, O, E>,
|
||||
R: Parser<I, (), E>,
|
||||
I: Stream,
|
||||
I: Recover<E>,
|
||||
E: ParserError<I> + FromRecoverableError<I, E>,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn parse_next(&mut self, i: &mut I) -> Result<O, E> {
|
||||
if I::is_recovery_supported() {
|
||||
retry_after_inner(&mut self.parser, &mut self.recover, i)
|
||||
} else {
|
||||
self.parser.parse_next(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable-recover")]
|
||||
#[cfg(feature = "std")]
|
||||
fn retry_after_inner<P, R, I, O, E>(parser: &mut P, recover: &mut R, i: &mut I) -> Result<O, E>
|
||||
where
|
||||
P: Parser<I, O, E>,
|
||||
R: Parser<I, (), E>,
|
||||
I: Stream,
|
||||
I: Recover<E>,
|
||||
E: ParserError<I> + FromRecoverableError<I, E>,
|
||||
{
|
||||
loop {
|
||||
let token_start = i.checkpoint();
|
||||
let mut err = match parser.parse_next(i) {
|
||||
Ok(o) => {
|
||||
return Ok(o);
|
||||
}
|
||||
Err(e) if e.is_incomplete() => return Err(e),
|
||||
Err(err) => err,
|
||||
};
|
||||
let err_start = i.checkpoint();
|
||||
let err_start_eof_offset = i.eof_offset();
|
||||
if recover.parse_next(i).is_ok() {
|
||||
let i_eof_offset = i.eof_offset();
|
||||
if err_start_eof_offset == i_eof_offset {
|
||||
// Didn't advance so bubble the error up
|
||||
} else if let Err(err_) = i.record_err(&token_start, &err_start, err) {
|
||||
err = err_;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
i.reset(&err_start);
|
||||
err = E::from_recoverable_error(&token_start, &err_start, i, err);
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
|
||||
/// [`Parser`] implementation for [`Parser::resume_after`]
|
||||
#[cfg(feature = "unstable-recover")]
|
||||
#[cfg(feature = "std")]
|
||||
pub struct ResumeAfter<P, R, I, O, E>
|
||||
where
|
||||
P: Parser<I, O, E>,
|
||||
R: Parser<I, (), E>,
|
||||
I: Stream,
|
||||
I: Recover<E>,
|
||||
E: ParserError<I> + FromRecoverableError<I, E>,
|
||||
{
|
||||
pub(crate) parser: P,
|
||||
pub(crate) recover: R,
|
||||
pub(crate) marker: core::marker::PhantomData<(I, O, E)>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable-recover")]
|
||||
#[cfg(feature = "std")]
|
||||
impl<P, R, I, O, E> Parser<I, Option<O>, E> for ResumeAfter<P, R, I, O, E>
|
||||
where
|
||||
P: Parser<I, O, E>,
|
||||
R: Parser<I, (), E>,
|
||||
I: Stream,
|
||||
I: Recover<E>,
|
||||
E: ParserError<I> + FromRecoverableError<I, E>,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn parse_next(&mut self, i: &mut I) -> Result<Option<O>, E> {
|
||||
if I::is_recovery_supported() {
|
||||
resume_after_inner(&mut self.parser, &mut self.recover, i)
|
||||
} else {
|
||||
self.parser.parse_next(i).map(Some)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable-recover")]
|
||||
#[cfg(feature = "std")]
|
||||
fn resume_after_inner<P, R, I, O, E>(
|
||||
parser: &mut P,
|
||||
recover: &mut R,
|
||||
i: &mut I,
|
||||
) -> Result<Option<O>, E>
|
||||
where
|
||||
P: Parser<I, O, E>,
|
||||
R: Parser<I, (), E>,
|
||||
I: Stream,
|
||||
I: Recover<E>,
|
||||
E: ParserError<I> + FromRecoverableError<I, E>,
|
||||
{
|
||||
let token_start = i.checkpoint();
|
||||
let mut err = match parser.parse_next(i) {
|
||||
Ok(o) => {
|
||||
return Ok(Some(o));
|
||||
}
|
||||
Err(e) if e.is_incomplete() => return Err(e),
|
||||
Err(err) => err,
|
||||
};
|
||||
let err_start = i.checkpoint();
|
||||
if recover.parse_next(i).is_ok() {
|
||||
if let Err(err_) = i.record_err(&token_start, &err_start, err) {
|
||||
err = err_;
|
||||
} else {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
i.reset(&err_start);
|
||||
err = FromRecoverableError::from_recoverable_error(&token_start, &err_start, i, err);
|
||||
Err(err)
|
||||
}
|
||||
Vendored
+189
@@ -0,0 +1,189 @@
|
||||
//! # List of parsers and combinators
|
||||
//!
|
||||
//! <div class="warning">
|
||||
//!
|
||||
//! **Note**: this list is meant to provide a nicer way to find a parser than reading through the documentation on docs.rs. Function combinators are organized in module so they are a bit easier to find.
|
||||
//!
|
||||
//! </div>
|
||||
//!
|
||||
//! ## Basic elements
|
||||
//!
|
||||
//! Those are used to take a series of tokens for the lowest level elements of your grammar, like, "here is a dot", or "here is an big endian integer".
|
||||
//!
|
||||
//! | combinator | usage | input | new input | output | comment |
|
||||
//! |---|---|---|---|---|---|
|
||||
//! | [`one_of`][crate::token::one_of] | `one_of(['a', 'b', 'c'])` | `"abc"` | `"bc"` | `Ok('a')` |Matches one of the provided [set of tokens][crate::stream::ContainsToken] (works with non ASCII characters too)|
|
||||
//! | [`none_of`][crate::token::none_of] | `none_of(['a', 'b', 'c'])` | `"xyab"` | `"yab"` | `Ok('x')` |Matches anything but one of the provided [set of tokens][crate::stream::ContainsToken]|
|
||||
//! | [`literal`][crate::token::literal] | `"hello"` | `"hello world"` | `" world"` | `Ok("hello")` |Recognizes a specific suite of characters or bytes (see also [`Caseless`][crate::ascii::Caseless])|
|
||||
//! | [`take`][crate::token::take] | `take(4)` | `"hello"` | `"o"` | `Ok("hell")` |Takes a specific number of bytes or characters|
|
||||
//! | [`take_while`][crate::token::take_while] | `take_while(0.., is_alphabetic)` | `"abc123"` | `"123"` | `Ok("abc")` |Returns the longest slice of bytes or characters for which the provided [set of tokens][crate::stream::ContainsToken] matches.|
|
||||
//! | [`take_till`][crate::token::take_till] | `take_till(0.., is_alphabetic)` | `"123abc"` | `"abc"` | `Ok("123")` |Returns a slice of bytes or characters until the provided [set of tokens][crate::stream::ContainsToken] matches. This is the reverse behaviour from `take_while`: `take_till(f)` is equivalent to `take_while(0.., \|c\| !f(c))`|
|
||||
//! | [`take_until`][crate::token::take_until] | `take_until(0.., "world")` | `"Hello world"` | `"world"` | `Ok("Hello ")` |Returns a slice of bytes or characters until the provided [literal][crate::token::literal] is found.|
|
||||
//!
|
||||
//! ## Choice combinators
|
||||
//!
|
||||
//! | combinator | usage | input | new input | output | comment |
|
||||
//! |---|---|---|---|---|---|
|
||||
//! | [`alt`] | `alt(("ab", "cd"))` | `"cdef"` | `"ef"` | `Ok("cd")` |Try a list of parsers and return the result of the first successful one|
|
||||
//! | [`dispatch`] | \- | \- | \- | \- | `match` for parsers |
|
||||
//!
|
||||
//! ## Sequence combinators
|
||||
//!
|
||||
//! | combinator | usage | input | new input | output | comment |
|
||||
//! |---|---|---|---|---|---|
|
||||
//! | [`(...)` (tuples)][crate::Parser] | `("ab", "XY", take(1))` | `"abXYZ!"` | `"!"` | `Ok(("ab", "XY", "Z"))` |Parse a series of values|
|
||||
//! | [`seq!`] | `seq!(_: '(', take(2), _: ')')` | `"(ab)cd"` | `"cd"` | `Ok("ab")` |Parse a series of values, discarding those you specify|
|
||||
//! | [`delimited`] | `delimited('(', take(2), ')')` | `"(ab)cd"` | `"cd"` | `Ok("ab")` |Parse three values, discarding the first and third value|
|
||||
//! | [`preceded`] | `preceded("ab", "XY")` | `"abXYZ"` | `"Z"` | `Ok("XY")` |Parse two values, discarding the first value|
|
||||
//! | [`terminated`] | `terminated("ab", "XY")` | `"abXYZ"` | `"Z"` | `Ok("ab")` |Parse two values, discarding the second value|
|
||||
//! | [`separated_pair`] | `separated_pair("hello", ',', "world")` | `"hello,world!"` | `"!"` | `Ok(("hello", "world"))` | Parse three values, discarding the middle value|
|
||||
//!
|
||||
//! ## Applying a parser multiple times
|
||||
//!
|
||||
//! | combinator | usage | input | new input | output | comment |
|
||||
//! |---|---|---|---|---|---|
|
||||
//! | [`repeat`] | `repeat(1..=3, "ab")` | `"ababc"` | `"c"` | `Ok(vec!["ab", "ab"])` |Applies the parser between m and n times (n included) and returns the list of results in a Vec|
|
||||
//! | [`repeat_till`] | `repeat_till(0.., "ab", "ef")` | `"ababefg"` | `"g"` | `Ok((vec!["ab", "ab"], "ef"))` |Applies the first parser until the second applies. Returns a tuple containing the list of results from the first in a Vec and the result of the second|
|
||||
//! | [`separated`] | `separated(1..=3, "ab", ",")` | `"ab,ab,ab."` | `"."` | `Ok(vec!["ab", "ab", "ab"])` |Applies the parser and separator between m and n times (n included) and returns the list of results in a Vec|
|
||||
//! | [`Repeat::fold`] | <code>repeat(1..=2, `be_u8`).fold(\|\| 0, \|acc, item\| acc + item)</code> | `[1, 2, 3]` | `[3]` | `Ok(3)` |Applies the parser between m and n times (n included) and folds the list of return value|
|
||||
//!
|
||||
//! ## Partial related
|
||||
//!
|
||||
//! - [`eof`]: Returns its input if it is at the end of input data
|
||||
//! - [`Parser::complete_err`]: Replaces an `Incomplete` returned by the child parser with an `Backtrack`
|
||||
//!
|
||||
//! ## Modifiers
|
||||
//!
|
||||
//! - [`cond`]: Conditional combinator. Wraps another parser and calls it if the condition is met
|
||||
//! - [`Parser::flat_map`]: method to map a new parser from the output of the first parser, then apply that parser over the rest of the input
|
||||
//! - [`Parser::value`]: method to replace the result of a parser
|
||||
//! - [`Parser::default_value`]: method to replace the result of a parser
|
||||
//! - [`Parser::void`]: method to discard the result of a parser
|
||||
//! - [`Parser::map`]: method to map a function on the result of a parser
|
||||
//! - [`Parser::and_then`]: Applies a second parser over the output of the first one
|
||||
//! - [`Parser::verify_map`]: Maps a function returning an `Option` on the output of a parser
|
||||
//! - [`Parser::try_map`]: Maps a function returning a `Result` on the output of a parser
|
||||
//! - [`Parser::parse_to`]: Apply [`std::str::FromStr`] to the output of the parser
|
||||
//! - [`not`]: Returns a result only if the embedded parser returns `Backtrack` or `Incomplete`. Does not consume the input
|
||||
//! - [`opt`]: Make the underlying parser optional
|
||||
//! - [`peek`]: Returns a result without consuming the input
|
||||
//! - [`Parser::take`]: If the child parser was successful, return the consumed input as the produced value
|
||||
//! - [`Parser::with_taken`]: If the child parser was successful, return a tuple of the consumed input and the produced output.
|
||||
//! - [`Parser::span`]: If the child parser was successful, return the location of the consumed input as the produced value
|
||||
//! - [`Parser::with_span`]: If the child parser was successful, return a tuple of the location of the consumed input and the produced output.
|
||||
//! - [`Parser::verify`]: Returns the result of the child parser if it satisfies a verification function
|
||||
//!
|
||||
//! ## Error management and debugging
|
||||
//!
|
||||
//! - [`cut_err`]: Commit the parse result, disallowing alternative parsers from being attempted
|
||||
//! - [`backtrack_err`]: Attempts a parse, allowing alternative parsers to be attempted despite
|
||||
//! use of `cut_err`
|
||||
//! - [`Parser::context`]: Add context to the error if the parser fails
|
||||
//! - [`trace`]: Print the parse state with the `debug` feature flag
|
||||
//! - [`todo()`]: Placeholder parser
|
||||
//!
|
||||
//! ## Remaining combinators
|
||||
//!
|
||||
//! - [`empty`]: Succeed, consuming no input
|
||||
//! - [`fail`]: Inversion of [`empty`]. Always fails.
|
||||
//! - [`Parser::by_ref`]: Allow moving `&mut impl Parser` into other parsers
|
||||
//!
|
||||
//! ## Text parsing
|
||||
//!
|
||||
//! - [`any`][crate::token::any]: Matches one token
|
||||
//! - [`tab`][crate::ascii::tab]: Matches a tab character `\t`
|
||||
//! - [`crlf`][crate::ascii::crlf]: Recognizes the string `\r\n`
|
||||
//! - [`line_ending`][crate::ascii::line_ending]: Recognizes an end of line (both `\n` and `\r\n`)
|
||||
//! - [`newline`][crate::ascii::newline]: Matches a newline character `\n`
|
||||
//! - [`till_line_ending`][crate::ascii::till_line_ending]: Recognizes a string of any char except `\r` or `\n`
|
||||
//! - [`rest`][crate::token::rest]: Return the remaining input
|
||||
//!
|
||||
//! - [`alpha0`][crate::ascii::alpha0]: Recognizes zero or more lowercase and uppercase alphabetic characters: `[a-zA-Z]`. [`alpha1`][crate::ascii::alpha1] does the same but returns at least one character
|
||||
//! - [`alphanumeric0`][crate::ascii::alphanumeric0]: Recognizes zero or more numerical and alphabetic characters: `[0-9a-zA-Z]`. [`alphanumeric1`][crate::ascii::alphanumeric1] does the same but returns at least one character
|
||||
//! - [`space0`][crate::ascii::space0]: Recognizes zero or more spaces and tabs. [`space1`][crate::ascii::space1] does the same but returns at least one character
|
||||
//! - [`multispace0`][crate::ascii::multispace0]: Recognizes zero or more spaces, tabs, carriage returns and line feeds. [`multispace1`][crate::ascii::multispace1] does the same but returns at least one character
|
||||
//! - [`digit0`][crate::ascii::digit0]: Recognizes zero or more numerical characters: `[0-9]`. [`digit1`][crate::ascii::digit1] does the same but returns at least one character
|
||||
//! - [`hex_digit0`][crate::ascii::hex_digit0]: Recognizes zero or more hexadecimal numerical characters: `[0-9A-Fa-f]`. [`hex_digit1`][crate::ascii::hex_digit1] does the same but returns at least one character
|
||||
//! - [`oct_digit0`][crate::ascii::oct_digit0]: Recognizes zero or more octal characters: `[0-7]`. [`oct_digit1`][crate::ascii::oct_digit1] does the same but returns at least one character
|
||||
//!
|
||||
//! - [`float`][crate::ascii::float]: Parse a floating point number in a byte string
|
||||
//! - [`dec_int`][crate::ascii::dec_int]: Decode a variable-width, decimal signed integer
|
||||
//! - [`dec_uint`][crate::ascii::dec_uint]: Decode a variable-width, decimal unsigned integer
|
||||
//! - [`hex_uint`][crate::ascii::hex_uint]: Decode a variable-width, hexadecimal integer
|
||||
//!
|
||||
//! - [`take_escaped`][crate::ascii::take_escaped]: Recognize the input slice with escaped characters
|
||||
//! - [`escaped`][crate::ascii::escaped]: Parse escaped characters, unescaping them
|
||||
//!
|
||||
//! - [`expression()`]: Parse an operator precedence expression with Pratt parsing
|
||||
//!
|
||||
//! ### Character test functions
|
||||
//!
|
||||
//! Use these functions with a combinator like `take_while`:
|
||||
//!
|
||||
//! - [`AsChar::is_alpha`][crate::stream::AsChar::is_alpha]: Tests if byte is ASCII alphabetic: `[A-Za-z]`
|
||||
//! - [`AsChar::is_alphanum`][crate::stream::AsChar::is_alphanum]: Tests if byte is ASCII alphanumeric: `[A-Za-z0-9]`
|
||||
//! - [`AsChar::is_dec_digit`][crate::stream::AsChar::is_dec_digit]: Tests if byte is ASCII digit: `[0-9]`
|
||||
//! - [`AsChar::is_hex_digit`][crate::stream::AsChar::is_hex_digit]: Tests if byte is ASCII hex digit: `[0-9A-Fa-f]`
|
||||
//! - [`AsChar::is_oct_digit`][crate::stream::AsChar::is_oct_digit]: Tests if byte is ASCII octal digit: `[0-7]`
|
||||
//! - [`AsChar::is_space`][crate::stream::AsChar::is_space]: Tests if byte is ASCII space or tab: `[ \t]`
|
||||
//! - [`AsChar::is_newline`][crate::stream::AsChar::is_newline]: Tests if byte is ASCII newline: `[\n]`
|
||||
//!
|
||||
//! ## Binary format parsing
|
||||
//!
|
||||
//! - [`length_repeat`][crate::binary::length_repeat] Gets a number from the first parser, then applies the second parser that many times
|
||||
//! - [`length_take`][crate::binary::length_take]: Gets a number from the first parser, then takes a subslice of the input of that size, and returns that subslice
|
||||
//! - [`length_and_then`][crate::binary::length_and_then]: Gets a number from the first parser, takes a subslice of the input of that size, then applies the second parser on that subslice. If the second parser returns `Incomplete`, `length_value` will return an error
|
||||
//!
|
||||
//! ### Integers
|
||||
//!
|
||||
//! Parsing integers from binary formats can be done in two ways: With parser functions, or combinators with configurable endianness.
|
||||
//!
|
||||
//! - **configurable endianness:** [`i16`][crate::binary::i16], [`i32`][crate::binary::i32],
|
||||
//! [`i64`][crate::binary::i64], [`u16`][crate::binary::u16], [`u32`][crate::binary::u32],
|
||||
//! [`u64`][crate::binary::u64] are combinators that take as argument a
|
||||
//! [`winnow::binary::Endianness`][crate::binary::Endianness], like this: `i16(endianness)`. If the
|
||||
//! parameter is `winnow::binary::Endianness::Big`, parse a big endian `i16` integer, otherwise a
|
||||
//! little endian `i16` integer.
|
||||
//! - **fixed endianness**: The functions are prefixed by `be_` for big endian numbers, and by `le_` for little endian numbers, and the suffix is the type they parse to. As an example, `be_u32` parses a big endian unsigned integer stored in 32 bits.
|
||||
//! - [`be_f32`][crate::binary::be_f32], [`be_f64`][crate::binary::be_f64]: Big endian floating point numbers
|
||||
//! - [`le_f32`][crate::binary::le_f32], [`le_f64`][crate::binary::le_f64]: Little endian floating point numbers
|
||||
//! - [`be_i8`][crate::binary::be_i8], [`be_i16`][crate::binary::be_i16], [`be_i24`][crate::binary::be_i24], [`be_i32`][crate::binary::be_i32], [`be_i64`][crate::binary::be_i64], [`be_i128`][crate::binary::be_i128]: Big endian signed integers
|
||||
//! - [`be_u8`][crate::binary::be_u8], [`be_u16`][crate::binary::be_u16], [`be_u24`][crate::binary::be_u24], [`be_u32`][crate::binary::be_u32], [`be_u64`][crate::binary::be_u64], [`be_u128`][crate::binary::be_u128]: Big endian unsigned integers
|
||||
//! - [`le_i8`][crate::binary::le_i8], [`le_i16`][crate::binary::le_i16], [`le_i24`][crate::binary::le_i24], [`le_i32`][crate::binary::le_i32], [`le_i64`][crate::binary::le_i64], [`le_i128`][crate::binary::le_i128]: Little endian signed integers
|
||||
//! - [`le_u8`][crate::binary::le_u8], [`le_u16`][crate::binary::le_u16], [`le_u24`][crate::binary::le_u24], [`le_u32`][crate::binary::le_u32], [`le_u64`][crate::binary::le_u64], [`le_u128`][crate::binary::le_u128]: Little endian unsigned integers
|
||||
//!
|
||||
//! ### Bit stream parsing
|
||||
//!
|
||||
//! - [`bits`][crate::binary::bits::bits]: Transforms the current input type (byte slice `&[u8]`) to a bit stream on which bit specific parsers and more general combinators can be applied
|
||||
//! - [`bytes`][crate::binary::bits::bytes]: Transforms its bits stream input back into a byte slice for the underlying parser
|
||||
//! - [`take`][crate::binary::bits::take]: Take a set number of bits
|
||||
//! - [`pattern`][crate::binary::bits::pattern]: Check if a set number of bits matches a pattern
|
||||
//! - [`bool`][crate::binary::bits::bool]: Match any one bit
|
||||
|
||||
mod branch;
|
||||
mod core;
|
||||
mod debug;
|
||||
mod expression;
|
||||
mod multi;
|
||||
mod sequence;
|
||||
|
||||
#[cfg(all(test, feature = "ascii", feature = "binary"))]
|
||||
mod tests;
|
||||
|
||||
pub mod impls;
|
||||
|
||||
pub use self::branch::{alt, dispatch, Alt};
|
||||
pub use self::core::{backtrack_err, cond, cut_err, empty, eof, fail, not, opt, peek, todo};
|
||||
pub use self::debug::trace;
|
||||
pub use self::expression::{expression, Expression, Infix, Postfix, Prefix};
|
||||
#[cfg(feature = "alloc")]
|
||||
pub use self::multi::separated_foldr1;
|
||||
pub use self::multi::{
|
||||
fill, iterator, repeat, repeat_till, separated, separated_foldl1, ParserIterator, Repeat,
|
||||
};
|
||||
pub use self::sequence::{delimited, preceded, separated_pair, seq, terminated, unordered_seq};
|
||||
|
||||
pub(crate) use self::debug::{trace_result, DisplayDebug};
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use crate::Parser;
|
||||
+1644
File diff suppressed because it is too large.
Load diff
+176
@@ -0,0 +1,176 @@
|
||||
use crate::combinator::trace;
|
||||
use crate::error::ParserError;
|
||||
use crate::stream::Stream;
|
||||
use crate::Parser;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use crate::seq;
|
||||
#[doc(inline)]
|
||||
pub use crate::unordered_seq;
|
||||
|
||||
/// Sequence two parsers, only returning the output from the second.
|
||||
///
|
||||
/// See also [`seq`] to generalize this across any number of fields.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use winnow::{error::ErrMode, error::Needed};
|
||||
/// # use winnow::prelude::*;
|
||||
/// # use winnow::error::Needed::Size;
|
||||
/// use winnow::combinator::preceded;
|
||||
///
|
||||
/// fn parser<'i>(input: &mut &'i str) -> ModalResult<&'i str> {
|
||||
/// preceded("abc", "efg").parse_next(input)
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(parser.parse_peek("abcefg"), Ok(("", "efg")));
|
||||
/// assert_eq!(parser.parse_peek("abcefghij"), Ok(("hij", "efg")));
|
||||
/// assert!(parser.parse_peek("").is_err());
|
||||
/// assert!(parser.parse_peek("123").is_err());
|
||||
/// ```
|
||||
#[doc(alias = "ignore_then")]
|
||||
pub fn preceded<Input, Ignored, Output, Error, IgnoredParser, ParseNext>(
|
||||
mut ignored: IgnoredParser,
|
||||
mut parser: ParseNext,
|
||||
) -> impl Parser<Input, Output, Error>
|
||||
where
|
||||
Input: Stream,
|
||||
Error: ParserError<Input>,
|
||||
IgnoredParser: Parser<Input, Ignored, Error>,
|
||||
ParseNext: Parser<Input, Output, Error>,
|
||||
{
|
||||
trace("preceded", move |input: &mut Input| {
|
||||
let _ = ignored.parse_next(input)?;
|
||||
parser.parse_next(input)
|
||||
})
|
||||
}
|
||||
|
||||
/// Sequence two parsers, only returning the output of the first.
|
||||
///
|
||||
/// See also [`seq`] to generalize this across any number of fields.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use winnow::{error::ErrMode, error::Needed};
|
||||
/// # use winnow::prelude::*;
|
||||
/// # use winnow::error::Needed::Size;
|
||||
/// use winnow::combinator::terminated;
|
||||
///
|
||||
/// fn parser<'i>(input: &mut &'i str) -> ModalResult<&'i str> {
|
||||
/// terminated("abc", "efg").parse_next(input)
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(parser.parse_peek("abcefg"), Ok(("", "abc")));
|
||||
/// assert_eq!(parser.parse_peek("abcefghij"), Ok(("hij", "abc")));
|
||||
/// assert!(parser.parse_peek("").is_err());
|
||||
/// assert!(parser.parse_peek("123").is_err());
|
||||
/// ```
|
||||
#[doc(alias = "then_ignore")]
|
||||
pub fn terminated<Input, Output, Ignored, Error, ParseNext, IgnoredParser>(
|
||||
mut parser: ParseNext,
|
||||
mut ignored: IgnoredParser,
|
||||
) -> impl Parser<Input, Output, Error>
|
||||
where
|
||||
Input: Stream,
|
||||
Error: ParserError<Input>,
|
||||
ParseNext: Parser<Input, Output, Error>,
|
||||
IgnoredParser: Parser<Input, Ignored, Error>,
|
||||
{
|
||||
trace("terminated", move |input: &mut Input| {
|
||||
let o = parser.parse_next(input)?;
|
||||
ignored.parse_next(input).map(|_| o)
|
||||
})
|
||||
}
|
||||
|
||||
/// Sequence three parsers, only returning the values of the first and third.
|
||||
///
|
||||
/// See also [`seq`] to generalize this across any number of fields.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use winnow::{error::ErrMode, error::Needed};
|
||||
/// # use winnow::error::Needed::Size;
|
||||
/// # use winnow::prelude::*;
|
||||
/// use winnow::combinator::separated_pair;
|
||||
///
|
||||
/// fn parser<'i>(input: &mut &'i str) -> ModalResult<(&'i str, &'i str)> {
|
||||
/// separated_pair("abc", "|", "efg").parse_next(input)
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(parser.parse_peek("abc|efg"), Ok(("", ("abc", "efg"))));
|
||||
/// assert_eq!(parser.parse_peek("abc|efghij"), Ok(("hij", ("abc", "efg"))));
|
||||
/// assert!(parser.parse_peek("").is_err());
|
||||
/// assert!(parser.parse_peek("123").is_err());
|
||||
/// ```
|
||||
pub fn separated_pair<Input, O1, Sep, O2, Error, P1, SepParser, P2>(
|
||||
mut first: P1,
|
||||
mut sep: SepParser,
|
||||
mut second: P2,
|
||||
) -> impl Parser<Input, (O1, O2), Error>
|
||||
where
|
||||
Input: Stream,
|
||||
Error: ParserError<Input>,
|
||||
P1: Parser<Input, O1, Error>,
|
||||
SepParser: Parser<Input, Sep, Error>,
|
||||
P2: Parser<Input, O2, Error>,
|
||||
{
|
||||
trace("separated_pair", move |input: &mut Input| {
|
||||
let o1 = first.parse_next(input)?;
|
||||
let _ = sep.parse_next(input)?;
|
||||
second.parse_next(input).map(|o2| (o1, o2))
|
||||
})
|
||||
}
|
||||
|
||||
/// Sequence three parsers, only returning the output of the second.
|
||||
///
|
||||
/// See also [`seq`] to generalize this across any number of fields.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use winnow::{error::ErrMode, error::Needed};
|
||||
/// # use winnow::error::Needed::Size;
|
||||
/// # use winnow::prelude::*;
|
||||
/// use winnow::combinator::delimited;
|
||||
///
|
||||
/// fn parser<'i>(input: &mut &'i str) -> ModalResult<&'i str> {
|
||||
/// delimited("(", "abc", ")").parse_next(input)
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(parser.parse_peek("(abc)"), Ok(("", "abc")));
|
||||
/// assert_eq!(parser.parse_peek("(abc)def"), Ok(("def", "abc")));
|
||||
/// assert!(parser.parse_peek("").is_err());
|
||||
/// assert!(parser.parse_peek("123").is_err());
|
||||
/// ```
|
||||
#[doc(alias = "between")]
|
||||
#[doc(alias = "padded")]
|
||||
pub fn delimited<
|
||||
Input,
|
||||
Ignored1,
|
||||
Output,
|
||||
Ignored2,
|
||||
Error,
|
||||
IgnoredParser1,
|
||||
ParseNext,
|
||||
IgnoredParser2,
|
||||
>(
|
||||
mut ignored1: IgnoredParser1,
|
||||
mut parser: ParseNext,
|
||||
mut ignored2: IgnoredParser2,
|
||||
) -> impl Parser<Input, Output, Error>
|
||||
where
|
||||
Input: Stream,
|
||||
Error: ParserError<Input>,
|
||||
IgnoredParser1: Parser<Input, Ignored1, Error>,
|
||||
ParseNext: Parser<Input, Output, Error>,
|
||||
IgnoredParser2: Parser<Input, Ignored2, Error>,
|
||||
{
|
||||
trace("delimited", move |input: &mut Input| {
|
||||
let _ = ignored1.parse_next(input)?;
|
||||
let o2 = parser.parse_next(input)?;
|
||||
ignored2.parse_next(input).map(|_| o2)
|
||||
})
|
||||
}
|
||||
+3854
File diff suppressed because it is too large.
Load diff
Vendored
+1527
File diff suppressed because it is too large.
Load diff
Vendored
+189
@@ -0,0 +1,189 @@
|
||||
//! > winnow, making parsing a breeze
|
||||
//!
|
||||
//! `winnow` is a parser combinator library
|
||||
//!
|
||||
//! Quick links:
|
||||
//! - [List of combinators][crate::combinator]
|
||||
//! - [Tutorial][_tutorial::chapter_0]
|
||||
//! - [Special Topics][_topic]
|
||||
//! - [Discussions](https://github.com/winnow-rs/winnow/discussions)
|
||||
//! - [CHANGELOG](https://github.com/winnow-rs/winnow/blob/v1.0.0/CHANGELOG.md) (includes major version migration
|
||||
//! guides)
|
||||
//!
|
||||
//! ## Aspirations
|
||||
//!
|
||||
//! `winnow` aims to be your "do everything" parser, much like people treat regular expressions.
|
||||
//!
|
||||
//! In roughly priority order:
|
||||
//! 1. Support writing parser declaratively while not getting in the way of imperative-style
|
||||
//! parsing when needed, working as an open-ended toolbox rather than a close-ended framework.
|
||||
//! 2. Flexible enough to be used for any application, including parsing strings, binary data,
|
||||
//! or separate [lexing and parsing phases][_topic::lexing]
|
||||
//! 3. Zero-cost abstractions, making it easy to write high performance parsers
|
||||
//! 4. Easy to use, making it trivial for one-off uses
|
||||
//!
|
||||
//! In addition:
|
||||
//! - Resilient maintainership, including
|
||||
//! - Willing to break compatibility rather than batching up breaking changes in large releases
|
||||
//! - Leverage feature flags to keep one active branch
|
||||
//! - We will support the last 6 months of rust releases (MSRV)
|
||||
//!
|
||||
//! See also [Special Topic: Why winnow?][crate::_topic::why]
|
||||
//!
|
||||
//! ## Example
|
||||
//!
|
||||
//! Run
|
||||
//! ```console
|
||||
//! $ cargo add winnow
|
||||
//! ```
|
||||
//!
|
||||
//! Then use it to parse:
|
||||
//! ```rust
|
||||
//! # #[cfg(all(feature = "alloc", feature = "parser"))] {
|
||||
#![doc = include_str!("../examples/css/parser.rs")]
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! See also the [Tutorial][_tutorial::chapter_0] and [Special Topics][_topic]
|
||||
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![cfg_attr(docsrs, feature(extended_key_value_attributes))]
|
||||
#![cfg_attr(all(not(feature = "std"), not(test)), no_std)]
|
||||
#![warn(missing_docs)]
|
||||
#![warn(clippy::std_instead_of_core)]
|
||||
#![warn(clippy::std_instead_of_alloc)]
|
||||
#![warn(clippy::print_stderr)]
|
||||
#![warn(clippy::print_stdout)]
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
#[cfg_attr(test, macro_use)]
|
||||
#[allow(unused_extern_crates)]
|
||||
extern crate alloc;
|
||||
|
||||
#[doc = include_str!("../README.md")]
|
||||
#[cfg(doctest)]
|
||||
pub struct ReadmeDoctests;
|
||||
|
||||
pub(crate) mod util {
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn from_fn<F: Fn(&mut core::fmt::Formatter<'_>) -> core::fmt::Result>(
|
||||
f: F,
|
||||
) -> FromFn<F> {
|
||||
FromFn(f)
|
||||
}
|
||||
|
||||
pub(crate) struct FromFn<F>(F)
|
||||
where
|
||||
F: Fn(&mut core::fmt::Formatter<'_>) -> core::fmt::Result;
|
||||
|
||||
impl<F> core::fmt::Debug for FromFn<F>
|
||||
where
|
||||
F: Fn(&mut core::fmt::Formatter<'_>) -> core::fmt::Result,
|
||||
{
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
(self.0)(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> core::fmt::Display for FromFn<F>
|
||||
where
|
||||
F: Fn(&mut core::fmt::Formatter<'_>) -> core::fmt::Result,
|
||||
{
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
(self.0)(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
#[macro_use]
|
||||
#[cfg(feature = "parser")]
|
||||
pub mod error;
|
||||
|
||||
#[cfg(feature = "parser")]
|
||||
mod parser;
|
||||
|
||||
pub mod stream;
|
||||
|
||||
#[cfg(feature = "ascii")]
|
||||
pub mod ascii;
|
||||
#[cfg(feature = "binary")]
|
||||
pub mod binary;
|
||||
#[cfg(feature = "parser")]
|
||||
pub mod combinator;
|
||||
#[cfg(feature = "parser")]
|
||||
pub mod token;
|
||||
|
||||
#[cfg(feature = "unstable-doc")]
|
||||
pub mod _topic;
|
||||
#[cfg(feature = "unstable-doc")]
|
||||
pub mod _tutorial;
|
||||
|
||||
/// Core concepts available for glob import
|
||||
///
|
||||
/// Including
|
||||
/// - [`StreamIsPartial`][crate::stream::StreamIsPartial]
|
||||
/// - [`Parser`]
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(feature = "ascii")] {
|
||||
/// use winnow::prelude::*;
|
||||
///
|
||||
/// fn parse_data(input: &mut &str) -> ModalResult<u64> {
|
||||
/// // ...
|
||||
/// # winnow::ascii::dec_uint(input)
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let result = parse_data.parse("100");
|
||||
/// assert_eq!(result, Ok(100));
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
pub mod prelude {
|
||||
#[cfg(feature = "parser")]
|
||||
pub use crate::error::ModalError as _;
|
||||
#[cfg(feature = "parser")]
|
||||
pub use crate::error::ParserError as _;
|
||||
pub use crate::stream::AsChar as _;
|
||||
pub use crate::stream::ContainsToken as _;
|
||||
pub use crate::stream::Stream as _;
|
||||
pub use crate::stream::StreamIsPartial as _;
|
||||
#[cfg(feature = "parser")]
|
||||
pub use crate::ModalParser;
|
||||
#[cfg(feature = "parser")]
|
||||
pub use crate::ModalResult;
|
||||
#[cfg(feature = "parser")]
|
||||
pub use crate::Parser;
|
||||
#[cfg(feature = "unstable-recover")]
|
||||
#[cfg(feature = "std")]
|
||||
#[cfg(feature = "parser")]
|
||||
pub use crate::RecoverableParser as _;
|
||||
|
||||
#[cfg(all(test, feature = "parser"))]
|
||||
pub(crate) use crate::TestResult;
|
||||
}
|
||||
|
||||
#[cfg(feature = "parser")]
|
||||
pub use error::ModalResult;
|
||||
#[cfg(feature = "parser")]
|
||||
pub use error::Result;
|
||||
#[cfg(feature = "unstable-recover")]
|
||||
#[cfg(feature = "std")]
|
||||
#[cfg(feature = "parser")]
|
||||
pub use parser::RecoverableParser;
|
||||
#[cfg(feature = "parser")]
|
||||
pub use parser::{ModalParser, Parser};
|
||||
pub use stream::BStr;
|
||||
pub use stream::Bytes;
|
||||
pub use stream::LocatingSlice;
|
||||
pub use stream::Partial;
|
||||
pub use stream::Stateful;
|
||||
pub use stream::Str;
|
||||
|
||||
#[cfg(all(test, feature = "parser"))]
|
||||
pub(crate) use error::TestResult;
|
||||
Vendored
+96
@@ -0,0 +1,96 @@
|
||||
/// `match` for parsers
|
||||
///
|
||||
/// While `match` works by accepting a value and returning values:
|
||||
/// ```rust,ignore
|
||||
/// let result_value = match scrutinee_value {
|
||||
/// ArmPattern => arm_value,
|
||||
/// };
|
||||
/// ```
|
||||
/// `dispatch!` composes parsers:
|
||||
/// ```rust,ignore
|
||||
/// let result_parser = dispatch!{scrutinee_parser;
|
||||
/// ArmPattern => arm_parser,
|
||||
/// };
|
||||
/// ```
|
||||
///
|
||||
/// This is useful when parsers have unique prefixes to test for.
|
||||
/// This offers better performance over
|
||||
/// [`alt`][crate::combinator::alt] though it might be at the cost of duplicating parts of your grammar
|
||||
/// if you needed to [`peek(input_parser)`][crate::combinator::peek] the scrutinee.
|
||||
///
|
||||
/// For tight control over the error in a catch-all case, use [`fail`][crate::combinator::fail].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(feature = "parser")] {
|
||||
/// use winnow::prelude::*;
|
||||
/// use winnow::combinator::dispatch;
|
||||
/// # use winnow::token::take;
|
||||
/// # use winnow::token::take_while;
|
||||
/// # use winnow::combinator::fail;
|
||||
///
|
||||
/// fn integer(input: &mut &str) -> ModalResult<u64> {
|
||||
/// dispatch! {take(2usize);
|
||||
/// "0b" => take_while(1.., '0'..='1').try_map(|s| u64::from_str_radix(s, 2)),
|
||||
/// "0o" => take_while(1.., '0'..='7').try_map(|s| u64::from_str_radix(s, 8)),
|
||||
/// "0d" => take_while(1.., '0'..='9').try_map(|s| u64::from_str_radix(s, 10)),
|
||||
/// "0x" => take_while(1.., ('0'..='9', 'a'..='f', 'A'..='F')).try_map(|s| u64::from_str_radix(s, 16)),
|
||||
/// _ => fail::<_, u64, _>,
|
||||
/// }
|
||||
/// .parse_next(input)
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(integer.parse_peek("0x100 Hello"), Ok((" Hello", 0x100)));
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(feature = "parser")] {
|
||||
/// use winnow::prelude::*;
|
||||
/// use winnow::combinator::dispatch;
|
||||
/// # use winnow::token::any;
|
||||
/// # use winnow::combinator::preceded;
|
||||
/// # use winnow::combinator::empty;
|
||||
/// # use winnow::combinator::fail;
|
||||
///
|
||||
/// fn escaped(input: &mut &str) -> ModalResult<char> {
|
||||
/// preceded('\\', escape_seq_char).parse_next(input)
|
||||
/// }
|
||||
///
|
||||
/// fn escape_seq_char(input: &mut &str) -> ModalResult<char> {
|
||||
/// dispatch! {any;
|
||||
/// 'b' => empty.value('\u{8}'),
|
||||
/// 'f' => empty.value('\u{c}'),
|
||||
/// 'n' => empty.value('\n'),
|
||||
/// 'r' => empty.value('\r'),
|
||||
/// 't' => empty.value('\t'),
|
||||
/// '\\' => empty.value('\\'),
|
||||
/// '"' => empty.value('"'),
|
||||
/// _ => fail::<_, char, _>,
|
||||
/// }
|
||||
/// .parse_next(input)
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(escaped.parse_peek("\\nHello"), Ok(("Hello", '\n')));
|
||||
/// # }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
#[doc(hidden)] // forced to be visible in intended location
|
||||
macro_rules! dispatch {
|
||||
(
|
||||
$scrutinee_parser:expr;
|
||||
$( $arm_pat:pat $(if $arm_pred:expr)? => $arm_parser: expr ),+ $(,)?
|
||||
) => {
|
||||
$crate::combinator::trace("dispatch", move |i: &mut _|
|
||||
{
|
||||
use $crate::Parser;
|
||||
let initial = $scrutinee_parser.parse_next(i)?;
|
||||
match initial {
|
||||
$(
|
||||
$arm_pat $(if $arm_pred)? => $arm_parser.parse_next(i),
|
||||
)*
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Vendored
+58
@@ -0,0 +1,58 @@
|
||||
mod dispatch;
|
||||
mod seq;
|
||||
mod unordered_seq;
|
||||
|
||||
#[cfg(test)]
|
||||
macro_rules! assert_parse(
|
||||
($left: expr, $right: expr) => {
|
||||
let res: $crate::error::ModalResult<_, $crate::error::InputError<_>> = $left;
|
||||
snapbox::assert_data_eq!(snapbox::data::ToDebug::to_debug(&res), $right);
|
||||
};
|
||||
);
|
||||
|
||||
macro_rules! impl_partial_eq {
|
||||
($lhs:ty, $rhs:ty) => {
|
||||
#[allow(unused_lifetimes)]
|
||||
impl<'a> PartialEq<$rhs> for $lhs {
|
||||
#[inline]
|
||||
fn eq(&self, other: &$rhs) -> bool {
|
||||
let l = self;
|
||||
let r: &Self = other.as_ref();
|
||||
PartialEq::eq(l, r)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_lifetimes)]
|
||||
impl<'a> PartialEq<$lhs> for $rhs {
|
||||
#[inline]
|
||||
fn eq(&self, other: &$lhs) -> bool {
|
||||
PartialEq::eq(other, self)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_partial_ord {
|
||||
($lhs:ty, $rhs:ty) => {
|
||||
#[allow(unused_lifetimes)]
|
||||
impl<'a> PartialOrd<$rhs> for $lhs {
|
||||
#[inline]
|
||||
fn partial_cmp(&self, other: &$rhs) -> Option<Ordering> {
|
||||
let l = self;
|
||||
let r: &Self = other.as_ref();
|
||||
PartialOrd::partial_cmp(l, r)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_lifetimes)]
|
||||
impl<'a> PartialOrd<$lhs> for $rhs {
|
||||
#[inline]
|
||||
fn partial_cmp(&self, other: &$lhs) -> Option<Ordering> {
|
||||
PartialOrd::partial_cmp(other, self)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "ascii"))]
|
||||
mod tests;
|
||||
Vendored
+336
@@ -0,0 +1,336 @@
|
||||
/// Initialize a struct or tuple out of a sequences of parsers
|
||||
///
|
||||
/// Unlike normal struct initialization syntax:
|
||||
/// - `_` fields can exist to run a parser but ignore the result
|
||||
/// - Parse results for a field can later be referenced using the field name
|
||||
///
|
||||
/// Unlike normal tuple initialization syntax:
|
||||
/// - Struct-style initialization (`{ 0: _, 1: _}`) is not supported
|
||||
/// - `_: <parser>` fields can exist to run a parser but ignore the result
|
||||
///
|
||||
///# Example
|
||||
///
|
||||
/// ```
|
||||
/// # #[cfg(feature = "ascii")] {
|
||||
/// # use winnow::prelude::*;
|
||||
/// # use winnow::ascii::{alphanumeric1, dec_uint, space0};
|
||||
/// # use winnow::combinator::delimited;
|
||||
/// # use winnow::combinator::empty;
|
||||
/// # use winnow::error::ContextError;
|
||||
/// # use winnow::error::ErrMode;
|
||||
/// use winnow::combinator::seq;
|
||||
///
|
||||
/// #[derive(Default, Debug, PartialEq)]
|
||||
/// struct Field {
|
||||
/// namespace: u32,
|
||||
/// name: Vec<u8>,
|
||||
/// value: Vec<u8>,
|
||||
/// point: (u32, u32),
|
||||
/// metadata: Vec<u8>,
|
||||
/// }
|
||||
///
|
||||
/// // Parse into structs / tuple-structs
|
||||
/// fn field(input: &mut &[u8]) -> ModalResult<Field> {
|
||||
/// seq!{Field {
|
||||
/// namespace: empty.value(5),
|
||||
/// name: alphanumeric1.map(|s: &[u8]| s.to_owned()),
|
||||
/// // `_` fields are ignored when building the struct
|
||||
/// _: (space0, b':', space0),
|
||||
/// value: alphanumeric1.map(|s: &[u8]| s.to_owned()),
|
||||
/// _: (space0, b':', space0),
|
||||
/// point: point,
|
||||
/// // default initialization also works
|
||||
/// ..Default::default()
|
||||
/// }}.parse_next(input)
|
||||
/// }
|
||||
///
|
||||
/// // Or parse into tuples
|
||||
/// fn point(input: &mut &[u8]) -> ModalResult<(u32, u32)> {
|
||||
/// let mut num = dec_uint::<_, u32, ErrMode<ContextError>>;
|
||||
/// seq!(num, _: (space0, b',', space0), num).parse_next(input)
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// field.parse_peek(&b"test: data: 123 , 4"[..]),
|
||||
/// Ok((
|
||||
/// &b""[..],
|
||||
/// Field {
|
||||
/// namespace: 5,
|
||||
/// name: b"test"[..].to_owned(),
|
||||
/// value: b"data"[..].to_owned(),
|
||||
/// point: (123, 4),
|
||||
/// metadata: Default::default(),
|
||||
/// },
|
||||
/// )),
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
#[doc(alias = "tuple")]
|
||||
#[doc(alias = "preceded")]
|
||||
#[doc(alias = "terminated")]
|
||||
#[doc(alias = "delimited")]
|
||||
#[doc(alias = "pair")]
|
||||
#[doc(alias = "separated_pair")]
|
||||
#[doc(alias = "struct_parser")]
|
||||
#[doc(hidden)] // forced to be visible in intended location
|
||||
macro_rules! seq {
|
||||
($($name: ident)::* { $($fields: tt)* }) => {
|
||||
$crate::combinator::trace(stringify!($($name)::*), move |input: &mut _| {
|
||||
$crate::seq_parse_struct_fields!(
|
||||
( $($fields)* );
|
||||
( _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20 );
|
||||
input ;
|
||||
);
|
||||
Ok($crate::seq_init_struct_fields!(
|
||||
( $($fields)* );
|
||||
$($name)::* ;
|
||||
))
|
||||
})
|
||||
};
|
||||
($($name: ident)::* ( $($fields: tt)* )) => {
|
||||
$crate::combinator::trace(stringify!($($name)::*), move |input: &mut _| {
|
||||
$crate::seq_parse_tuple_fields!(
|
||||
( $($fields)* );
|
||||
( _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20 );
|
||||
input;
|
||||
);
|
||||
Ok($crate::seq_init_tuple_fields!(
|
||||
( $($fields)* );
|
||||
( _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20 );
|
||||
$($name)::*;
|
||||
))
|
||||
})
|
||||
};
|
||||
(( $($fields: tt)* )) => {
|
||||
$crate::combinator::trace("tuple", move |input: &mut _| {
|
||||
$crate::seq_parse_tuple_fields!(
|
||||
( $($fields)* );
|
||||
( _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20 );
|
||||
input;
|
||||
);
|
||||
Ok($crate::seq_init_tuple_fields!(
|
||||
( $($fields)* );
|
||||
( _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20 );
|
||||
;
|
||||
))
|
||||
})
|
||||
};
|
||||
($($fields: tt)*) => {
|
||||
$crate::seq!((
|
||||
$($fields)*
|
||||
))
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! seq_parse_struct_fields {
|
||||
(
|
||||
( _ : $head_parser: expr, $($fields: tt)* );
|
||||
( $unnamed1: ident, $($unnamed: ident),* );
|
||||
$input: ident ;
|
||||
) => {
|
||||
let $unnamed1 = $crate::Parser::parse_next(&mut $head_parser, $input)?;
|
||||
$crate::seq_parse_struct_fields!(
|
||||
( $($fields)* );
|
||||
( $($unnamed),* );
|
||||
$input ;
|
||||
)
|
||||
};
|
||||
(
|
||||
( _ : $head_parser: expr );
|
||||
( $unnamed1: ident, $($unnamed: ident),* );
|
||||
$input: ident ;
|
||||
) => {
|
||||
let $unnamed1 = $crate::Parser::parse_next(&mut $head_parser, $input)?;
|
||||
};
|
||||
(
|
||||
( $head_field: ident : $head_parser: expr, $($fields: tt)* );
|
||||
( $unnamed1: ident, $($unnamed: ident),* );
|
||||
$input: ident ;
|
||||
) => {
|
||||
let $head_field = $crate::Parser::parse_next(&mut $head_parser, $input)?;
|
||||
$crate::seq_parse_struct_fields!(
|
||||
( $($fields)* );
|
||||
( $($unnamed),* );
|
||||
$input ;
|
||||
)
|
||||
};
|
||||
(
|
||||
( $head_field: ident : $head_parser: expr );
|
||||
( $unnamed1: ident, $($unnamed: ident),* );
|
||||
$input: ident ;
|
||||
) => {
|
||||
let $head_field = $crate::Parser::parse_next(&mut $head_parser, $input)?;
|
||||
};
|
||||
(
|
||||
( .. $update: expr );
|
||||
( $($unnamed: ident),* );
|
||||
$input: expr ;
|
||||
) => {};
|
||||
(
|
||||
( $(,)? );
|
||||
( $($unnamed: ident),* );
|
||||
$input: expr ;
|
||||
) => {};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! seq_parse_tuple_fields {
|
||||
(
|
||||
( $(_ :)? $head_parser: expr, $($fields: tt)* );
|
||||
( $unnamed1: ident, $($unnamed: ident),* );
|
||||
$input: ident;
|
||||
) => {
|
||||
let $unnamed1 = $crate::Parser::parse_next(&mut $head_parser, $input)?;
|
||||
$crate::seq_parse_tuple_fields!(
|
||||
( $($fields)* );
|
||||
( $($unnamed),* );
|
||||
$input ;
|
||||
)
|
||||
};
|
||||
(
|
||||
( $(_ :)? $head_parser: expr );
|
||||
( $unnamed1: ident, $($unnamed: ident),* );
|
||||
$input: ident;
|
||||
) => {
|
||||
let $unnamed1 = $crate::Parser::parse_next(&mut $head_parser, $input)?;
|
||||
};
|
||||
(
|
||||
( $(,)? );
|
||||
( $($unnamed: ident),* );
|
||||
$input: expr;
|
||||
) => {};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! seq_init_struct_fields {
|
||||
(
|
||||
( _ : $head_parser: expr, $($fields: tt)* );
|
||||
$($name: ident)::* ;
|
||||
$($inits: tt)*
|
||||
) => {
|
||||
$crate::seq_init_struct_fields!(
|
||||
( $($fields)* );
|
||||
$($name)::* ;
|
||||
$($inits)*
|
||||
)
|
||||
};
|
||||
(
|
||||
( _ : $head_parser: expr );
|
||||
$($name: ident)::* ;
|
||||
$($inits: tt)*
|
||||
) => {
|
||||
$crate::seq_init_struct_fields!(
|
||||
();
|
||||
$($name)::* ;
|
||||
$($inits)*
|
||||
)
|
||||
};
|
||||
(
|
||||
( $head_field: ident : $head_parser: expr, $($fields: tt)* );
|
||||
$($name: ident)::* ;
|
||||
$($inits: tt)*
|
||||
) =>
|
||||
{
|
||||
$crate::seq_init_struct_fields!(
|
||||
( $($fields)* );
|
||||
$($name)::* ;
|
||||
$($inits)* $head_field,
|
||||
)
|
||||
};
|
||||
(
|
||||
( $head_field: ident : $head_parser: expr );
|
||||
$($name: ident)::* ;
|
||||
$($inits: tt)*
|
||||
) => {
|
||||
$crate::seq_init_struct_fields!(
|
||||
();
|
||||
$($name)::* ;
|
||||
$($inits)* $head_field,
|
||||
)
|
||||
};
|
||||
(
|
||||
( .. $update: expr );
|
||||
$($name: ident)::* ;
|
||||
$($inits: tt)*
|
||||
) => {
|
||||
$($name)::* { $($inits)* ..$update }
|
||||
};
|
||||
(
|
||||
( $(,)? );
|
||||
$($name: ident)::* ;
|
||||
$($inits: tt)*
|
||||
) => {
|
||||
$($name)::* { $($inits)* }
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! seq_init_tuple_fields {
|
||||
(
|
||||
( _ : $head_parser: expr, $($fields: tt)* );
|
||||
( $unnamed1: ident, $($unnamed: ident),* );
|
||||
$($name: ident)::*;
|
||||
$($inits: tt)*
|
||||
) => {
|
||||
$crate::seq_init_tuple_fields!(
|
||||
( $($fields)* );
|
||||
( $($unnamed),* );
|
||||
$($name)::* ;
|
||||
$($inits)*
|
||||
)
|
||||
};
|
||||
(
|
||||
( _ : $head_parser: expr );
|
||||
( $unnamed1: ident, $($unnamed: ident),* );
|
||||
$($name: ident)::*;
|
||||
$($inits: tt)*
|
||||
) => {
|
||||
$crate::seq_init_tuple_fields!(
|
||||
();
|
||||
( $($unnamed),* );
|
||||
$($name)::* ;
|
||||
$($inits)*
|
||||
)
|
||||
};
|
||||
(
|
||||
( $head_parser: expr, $($fields: tt)* );
|
||||
( $unnamed1: ident, $($unnamed: ident),* );
|
||||
$($name: ident)::*;
|
||||
$($inits: tt)*
|
||||
) =>
|
||||
{
|
||||
$crate::seq_init_tuple_fields!(
|
||||
( $($fields)* );
|
||||
( $($unnamed),* );
|
||||
$($name)::* ;
|
||||
$($inits)* $unnamed1,
|
||||
)
|
||||
};
|
||||
(
|
||||
( $head_parser: expr );
|
||||
( $unnamed1: ident, $($unnamed: ident),* );
|
||||
$($name: ident)::*;
|
||||
$($inits: tt)*
|
||||
) => {
|
||||
$crate::seq_init_tuple_fields!(
|
||||
();
|
||||
( $($unnamed),* );
|
||||
$($name)::* ;
|
||||
$($inits)* $unnamed1,
|
||||
)
|
||||
};
|
||||
(
|
||||
( $(,)? );
|
||||
( $unnamed1: ident, $($unnamed: ident),* );
|
||||
$($name: ident)::*;
|
||||
$($inits: tt)*
|
||||
) => {
|
||||
$($name)::* ( $($inits)* )
|
||||
};
|
||||
}
|
||||
Vendored
+1330
File diff suppressed because it is too large.
Load diff
+594
@@ -0,0 +1,594 @@
|
||||
/// Initialize a struct or tuple out of an unordered sequences of parsers
|
||||
///
|
||||
/// Unlike normal struct initialization syntax:
|
||||
/// - `_` fields can exist to run a parser but ignore the result
|
||||
/// - Parse results for a field can later be referenced using the field name
|
||||
///
|
||||
/// Unlike normal tuple initialization syntax:
|
||||
/// - Struct-style initialization (`{ 0: _, 1: _}`) is not supported
|
||||
/// - `_: <parser>` fields can exist to run a parser but ignore the result
|
||||
///
|
||||
/// To stop on an error, rather than trying further permutations, see
|
||||
/// [`cut_err`][crate::combinator::cut_err] ([example][crate::_tutorial::chapter_7]).
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(feature = "ascii")] {
|
||||
/// # use winnow::{error::ErrMode, error::Needed};
|
||||
/// # use winnow::prelude::*;
|
||||
/// use winnow::ascii::{alpha1, digit1};
|
||||
/// use winnow::combinator::unordered_seq;
|
||||
/// fn parser<'i>(input: &mut &'i str) -> ModalResult<(&'i str, &'i str)> {
|
||||
/// unordered_seq!((alpha1, digit1)).parse_next(input)
|
||||
/// }
|
||||
///
|
||||
/// // permutation takes alphabetic characters then digit
|
||||
/// assert_eq!(parser.parse_peek("abc123"), Ok(("", ("abc", "123"))));
|
||||
///
|
||||
/// // but also in inverse order
|
||||
/// assert_eq!(parser.parse_peek("123abc"), Ok(("", ("abc", "123"))));
|
||||
///
|
||||
/// // it will fail if one of the parsers failed
|
||||
/// assert!(parser.parse_peek("abc;").is_err());
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// The parsers are applied greedily: if there are multiple unapplied parsers
|
||||
/// that could parse the next slice of input, the first one is used.
|
||||
/// ```rust
|
||||
/// # #[cfg(feature = "parser")] {
|
||||
/// # use winnow::error::ErrMode;
|
||||
/// # use winnow::prelude::*;
|
||||
/// use winnow::combinator::unordered_seq;
|
||||
/// use winnow::token::any;
|
||||
///
|
||||
/// fn parser(input: &mut &str) -> ModalResult<(char, char)> {
|
||||
/// unordered_seq!((any, 'a')).parse_next(input)
|
||||
/// }
|
||||
///
|
||||
/// // any parses 'b', then char('a') parses 'a'
|
||||
/// assert_eq!(parser.parse_peek("ba"), Ok(("", ('b', 'a'))));
|
||||
///
|
||||
/// // any parses 'a', then char('a') fails on 'b',
|
||||
/// // even though char('a') followed by any would succeed
|
||||
/// assert!(parser.parse_peek("ab").is_err());
|
||||
/// # }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
#[doc(alias = "permutation")]
|
||||
#[doc(hidden)] // forced to be visible in intended location
|
||||
macro_rules! unordered_seq {
|
||||
($($name: ident)::* { $($fields: tt)* }) => {
|
||||
$crate::combinator::trace(stringify!($($name)::*), move |input: &mut _| {
|
||||
$crate::unordered_seq_parse_struct_body!(
|
||||
( $($name)::* );
|
||||
( $($fields)* );
|
||||
( _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20 );
|
||||
( input );
|
||||
)
|
||||
})
|
||||
};
|
||||
(( $($fields: tt)* )) => {
|
||||
$crate::combinator::trace("tuple", move |input: &mut _| {
|
||||
$crate::unordered_seq_parse_tuple_body!(
|
||||
( );
|
||||
( $($fields)* );
|
||||
( _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20 );
|
||||
( input );
|
||||
)
|
||||
})
|
||||
};
|
||||
($($name: ident)::* ( $($fields: tt)* )) => {
|
||||
$crate::combinator::trace(stringify!($($name)::*), move |input: &mut _| {
|
||||
$crate::unordered_seq_parse_tuple_body!(
|
||||
( $($name)::* );
|
||||
( $($fields)* );
|
||||
( _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20 );
|
||||
( input );
|
||||
)
|
||||
})
|
||||
};
|
||||
($($fields: tt)*) => {
|
||||
$crate::unordered_seq!((
|
||||
$($fields)*
|
||||
))
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! unordered_seq_parse_struct_body {
|
||||
(
|
||||
( $($name: ident)::* );
|
||||
( $($fields: tt)* );
|
||||
( $($unnamed: ident),* );
|
||||
( $input: expr );
|
||||
) => {{
|
||||
// Workaround a type inference issue by hinting to the compiler `I` within `ParserError` calls
|
||||
fn recover_err<I, E>(_input: &I, curr: E, acc: &mut Option<E>) -> Option<E>
|
||||
where
|
||||
I: $crate::stream::Stream,
|
||||
E: $crate::error::ParserError<I>,
|
||||
{
|
||||
if $crate::error::ParserError::is_backtrack(&curr) {
|
||||
*acc = Some(match acc.take() {
|
||||
Some(err) => $crate::error::ParserError::or(err, curr),
|
||||
None => curr,
|
||||
});
|
||||
None
|
||||
} else {
|
||||
Some(curr)
|
||||
}
|
||||
}
|
||||
|
||||
$crate::unordered_seq_parse_struct_init!(
|
||||
( $($fields)* );
|
||||
( $($unnamed),* );
|
||||
);
|
||||
|
||||
loop {
|
||||
let mut err = ::core::option::Option::None;
|
||||
let start = $crate::stream::Stream::checkpoint($input);
|
||||
|
||||
$crate::unordered_seq_parse_struct_each!(
|
||||
( $($fields)* );
|
||||
( $($unnamed),* );
|
||||
( $input );
|
||||
( start );
|
||||
( err );
|
||||
);
|
||||
|
||||
// If we reach here, every iterator has either been applied before,
|
||||
// or errored on the remaining input
|
||||
if let ::core::option::Option::Some(err) = err {
|
||||
// There are remaining parsers, and all errored on the remaining input
|
||||
break Err($crate::error::ParserError::append(err, $input, &start));
|
||||
}
|
||||
|
||||
// All parsers were applied
|
||||
break Ok(
|
||||
$crate::unordered_seq_parse_struct_collect!(
|
||||
( $($fields)* );
|
||||
( $($unnamed),* );
|
||||
( $($name)::* );
|
||||
)
|
||||
);
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! unordered_seq_parse_struct_init {
|
||||
(
|
||||
( _ : $head_parser: expr, $($fields: tt)* );
|
||||
( $unnamed1: ident, $($unnamed: ident),* );
|
||||
) => {
|
||||
let mut $unnamed1 = ::core::option::Option::None;
|
||||
$crate::unordered_seq_parse_struct_init!(
|
||||
( $($fields)* );
|
||||
( $($unnamed),* );
|
||||
);
|
||||
};
|
||||
(
|
||||
( $named: ident : $head_parser: expr, $($fields: tt)* );
|
||||
( $unnamed1: ident, $($unnamed: ident),* );
|
||||
) => {
|
||||
let mut $named = ::core::option::Option::None;
|
||||
$crate::unordered_seq_parse_struct_init!(
|
||||
( $($fields)* );
|
||||
( $($unnamed),* );
|
||||
);
|
||||
};
|
||||
(
|
||||
( _ : $head_parser: expr );
|
||||
( $unnamed1: ident, $($unnamed: ident),* );
|
||||
) => {
|
||||
let mut $unnamed1 = ::core::option::Option::None;
|
||||
};
|
||||
(
|
||||
( $named: ident : $head_parser: expr );
|
||||
( $unnamed1: ident, $($unnamed: ident),* );
|
||||
) => {
|
||||
let mut $named = ::core::option::Option::None;
|
||||
};
|
||||
(
|
||||
( .. $update: expr $(,)? );
|
||||
( $($unnamed: ident),* );
|
||||
) => {
|
||||
};
|
||||
(
|
||||
( $(,)? );
|
||||
( $($unnamed: ident),* );
|
||||
) => {
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! unordered_seq_parse_struct_collect {
|
||||
(
|
||||
( $(,)? );
|
||||
( $($unnamed: ident),* );
|
||||
( $($name: ident)::* );
|
||||
$($inits: tt)*
|
||||
) => {
|
||||
$($name)::* { $($inits)* }
|
||||
};
|
||||
(
|
||||
( .. $update: expr $(,)? );
|
||||
( $unnamed1: ident, $($unnamed: ident),* );
|
||||
( $($name: ident)::* );
|
||||
$($inits: tt)*
|
||||
) => {
|
||||
$crate::unordered_seq_parse_struct_collect!(
|
||||
( , );
|
||||
( $($unnamed),* );
|
||||
( $($name)::* );
|
||||
$($inits)* .. $update
|
||||
)
|
||||
};
|
||||
(
|
||||
( _ : $head_parser: expr );
|
||||
( $unnamed1: ident, $($unnamed: ident),* );
|
||||
( $($name: ident)::* );
|
||||
$($inits: tt)*
|
||||
) => {
|
||||
$crate::unordered_seq_parse_struct_collect!(
|
||||
( , );
|
||||
( $($unnamed),* );
|
||||
( $($name)::* );
|
||||
$($inits)*
|
||||
)
|
||||
};
|
||||
(
|
||||
( _ : $head_parser: expr, $($fields: tt)* );
|
||||
( $unnamed1: ident, $($unnamed: ident),* );
|
||||
( $($name: ident)::* );
|
||||
$($inits: tt)*
|
||||
) => {
|
||||
$crate::unordered_seq_parse_struct_collect!(
|
||||
( $($fields)* );
|
||||
( $($unnamed),* );
|
||||
( $($name)::* );
|
||||
$($inits)*
|
||||
)
|
||||
};
|
||||
(
|
||||
( $named: ident : $head_parser: expr );
|
||||
( $unnamed1: ident, $($unnamed: ident),* );
|
||||
( $($name: ident)::* );
|
||||
$($inits: tt)*
|
||||
) => {
|
||||
$crate::unordered_seq_parse_struct_collect!(
|
||||
( , );
|
||||
( $($unnamed),* );
|
||||
( $($name)::* );
|
||||
$($inits)* $named: $named.unwrap(),
|
||||
)
|
||||
};
|
||||
(
|
||||
( $named: ident : $head_parser: expr, $($fields: tt)* );
|
||||
( $unnamed1: ident, $($unnamed: ident),* );
|
||||
( $($name: ident)::* );
|
||||
$($inits: tt)*
|
||||
) => {
|
||||
$crate::unordered_seq_parse_struct_collect!(
|
||||
( $($fields)* );
|
||||
( $($unnamed),* );
|
||||
( $($name)::* );
|
||||
$($inits)* $named: $named.unwrap(),
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! unordered_seq_parse_struct_each(
|
||||
(
|
||||
( _ : $head_parser: expr $(,)? );
|
||||
( $unnamed1: ident, $($unnamed: ident),* );
|
||||
( $input: expr );
|
||||
( $start: expr );
|
||||
( $err: expr );
|
||||
) => (
|
||||
$crate::unordered_seq_parse_next!(
|
||||
( $head_parser );
|
||||
( $unnamed1 );
|
||||
( $input );
|
||||
( $start );
|
||||
( $err );
|
||||
);
|
||||
);
|
||||
(
|
||||
( $named: ident : $head_parser: expr $(,)? );
|
||||
( $unnamed1: ident, $($unnamed: ident),* );
|
||||
( $input: expr );
|
||||
( $start: expr );
|
||||
( $err: expr );
|
||||
) => (
|
||||
$crate::unordered_seq_parse_next!(
|
||||
( $head_parser );
|
||||
( $named );
|
||||
( $input );
|
||||
( $start );
|
||||
( $err );
|
||||
);
|
||||
);
|
||||
(
|
||||
( _ : $head_parser: expr, $($fields: tt)* );
|
||||
( $unnamed1: ident, $($unnamed: ident),* );
|
||||
( $input: expr );
|
||||
( $start: expr );
|
||||
( $err: expr );
|
||||
) => (
|
||||
$crate::unordered_seq_parse_next!(
|
||||
( $head_parser );
|
||||
( $unnamed1 );
|
||||
( $input );
|
||||
( $start );
|
||||
( $err );
|
||||
);
|
||||
$crate::unordered_seq_parse_struct_each!(
|
||||
( $($fields)* );
|
||||
( $($unnamed),* );
|
||||
( $input );
|
||||
( $start );
|
||||
( $err );
|
||||
);
|
||||
);
|
||||
(
|
||||
( $named: ident : $head_parser: expr, $($fields: tt)* );
|
||||
( $unnamed1: ident, $($unnamed: ident),* );
|
||||
( $input: expr );
|
||||
( $start: expr );
|
||||
( $err: expr );
|
||||
) => (
|
||||
$crate::unordered_seq_parse_next!(
|
||||
( $head_parser );
|
||||
( $named );
|
||||
( $input );
|
||||
( $start );
|
||||
( $err );
|
||||
);
|
||||
$crate::unordered_seq_parse_struct_each!(
|
||||
( $($fields)* );
|
||||
( $($unnamed),* );
|
||||
( $input );
|
||||
( $start );
|
||||
( $err );
|
||||
);
|
||||
);
|
||||
(
|
||||
( .. $update: expr $(,)? );
|
||||
( $unnamed1: ident, $($unnamed: ident),* );
|
||||
( $input: expr );
|
||||
( $start: expr );
|
||||
( $err: expr );
|
||||
) => (
|
||||
);
|
||||
);
|
||||
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! unordered_seq_parse_tuple_body {
|
||||
(
|
||||
( $($name: ident)::* );
|
||||
( $($fields: tt)* );
|
||||
( $($unnamed: ident),* );
|
||||
( $input: expr );
|
||||
) => {{
|
||||
// Workaround a type inference issue by hinting to the compiler `I` within `ParserError` calls
|
||||
fn recover_err<I, E>(_input: &I, curr: E, acc: &mut Option<E>) -> Option<E>
|
||||
where
|
||||
I: $crate::stream::Stream,
|
||||
E: $crate::error::ParserError<I>,
|
||||
{
|
||||
if $crate::error::ParserError::is_backtrack(&curr) {
|
||||
*acc = Some(match acc.take() {
|
||||
Some(err) => $crate::error::ParserError::or(err, curr),
|
||||
None => curr,
|
||||
});
|
||||
None
|
||||
} else {
|
||||
Some(curr)
|
||||
}
|
||||
}
|
||||
|
||||
$crate::unordered_seq_parse_tuple_init!(
|
||||
( $($fields)* );
|
||||
( $($unnamed),* );
|
||||
);
|
||||
|
||||
loop {
|
||||
let mut err = ::core::option::Option::None;
|
||||
let start = $crate::stream::Stream::checkpoint($input);
|
||||
|
||||
$crate::unordered_seq_parse_tuple_each!(
|
||||
( $($fields)* );
|
||||
( $($unnamed),* );
|
||||
( $input );
|
||||
( start );
|
||||
( err );
|
||||
);
|
||||
|
||||
// If we reach here, every iterator has either been applied before,
|
||||
// or errored on the remaining input
|
||||
if let ::core::option::Option::Some(err) = err {
|
||||
// There are remaining parsers, and all errored on the remaining input
|
||||
break Err($crate::error::ParserError::append(err, $input, &start));
|
||||
}
|
||||
|
||||
// All parsers were applied
|
||||
break Ok(
|
||||
$crate::unordered_seq_parse_tuple_collect!(
|
||||
( $($fields)* );
|
||||
( $($unnamed),* );
|
||||
( $($name)::* );
|
||||
)
|
||||
);
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! unordered_seq_parse_tuple_init {
|
||||
(
|
||||
( $(_ :)? $head_parser: expr, $($fields: tt)* );
|
||||
( $unnamed1: ident, $($unnamed: ident),* );
|
||||
) => {
|
||||
let mut $unnamed1 = ::core::option::Option::None;
|
||||
$crate::unordered_seq_parse_tuple_init!(
|
||||
( $($fields)* );
|
||||
( $($unnamed),* );
|
||||
);
|
||||
};
|
||||
(
|
||||
( $(_ :)? $head_parser: expr );
|
||||
( $unnamed1: ident, $($unnamed: ident),* );
|
||||
) => {
|
||||
let mut $unnamed1 = ::core::option::Option::None;
|
||||
};
|
||||
(
|
||||
( $(,)? );
|
||||
( $($unnamed: ident),* );
|
||||
) => {
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! unordered_seq_parse_tuple_collect {
|
||||
(
|
||||
( $(,)? );
|
||||
( $($unnamed: ident),* );
|
||||
( $($name: ident)::* );
|
||||
$($inits: tt)*
|
||||
) => {
|
||||
$($name)::* ( $($inits)* )
|
||||
};
|
||||
(
|
||||
( _ : $head_parser: expr );
|
||||
( $unnamed1: ident, $($unnamed: ident),* );
|
||||
( $($name: ident)::* );
|
||||
$($inits: tt)*
|
||||
) => {
|
||||
$crate::unordered_seq_parse_tuple_collect!(
|
||||
( , );
|
||||
( $($unnamed),* );
|
||||
( $($name)::* );
|
||||
$($inits)*
|
||||
)
|
||||
};
|
||||
(
|
||||
( _ : $head_parser: expr, $($fields: tt)* );
|
||||
( $unnamed1: ident, $($unnamed: ident),* );
|
||||
( $($name: ident)::* );
|
||||
$($inits: tt)*
|
||||
) => {
|
||||
$crate::unordered_seq_parse_tuple_collect!(
|
||||
( $($fields)* );
|
||||
( $($unnamed),* );
|
||||
( $($name)::* );
|
||||
$($inits)*
|
||||
)
|
||||
};
|
||||
(
|
||||
( $head_parser: expr );
|
||||
( $unnamed1: ident, $($unnamed: ident),* );
|
||||
( $($name: ident)::* );
|
||||
$($inits: tt)*
|
||||
) => {
|
||||
$crate::unordered_seq_parse_tuple_collect!(
|
||||
( , );
|
||||
( $($unnamed),* );
|
||||
( $($name)::* );
|
||||
$($inits)* $unnamed1.unwrap(),
|
||||
)
|
||||
};
|
||||
(
|
||||
( $head_parser: expr, $($fields: tt)* );
|
||||
( $unnamed1: ident, $($unnamed: ident),* );
|
||||
( $($name: ident)::* );
|
||||
$($inits: tt)*
|
||||
) => {
|
||||
$crate::unordered_seq_parse_tuple_collect!(
|
||||
( $($fields)* );
|
||||
( $($unnamed),* );
|
||||
( $($name)::* );
|
||||
$($inits)* $unnamed1.unwrap(),
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! unordered_seq_parse_tuple_each(
|
||||
(
|
||||
( $(_ :)? $head_parser: expr $(,)? );
|
||||
( $unnamed1: ident, $($unnamed: ident),* );
|
||||
( $input: expr );
|
||||
( $start: expr );
|
||||
( $err: expr );
|
||||
) => (
|
||||
$crate::unordered_seq_parse_next!(
|
||||
( $head_parser );
|
||||
( $unnamed1 );
|
||||
( $input );
|
||||
( $start );
|
||||
( $err );
|
||||
);
|
||||
);
|
||||
(
|
||||
( $(_ :)? $head_parser: expr, $($fields: tt)* );
|
||||
( $unnamed1: ident, $($unnamed: ident),* );
|
||||
( $input: expr );
|
||||
( $start: expr );
|
||||
( $err: expr );
|
||||
) => (
|
||||
$crate::unordered_seq_parse_next!(
|
||||
( $head_parser );
|
||||
( $unnamed1 );
|
||||
( $input );
|
||||
( $start );
|
||||
( $err );
|
||||
);
|
||||
$crate::unordered_seq_parse_tuple_each!(
|
||||
( $($fields)* );
|
||||
( $($unnamed),* );
|
||||
( $input );
|
||||
( $start );
|
||||
( $err );
|
||||
);
|
||||
);
|
||||
);
|
||||
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! unordered_seq_parse_next(
|
||||
(
|
||||
( $field: tt );
|
||||
( $unnamed: ident );
|
||||
( $input: expr );
|
||||
( $start: expr );
|
||||
( $err: expr );
|
||||
) => (
|
||||
if $unnamed.is_none() {
|
||||
$crate::stream::Stream::reset($input, &$start);
|
||||
|
||||
match $crate::Parser::parse_next(&mut $field, $input) {
|
||||
Ok(o) => {
|
||||
$unnamed = Some(o);
|
||||
continue;
|
||||
}
|
||||
Err(e) => {
|
||||
if let Some(cut) = recover_err($input, e, &mut $err) {
|
||||
return Err(cut);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
);
|
||||
);
|
||||
Vendored
+1487
File diff suppressed because it is too large.
Load diff
Vendored
+540
@@ -0,0 +1,540 @@
|
||||
use core::num::NonZeroUsize;
|
||||
|
||||
use crate::stream::AsBStr;
|
||||
use crate::stream::Checkpoint;
|
||||
use crate::stream::Compare;
|
||||
use crate::stream::CompareResult;
|
||||
use crate::stream::FindSlice;
|
||||
use crate::stream::Needed;
|
||||
use crate::stream::Offset;
|
||||
#[cfg(feature = "unstable-recover")]
|
||||
#[cfg(feature = "std")]
|
||||
use crate::stream::Recover;
|
||||
use crate::stream::SliceLen;
|
||||
use crate::stream::Stream;
|
||||
use crate::stream::StreamIsPartial;
|
||||
use crate::stream::UpdateSlice;
|
||||
use core::iter::{Cloned, Enumerate};
|
||||
use core::slice::Iter;
|
||||
use core::{cmp::Ordering, fmt, ops};
|
||||
|
||||
/// Improved `Debug` experience for `&[u8]` UTF-8-ish streams
|
||||
#[allow(clippy::derived_hash_with_manual_eq)]
|
||||
#[derive(Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct BStr([u8]);
|
||||
|
||||
impl BStr {
|
||||
/// Make a stream out of a byte slice-like.
|
||||
#[inline]
|
||||
pub fn new<B: ?Sized + AsRef<[u8]>>(bytes: &B) -> &Self {
|
||||
Self::from_bytes(bytes.as_ref())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn from_bytes(slice: &[u8]) -> &Self {
|
||||
unsafe { core::mem::transmute(slice) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn as_bytes(&self) -> &[u8] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl SliceLen for &BStr {
|
||||
#[inline(always)]
|
||||
fn slice_len(&self) -> usize {
|
||||
self.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'i> Stream for &'i BStr {
|
||||
type Token = u8;
|
||||
type Slice = &'i [u8];
|
||||
|
||||
type IterOffsets = Enumerate<Cloned<Iter<'i, u8>>>;
|
||||
|
||||
type Checkpoint = Checkpoint<Self, Self>;
|
||||
|
||||
#[inline(always)]
|
||||
fn iter_offsets(&self) -> Self::IterOffsets {
|
||||
self.iter().cloned().enumerate()
|
||||
}
|
||||
#[inline(always)]
|
||||
fn eof_offset(&self) -> usize {
|
||||
self.len()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn next_token(&mut self) -> Option<Self::Token> {
|
||||
if self.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let token = self[0];
|
||||
*self = &self[1..];
|
||||
Some(token)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn peek_token(&self) -> Option<Self::Token> {
|
||||
if self.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(self[0])
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn offset_for<P>(&self, predicate: P) -> Option<usize>
|
||||
where
|
||||
P: Fn(Self::Token) -> bool,
|
||||
{
|
||||
self.iter().position(|b| predicate(*b))
|
||||
}
|
||||
#[inline(always)]
|
||||
fn offset_at(&self, tokens: usize) -> Result<usize, Needed> {
|
||||
if let Some(needed) = tokens.checked_sub(self.len()).and_then(NonZeroUsize::new) {
|
||||
Err(Needed::Size(needed))
|
||||
} else {
|
||||
Ok(tokens)
|
||||
}
|
||||
}
|
||||
#[inline(always)]
|
||||
fn next_slice(&mut self, offset: usize) -> Self::Slice {
|
||||
let (slice, next) = self.0.split_at(offset);
|
||||
*self = BStr::from_bytes(next);
|
||||
slice
|
||||
}
|
||||
#[inline(always)]
|
||||
unsafe fn next_slice_unchecked(&mut self, offset: usize) -> Self::Slice {
|
||||
#[cfg(debug_assertions)]
|
||||
self.peek_slice(offset);
|
||||
|
||||
// SAFETY: `Stream::next_slice_unchecked` requires `offset` to be in bounds
|
||||
let slice = unsafe { self.0.get_unchecked(..offset) };
|
||||
// SAFETY: `Stream::next_slice_unchecked` requires `offset` to be in bounds
|
||||
let next = unsafe { self.0.get_unchecked(offset..) };
|
||||
*self = BStr::from_bytes(next);
|
||||
slice
|
||||
}
|
||||
#[inline(always)]
|
||||
fn peek_slice(&self, offset: usize) -> Self::Slice {
|
||||
&self[..offset]
|
||||
}
|
||||
#[inline(always)]
|
||||
unsafe fn peek_slice_unchecked(&self, offset: usize) -> Self::Slice {
|
||||
#[cfg(debug_assertions)]
|
||||
self.peek_slice(offset);
|
||||
|
||||
// SAFETY: `Stream::next_slice_unchecked` requires `offset` to be in bounds
|
||||
let slice = unsafe { self.0.get_unchecked(..offset) };
|
||||
slice
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn checkpoint(&self) -> Self::Checkpoint {
|
||||
Checkpoint::<_, Self>::new(*self)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn reset(&mut self, checkpoint: &Self::Checkpoint) {
|
||||
*self = checkpoint.inner;
|
||||
}
|
||||
|
||||
fn trace(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "{self:#?}")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable-recover")]
|
||||
#[cfg(feature = "std")]
|
||||
impl<E> Recover<E> for &BStr {
|
||||
#[inline(always)]
|
||||
fn record_err(
|
||||
&mut self,
|
||||
_token_start: &Self::Checkpoint,
|
||||
_err_start: &Self::Checkpoint,
|
||||
err: E,
|
||||
) -> Result<(), E> {
|
||||
Err(err)
|
||||
}
|
||||
|
||||
/// Report whether the [`Stream`] can save off errors for recovery
|
||||
#[inline(always)]
|
||||
fn is_recovery_supported() -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl StreamIsPartial for &BStr {
|
||||
type PartialState = ();
|
||||
|
||||
#[inline]
|
||||
fn complete(&mut self) -> Self::PartialState {
|
||||
// Already complete
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn restore_partial(&mut self, _state: Self::PartialState) {}
|
||||
|
||||
#[inline(always)]
|
||||
fn is_partial_supported() -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl Offset for &BStr {
|
||||
#[inline(always)]
|
||||
fn offset_from(&self, start: &Self) -> usize {
|
||||
self.as_bytes().offset_from(&start.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Offset<<&'a BStr as Stream>::Checkpoint> for &'a BStr {
|
||||
#[inline(always)]
|
||||
fn offset_from(&self, other: &<&'a BStr as Stream>::Checkpoint) -> usize {
|
||||
self.checkpoint().offset_from(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsBStr for &BStr {
|
||||
#[inline(always)]
|
||||
fn as_bstr(&self) -> &[u8] {
|
||||
(*self).as_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Compare<T> for &'a BStr
|
||||
where
|
||||
&'a [u8]: Compare<T>,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn compare(&self, t: T) -> CompareResult {
|
||||
let bytes = (*self).as_bytes();
|
||||
bytes.compare(t)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'i, S> FindSlice<S> for &'i BStr
|
||||
where
|
||||
&'i [u8]: FindSlice<S>,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn find_slice(&self, substr: S) -> Option<core::ops::Range<usize>> {
|
||||
let bytes = (*self).as_bytes();
|
||||
let offset = bytes.find_slice(substr);
|
||||
offset
|
||||
}
|
||||
}
|
||||
|
||||
impl UpdateSlice for &BStr {
|
||||
#[inline(always)]
|
||||
fn update_slice(self, inner: Self::Slice) -> Self {
|
||||
BStr::new(inner)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl fmt::Display for BStr {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
alloc::string::String::from_utf8_lossy(self.as_bytes()).fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for BStr {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if !f.alternate() {
|
||||
write!(f, "\"")?;
|
||||
}
|
||||
for byte in self.as_bytes() {
|
||||
let c = *byte as char;
|
||||
write!(f, "{}", c.escape_debug())?;
|
||||
}
|
||||
if !f.alternate() {
|
||||
write!(f, "\"")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Deref for BStr {
|
||||
type Target = [u8];
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &[u8] {
|
||||
self.as_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Index<usize> for BStr {
|
||||
type Output = u8;
|
||||
|
||||
#[inline]
|
||||
fn index(&self, idx: usize) -> &u8 {
|
||||
&self.as_bytes()[idx]
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Index<ops::RangeFull> for BStr {
|
||||
type Output = BStr;
|
||||
|
||||
#[inline]
|
||||
fn index(&self, _: ops::RangeFull) -> &BStr {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Index<ops::Range<usize>> for BStr {
|
||||
type Output = BStr;
|
||||
|
||||
#[inline]
|
||||
fn index(&self, r: ops::Range<usize>) -> &BStr {
|
||||
BStr::new(&self.as_bytes()[r.start..r.end])
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Index<ops::RangeInclusive<usize>> for BStr {
|
||||
type Output = BStr;
|
||||
|
||||
#[inline]
|
||||
fn index(&self, r: ops::RangeInclusive<usize>) -> &BStr {
|
||||
BStr::new(&self.as_bytes()[*r.start()..=*r.end()])
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Index<ops::RangeFrom<usize>> for BStr {
|
||||
type Output = BStr;
|
||||
|
||||
#[inline]
|
||||
fn index(&self, r: ops::RangeFrom<usize>) -> &BStr {
|
||||
BStr::new(&self.as_bytes()[r.start..])
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Index<ops::RangeTo<usize>> for BStr {
|
||||
type Output = BStr;
|
||||
|
||||
#[inline]
|
||||
fn index(&self, r: ops::RangeTo<usize>) -> &BStr {
|
||||
BStr::new(&self.as_bytes()[..r.end])
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Index<ops::RangeToInclusive<usize>> for BStr {
|
||||
type Output = BStr;
|
||||
|
||||
#[inline]
|
||||
fn index(&self, r: ops::RangeToInclusive<usize>) -> &BStr {
|
||||
BStr::new(&self.as_bytes()[..=r.end])
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for BStr {
|
||||
#[inline]
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.as_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<BStr> for [u8] {
|
||||
#[inline]
|
||||
fn as_ref(&self) -> &BStr {
|
||||
BStr::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<BStr> for str {
|
||||
#[inline]
|
||||
fn as_ref(&self) -> &BStr {
|
||||
BStr::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl alloc::borrow::ToOwned for BStr {
|
||||
type Owned = alloc::vec::Vec<u8>;
|
||||
|
||||
#[inline]
|
||||
fn to_owned(&self) -> Self::Owned {
|
||||
alloc::vec::Vec::from(self.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl core::borrow::Borrow<BStr> for alloc::vec::Vec<u8> {
|
||||
#[inline]
|
||||
fn borrow(&self) -> &BStr {
|
||||
BStr::from_bytes(self.as_slice())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Default for &'a BStr {
|
||||
fn default() -> &'a BStr {
|
||||
BStr::new(b"")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a [u8]> for &'a BStr {
|
||||
#[inline]
|
||||
fn from(s: &'a [u8]) -> &'a BStr {
|
||||
BStr::new(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a BStr> for &'a [u8] {
|
||||
#[inline]
|
||||
fn from(s: &'a BStr) -> &'a [u8] {
|
||||
BStr::as_bytes(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for &'a BStr {
|
||||
#[inline]
|
||||
fn from(s: &'a str) -> &'a BStr {
|
||||
BStr::new(s.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for BStr {}
|
||||
|
||||
impl PartialEq<BStr> for BStr {
|
||||
#[inline]
|
||||
fn eq(&self, other: &BStr) -> bool {
|
||||
self.as_bytes() == other.as_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl_partial_eq!(BStr, [u8]);
|
||||
impl_partial_eq!(BStr, &'a [u8]);
|
||||
impl_partial_eq!(BStr, str);
|
||||
impl_partial_eq!(BStr, &'a str);
|
||||
|
||||
impl PartialOrd for BStr {
|
||||
#[inline]
|
||||
fn partial_cmp(&self, other: &BStr) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for BStr {
|
||||
#[inline]
|
||||
fn cmp(&self, other: &BStr) -> Ordering {
|
||||
Ord::cmp(self.as_bytes(), other.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl_partial_ord!(BStr, [u8]);
|
||||
impl_partial_ord!(BStr, &'a [u8]);
|
||||
impl_partial_ord!(BStr, str);
|
||||
impl_partial_ord!(BStr, &'a str);
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::stream::BStr;
|
||||
|
||||
#[test]
|
||||
fn partial_eq_bstr_byte_slice() {
|
||||
let input = b"foo".as_slice();
|
||||
let actual = BStr::new(input);
|
||||
assert!(actual == input);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn partial_eq_byte_slice_bstr() {
|
||||
let input = b"foo".as_slice();
|
||||
let actual = BStr::new(input);
|
||||
assert!(input == actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn partial_eq_bstr_str() {
|
||||
let input = "foo";
|
||||
let actual = BStr::new(input);
|
||||
assert!(actual == input);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn partial_eq_str_bstr() {
|
||||
let input = "foo";
|
||||
let actual = BStr::new(input);
|
||||
assert!(input == actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn partial_ord_bstr_byte_slice() {
|
||||
let input = b"foo".as_slice();
|
||||
let actual = BStr::new(input);
|
||||
assert!(actual.partial_cmp(input) == Some(core::cmp::Ordering::Equal));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn partial_ord_byte_slice_bstr() {
|
||||
let input = b"foo".as_slice();
|
||||
let actual = BStr::new(input);
|
||||
assert!(input.partial_cmp(actual) == Some(core::cmp::Ordering::Equal));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn partial_ord_bstr_str() {
|
||||
let input = "foo";
|
||||
let actual = BStr::new(input);
|
||||
assert!(actual.partial_cmp(input) == Some(core::cmp::Ordering::Equal));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn partial_ord_str_bstr() {
|
||||
let input = "foo";
|
||||
let actual = BStr::new(input);
|
||||
assert!(input.partial_cmp(actual) == Some(core::cmp::Ordering::Equal));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "std"))]
|
||||
mod display {
|
||||
use crate::stream::BStr;
|
||||
|
||||
#[test]
|
||||
fn clean() {
|
||||
assert_eq!(&format!("{}", BStr::new(b"abc")), "abc");
|
||||
assert_eq!(&format!("{}", BStr::new(b"\xf0\x28\x8c\xbc")), "�(��");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "std"))]
|
||||
mod debug {
|
||||
use crate::stream::BStr;
|
||||
use crate::stream::Stream as _;
|
||||
use snapbox::assert_data_eq;
|
||||
use snapbox::str;
|
||||
|
||||
#[test]
|
||||
fn test_debug() {
|
||||
let input = BStr::new(b"abc");
|
||||
let expected = str![[r#""abc""#]];
|
||||
assert_data_eq!(&format!("{input:?}"), expected);
|
||||
|
||||
let input = BStr::new(b"\0\0\0 ftypisom\0\0\x02\0isomiso2avc1mp");
|
||||
let expected = str![[r#""/0/0/0 ftypisom/0/0/u{2}/0isomiso2avc1mp""#]];
|
||||
assert_data_eq!(&format!("{input:?}"), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pretty_debug() {
|
||||
let input = BStr::new(b"abc");
|
||||
let expected = str!["abc"];
|
||||
assert_data_eq!(&format!("{input:#?}"), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trace() {
|
||||
let input = BStr::new(b"abc");
|
||||
let expected = str!["abc"];
|
||||
assert_data_eq!(
|
||||
crate::util::from_fn(|f| input.trace(f)).to_string(),
|
||||
expected
|
||||
);
|
||||
}
|
||||
}
|
||||
Vendored
+562
@@ -0,0 +1,562 @@
|
||||
use core::num::NonZeroUsize;
|
||||
|
||||
use crate::stream::AsBytes;
|
||||
use crate::stream::Checkpoint;
|
||||
use crate::stream::Compare;
|
||||
use crate::stream::CompareResult;
|
||||
use crate::stream::FindSlice;
|
||||
use crate::stream::Needed;
|
||||
use crate::stream::Offset;
|
||||
#[cfg(feature = "unstable-recover")]
|
||||
#[cfg(feature = "std")]
|
||||
use crate::stream::Recover;
|
||||
use crate::stream::SliceLen;
|
||||
use crate::stream::Stream;
|
||||
use crate::stream::StreamIsPartial;
|
||||
use crate::stream::UpdateSlice;
|
||||
use core::iter::{Cloned, Enumerate};
|
||||
use core::slice::Iter;
|
||||
use core::{cmp::Ordering, fmt, ops};
|
||||
|
||||
/// Improved `Debug` experience for `&[u8]` byte streams
|
||||
#[allow(clippy::derived_hash_with_manual_eq)]
|
||||
#[derive(Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct Bytes([u8]);
|
||||
|
||||
impl Bytes {
|
||||
/// Make a stream out of a byte slice-like.
|
||||
#[inline]
|
||||
pub fn new<B: ?Sized + AsRef<[u8]>>(bytes: &B) -> &Self {
|
||||
Self::from_bytes(bytes.as_ref())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn from_bytes(slice: &[u8]) -> &Self {
|
||||
unsafe { core::mem::transmute(slice) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn as_bytes(&self) -> &[u8] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl SliceLen for &Bytes {
|
||||
#[inline(always)]
|
||||
fn slice_len(&self) -> usize {
|
||||
self.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'i> Stream for &'i Bytes {
|
||||
type Token = u8;
|
||||
type Slice = &'i [u8];
|
||||
|
||||
type IterOffsets = Enumerate<Cloned<Iter<'i, u8>>>;
|
||||
|
||||
type Checkpoint = Checkpoint<Self, Self>;
|
||||
|
||||
#[inline(always)]
|
||||
fn iter_offsets(&self) -> Self::IterOffsets {
|
||||
self.iter().cloned().enumerate()
|
||||
}
|
||||
#[inline(always)]
|
||||
fn eof_offset(&self) -> usize {
|
||||
self.len()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn next_token(&mut self) -> Option<Self::Token> {
|
||||
if self.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let token = self[0];
|
||||
*self = &self[1..];
|
||||
Some(token)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn peek_token(&self) -> Option<Self::Token> {
|
||||
if self.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(self[0])
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn offset_for<P>(&self, predicate: P) -> Option<usize>
|
||||
where
|
||||
P: Fn(Self::Token) -> bool,
|
||||
{
|
||||
self.iter().position(|b| predicate(*b))
|
||||
}
|
||||
#[inline(always)]
|
||||
fn offset_at(&self, tokens: usize) -> Result<usize, Needed> {
|
||||
if let Some(needed) = tokens.checked_sub(self.len()).and_then(NonZeroUsize::new) {
|
||||
Err(Needed::Size(needed))
|
||||
} else {
|
||||
Ok(tokens)
|
||||
}
|
||||
}
|
||||
#[inline(always)]
|
||||
fn next_slice(&mut self, offset: usize) -> Self::Slice {
|
||||
let (slice, next) = self.0.split_at(offset);
|
||||
*self = Bytes::from_bytes(next);
|
||||
slice
|
||||
}
|
||||
#[inline(always)]
|
||||
unsafe fn next_slice_unchecked(&mut self, offset: usize) -> Self::Slice {
|
||||
#[cfg(debug_assertions)]
|
||||
self.peek_slice(offset);
|
||||
|
||||
// SAFETY: `Stream::next_slice_unchecked` requires `offset` to be in bounds
|
||||
let slice = unsafe { self.0.get_unchecked(..offset) };
|
||||
// SAFETY: `Stream::next_slice_unchecked` requires `offset` to be in bounds
|
||||
let next = unsafe { self.0.get_unchecked(offset..) };
|
||||
*self = Bytes::from_bytes(next);
|
||||
slice
|
||||
}
|
||||
#[inline(always)]
|
||||
fn peek_slice(&self, offset: usize) -> Self::Slice {
|
||||
&self[..offset]
|
||||
}
|
||||
#[inline(always)]
|
||||
unsafe fn peek_slice_unchecked(&self, offset: usize) -> Self::Slice {
|
||||
#[cfg(debug_assertions)]
|
||||
self.peek_slice(offset);
|
||||
|
||||
// SAFETY: `Stream::next_slice_unchecked` requires `offset` to be in bounds
|
||||
let slice = unsafe { self.0.get_unchecked(..offset) };
|
||||
slice
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn checkpoint(&self) -> Self::Checkpoint {
|
||||
Checkpoint::<_, Self>::new(*self)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn reset(&mut self, checkpoint: &Self::Checkpoint) {
|
||||
*self = checkpoint.inner;
|
||||
}
|
||||
|
||||
fn trace(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "{self:#?}")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable-recover")]
|
||||
#[cfg(feature = "std")]
|
||||
impl<E> Recover<E> for &Bytes {
|
||||
#[inline(always)]
|
||||
fn record_err(
|
||||
&mut self,
|
||||
_token_start: &Self::Checkpoint,
|
||||
_err_start: &Self::Checkpoint,
|
||||
err: E,
|
||||
) -> Result<(), E> {
|
||||
Err(err)
|
||||
}
|
||||
|
||||
/// Report whether the [`Stream`] can save off errors for recovery
|
||||
#[inline(always)]
|
||||
fn is_recovery_supported() -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl StreamIsPartial for &Bytes {
|
||||
type PartialState = ();
|
||||
|
||||
#[inline]
|
||||
fn complete(&mut self) -> Self::PartialState {
|
||||
// Already complete
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn restore_partial(&mut self, _state: Self::PartialState) {}
|
||||
|
||||
#[inline(always)]
|
||||
fn is_partial_supported() -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl Offset for &Bytes {
|
||||
#[inline(always)]
|
||||
fn offset_from(&self, start: &Self) -> usize {
|
||||
self.as_bytes().offset_from(&start.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Offset<<&'a Bytes as Stream>::Checkpoint> for &'a Bytes {
|
||||
#[inline(always)]
|
||||
fn offset_from(&self, other: &<&'a Bytes as Stream>::Checkpoint) -> usize {
|
||||
self.checkpoint().offset_from(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsBytes for &Bytes {
|
||||
#[inline(always)]
|
||||
fn as_bytes(&self) -> &[u8] {
|
||||
(*self).as_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Compare<T> for &'a Bytes
|
||||
where
|
||||
&'a [u8]: Compare<T>,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn compare(&self, t: T) -> CompareResult {
|
||||
let bytes = (*self).as_bytes();
|
||||
bytes.compare(t)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'i, S> FindSlice<S> for &'i Bytes
|
||||
where
|
||||
&'i [u8]: FindSlice<S>,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn find_slice(&self, substr: S) -> Option<core::ops::Range<usize>> {
|
||||
let bytes = (*self).as_bytes();
|
||||
let offset = bytes.find_slice(substr);
|
||||
offset
|
||||
}
|
||||
}
|
||||
|
||||
impl UpdateSlice for &Bytes {
|
||||
#[inline(always)]
|
||||
fn update_slice(self, inner: Self::Slice) -> Self {
|
||||
Bytes::new(inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Bytes {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
<Self as fmt::UpperHex>::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Bytes {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
<Self as fmt::UpperHex>::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::LowerHex for Bytes {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
for byte in self.as_bytes() {
|
||||
write!(f, "{byte:0>2x}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::UpperHex for Bytes {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
for (i, byte) in self.as_bytes().iter().enumerate() {
|
||||
if 0 < i {
|
||||
let absolute = (self.as_bytes().as_ptr() as usize) + i;
|
||||
if f.alternate() && absolute != 0 && absolute % 4 == 0 {
|
||||
write!(f, "_")?;
|
||||
}
|
||||
}
|
||||
write!(f, "{byte:0>2X}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Deref for Bytes {
|
||||
type Target = [u8];
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &[u8] {
|
||||
self.as_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Index<usize> for Bytes {
|
||||
type Output = u8;
|
||||
|
||||
#[inline]
|
||||
fn index(&self, idx: usize) -> &u8 {
|
||||
&self.as_bytes()[idx]
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Index<ops::RangeFull> for Bytes {
|
||||
type Output = Bytes;
|
||||
|
||||
#[inline]
|
||||
fn index(&self, _: ops::RangeFull) -> &Bytes {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Index<ops::Range<usize>> for Bytes {
|
||||
type Output = Bytes;
|
||||
|
||||
#[inline]
|
||||
fn index(&self, r: ops::Range<usize>) -> &Bytes {
|
||||
Bytes::new(&self.as_bytes()[r.start..r.end])
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Index<ops::RangeInclusive<usize>> for Bytes {
|
||||
type Output = Bytes;
|
||||
|
||||
#[inline]
|
||||
fn index(&self, r: ops::RangeInclusive<usize>) -> &Bytes {
|
||||
Bytes::new(&self.as_bytes()[*r.start()..=*r.end()])
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Index<ops::RangeFrom<usize>> for Bytes {
|
||||
type Output = Bytes;
|
||||
|
||||
#[inline]
|
||||
fn index(&self, r: ops::RangeFrom<usize>) -> &Bytes {
|
||||
Bytes::new(&self.as_bytes()[r.start..])
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Index<ops::RangeTo<usize>> for Bytes {
|
||||
type Output = Bytes;
|
||||
|
||||
#[inline]
|
||||
fn index(&self, r: ops::RangeTo<usize>) -> &Bytes {
|
||||
Bytes::new(&self.as_bytes()[..r.end])
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Index<ops::RangeToInclusive<usize>> for Bytes {
|
||||
type Output = Bytes;
|
||||
|
||||
#[inline]
|
||||
fn index(&self, r: ops::RangeToInclusive<usize>) -> &Bytes {
|
||||
Bytes::new(&self.as_bytes()[..=r.end])
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for Bytes {
|
||||
#[inline]
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.as_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<Bytes> for [u8] {
|
||||
#[inline]
|
||||
fn as_ref(&self) -> &Bytes {
|
||||
Bytes::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<Bytes> for str {
|
||||
#[inline]
|
||||
fn as_ref(&self) -> &Bytes {
|
||||
Bytes::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl alloc::borrow::ToOwned for Bytes {
|
||||
type Owned = alloc::vec::Vec<u8>;
|
||||
|
||||
#[inline]
|
||||
fn to_owned(&self) -> Self::Owned {
|
||||
alloc::vec::Vec::from(self.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl core::borrow::Borrow<Bytes> for alloc::vec::Vec<u8> {
|
||||
#[inline]
|
||||
fn borrow(&self) -> &Bytes {
|
||||
Bytes::from_bytes(self.as_slice())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Default for &'a Bytes {
|
||||
fn default() -> &'a Bytes {
|
||||
Bytes::new(b"")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a [u8]> for &'a Bytes {
|
||||
#[inline]
|
||||
fn from(s: &'a [u8]) -> &'a Bytes {
|
||||
Bytes::new(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Bytes> for &'a [u8] {
|
||||
#[inline]
|
||||
fn from(s: &'a Bytes) -> &'a [u8] {
|
||||
Bytes::as_bytes(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for &'a Bytes {
|
||||
#[inline]
|
||||
fn from(s: &'a str) -> &'a Bytes {
|
||||
Bytes::new(s.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Bytes {}
|
||||
|
||||
impl PartialEq<Bytes> for Bytes {
|
||||
#[inline]
|
||||
fn eq(&self, other: &Bytes) -> bool {
|
||||
self.as_bytes() == other.as_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl_partial_eq!(Bytes, [u8]);
|
||||
impl_partial_eq!(Bytes, &'a [u8]);
|
||||
impl_partial_eq!(Bytes, str);
|
||||
impl_partial_eq!(Bytes, &'a str);
|
||||
|
||||
impl PartialOrd for Bytes {
|
||||
#[inline]
|
||||
fn partial_cmp(&self, other: &Bytes) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Bytes {
|
||||
#[inline]
|
||||
fn cmp(&self, other: &Bytes) -> Ordering {
|
||||
Ord::cmp(self.as_bytes(), other.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl_partial_ord!(Bytes, [u8]);
|
||||
impl_partial_ord!(Bytes, &'a [u8]);
|
||||
impl_partial_ord!(Bytes, str);
|
||||
impl_partial_ord!(Bytes, &'a str);
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::stream::Bytes;
|
||||
|
||||
#[test]
|
||||
fn partial_eq_bytes_byte_slice() {
|
||||
let input = b"foo".as_slice();
|
||||
let actual = Bytes::new(input);
|
||||
assert!(actual == input);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn partial_eq_byte_slice_bytes() {
|
||||
let input = b"foo".as_slice();
|
||||
let actual = Bytes::new(input);
|
||||
assert!(input == actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn partial_eq_bytes_str() {
|
||||
let input = "foo";
|
||||
let actual = Bytes::new(input);
|
||||
assert!(actual == input);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn partial_eq_str_bytes() {
|
||||
let input = "foo";
|
||||
let actual = Bytes::new(input);
|
||||
assert!(input == actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn partial_ord_bytes_byte_slice() {
|
||||
let input = b"foo".as_slice();
|
||||
let actual = Bytes::new(input);
|
||||
assert!(actual.partial_cmp(input) == Some(core::cmp::Ordering::Equal));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn partial_ord_byte_slice_bytes() {
|
||||
let input = b"foo".as_slice();
|
||||
let actual = Bytes::new(input);
|
||||
assert!(input.partial_cmp(actual) == Some(core::cmp::Ordering::Equal));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn partial_ord_bytes_str() {
|
||||
let input = "foo";
|
||||
let actual = Bytes::new(input);
|
||||
assert!(actual.partial_cmp(input) == Some(core::cmp::Ordering::Equal));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn partial_ord_str_bytes() {
|
||||
let input = "foo";
|
||||
let actual = Bytes::new(input);
|
||||
assert!(input.partial_cmp(actual) == Some(core::cmp::Ordering::Equal));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "std"))]
|
||||
mod display {
|
||||
use crate::stream::Bytes;
|
||||
|
||||
#[test]
|
||||
fn clean() {
|
||||
assert_eq!(&format!("{}", Bytes::new(b"abc")), "616263");
|
||||
assert_eq!(&format!("{}", Bytes::new(b"\xf0\x28\x8c\xbc")), "F0288CBC");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "std"))]
|
||||
mod debug {
|
||||
use crate::stream::Bytes;
|
||||
use crate::stream::Stream as _;
|
||||
use snapbox::assert_data_eq;
|
||||
use snapbox::str;
|
||||
|
||||
#[test]
|
||||
fn test_debug() {
|
||||
let input = Bytes::new(b"\0\0\0 ftypisom\0\0\x02\0isomiso2avc1mp");
|
||||
let expected = str!["000000206674797069736F6D0000020069736F6D69736F32617663316D70"];
|
||||
assert_data_eq!(&format!("{input:?}"), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pretty_debug() {
|
||||
let input = Bytes::new(b"\0\0\0 ftypisom\0\0\x02\0isomiso2avc1mp");
|
||||
let expected = str!["000000206674797069736F6D0000020069736F6D69736F32617663316D70"];
|
||||
assert_data_eq!(&format!("{input:#?}").replace('_', ""), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trace() {
|
||||
let input = Bytes::new(b"\0\0\0 ftypisom\0\0\x02\0isomiso2avc1mp");
|
||||
let expected = str!["000000206674797069736F6D0000020069736F6D69736F32617663316D70"];
|
||||
assert_data_eq!(
|
||||
crate::util::from_fn(|f| input.trace(f))
|
||||
.to_string()
|
||||
.replace('_', ""),
|
||||
expected
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sliced() {
|
||||
// Output can change from run-to-run
|
||||
let total = Bytes::new(b"12345678901234567890");
|
||||
let _ = format!("{total:#?}");
|
||||
let _ = format!("{:#?}", &total[1..]);
|
||||
let _ = format!("{:#?}", &total[10..]);
|
||||
}
|
||||
}
|
||||
Vendored
+320
@@ -0,0 +1,320 @@
|
||||
use crate::stream::AsBStr;
|
||||
use crate::stream::AsBytes;
|
||||
use crate::stream::Checkpoint;
|
||||
use crate::stream::Compare;
|
||||
use crate::stream::CompareResult;
|
||||
use crate::stream::FindSlice;
|
||||
use crate::stream::Location;
|
||||
use crate::stream::Needed;
|
||||
use crate::stream::Offset;
|
||||
#[cfg(feature = "unstable-recover")]
|
||||
#[cfg(feature = "std")]
|
||||
use crate::stream::Recover;
|
||||
use crate::stream::SliceLen;
|
||||
use crate::stream::Stream;
|
||||
use crate::stream::StreamIsPartial;
|
||||
use crate::stream::UpdateSlice;
|
||||
|
||||
/// Allow collecting the span of a parsed token within a slice
|
||||
///
|
||||
/// Converting byte offsets to line or column numbers is left up to the user, as computing column
|
||||
/// numbers requires domain knowledge (are columns byte-based, codepoint-based, or grapheme-based?)
|
||||
/// and O(n) iteration over the input to determine codepoint and line boundaries.
|
||||
///
|
||||
/// [The `line-span` crate](https://docs.rs/line-span/latest/line_span/) can help with converting
|
||||
/// byte offsets to line numbers.
|
||||
///
|
||||
/// See [`Parser::span`][crate::Parser::span] and [`Parser::with_span`][crate::Parser::with_span] for more details
|
||||
#[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[doc(alias = "LocatingSliceSpan")]
|
||||
#[doc(alias = "Located")]
|
||||
pub struct LocatingSlice<I> {
|
||||
initial: I,
|
||||
input: I,
|
||||
}
|
||||
|
||||
impl<I> LocatingSlice<I>
|
||||
where
|
||||
I: Clone + Offset,
|
||||
{
|
||||
/// Wrap another Stream with span tracking
|
||||
pub fn new(input: I) -> Self {
|
||||
let initial = input.clone();
|
||||
Self { initial, input }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn previous_token_end(&self) -> usize {
|
||||
// Assumptions:
|
||||
// - Index offsets is sufficient
|
||||
// - Tokens are continuous
|
||||
self.input.offset_from(&self.initial)
|
||||
}
|
||||
#[inline]
|
||||
fn current_token_start(&self) -> usize {
|
||||
// Assumptions:
|
||||
// - Index offsets is sufficient
|
||||
self.input.offset_from(&self.initial)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> LocatingSlice<I>
|
||||
where
|
||||
I: Clone + Stream + Offset,
|
||||
{
|
||||
/// Reset the stream to the start
|
||||
///
|
||||
/// This is useful for formats that encode a graph with addresses relative to the start of the
|
||||
/// input.
|
||||
#[doc(alias = "fseek")]
|
||||
#[inline]
|
||||
pub fn reset_to_start(&mut self) {
|
||||
let start = self.initial.checkpoint();
|
||||
self.input.reset(&start);
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> AsRef<I> for LocatingSlice<I> {
|
||||
#[inline(always)]
|
||||
fn as_ref(&self) -> &I {
|
||||
&self.input
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: core::fmt::Debug> core::fmt::Debug for LocatingSlice<I> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
self.input.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> core::ops::Deref for LocatingSlice<I> {
|
||||
type Target = I;
|
||||
|
||||
#[inline(always)]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.input
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: core::fmt::Display> core::fmt::Display for LocatingSlice<I> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
self.input.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> SliceLen for LocatingSlice<I>
|
||||
where
|
||||
I: SliceLen,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn slice_len(&self) -> usize {
|
||||
self.input.slice_len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: Stream> Stream for LocatingSlice<I> {
|
||||
type Token = <I as Stream>::Token;
|
||||
type Slice = <I as Stream>::Slice;
|
||||
|
||||
type IterOffsets = <I as Stream>::IterOffsets;
|
||||
|
||||
type Checkpoint = Checkpoint<I::Checkpoint, Self>;
|
||||
|
||||
#[inline(always)]
|
||||
fn iter_offsets(&self) -> Self::IterOffsets {
|
||||
self.input.iter_offsets()
|
||||
}
|
||||
#[inline(always)]
|
||||
fn eof_offset(&self) -> usize {
|
||||
self.input.eof_offset()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn next_token(&mut self) -> Option<Self::Token> {
|
||||
self.input.next_token()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn peek_token(&self) -> Option<Self::Token> {
|
||||
self.input.peek_token()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn offset_for<P>(&self, predicate: P) -> Option<usize>
|
||||
where
|
||||
P: Fn(Self::Token) -> bool,
|
||||
{
|
||||
self.input.offset_for(predicate)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn offset_at(&self, tokens: usize) -> Result<usize, Needed> {
|
||||
self.input.offset_at(tokens)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn next_slice(&mut self, offset: usize) -> Self::Slice {
|
||||
self.input.next_slice(offset)
|
||||
}
|
||||
#[inline(always)]
|
||||
unsafe fn next_slice_unchecked(&mut self, offset: usize) -> Self::Slice {
|
||||
// SAFETY: Passing up invariants
|
||||
unsafe { self.input.next_slice_unchecked(offset) }
|
||||
}
|
||||
#[inline(always)]
|
||||
fn peek_slice(&self, offset: usize) -> Self::Slice {
|
||||
self.input.peek_slice(offset)
|
||||
}
|
||||
#[inline(always)]
|
||||
unsafe fn peek_slice_unchecked(&self, offset: usize) -> Self::Slice {
|
||||
// SAFETY: Passing up invariants
|
||||
unsafe { self.input.peek_slice_unchecked(offset) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn checkpoint(&self) -> Self::Checkpoint {
|
||||
Checkpoint::<_, Self>::new(self.input.checkpoint())
|
||||
}
|
||||
#[inline(always)]
|
||||
fn reset(&mut self, checkpoint: &Self::Checkpoint) {
|
||||
self.input.reset(&checkpoint.inner);
|
||||
}
|
||||
|
||||
fn trace(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
self.input.trace(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Location for LocatingSlice<I>
|
||||
where
|
||||
I: Clone + Offset,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn previous_token_end(&self) -> usize {
|
||||
self.previous_token_end()
|
||||
}
|
||||
#[inline(always)]
|
||||
fn current_token_start(&self) -> usize {
|
||||
self.current_token_start()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable-recover")]
|
||||
#[cfg(feature = "std")]
|
||||
impl<I, E> Recover<E> for LocatingSlice<I>
|
||||
where
|
||||
I: Recover<E>,
|
||||
I: Stream,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn record_err(
|
||||
&mut self,
|
||||
_token_start: &Self::Checkpoint,
|
||||
_err_start: &Self::Checkpoint,
|
||||
err: E,
|
||||
) -> Result<(), E> {
|
||||
Err(err)
|
||||
}
|
||||
|
||||
/// Report whether the [`Stream`] can save off errors for recovery
|
||||
#[inline(always)]
|
||||
fn is_recovery_supported() -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> StreamIsPartial for LocatingSlice<I>
|
||||
where
|
||||
I: StreamIsPartial,
|
||||
{
|
||||
type PartialState = I::PartialState;
|
||||
|
||||
#[inline]
|
||||
fn complete(&mut self) -> Self::PartialState {
|
||||
self.input.complete()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn restore_partial(&mut self, state: Self::PartialState) {
|
||||
self.input.restore_partial(state);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn is_partial_supported() -> bool {
|
||||
I::is_partial_supported()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn is_partial(&self) -> bool {
|
||||
self.input.is_partial()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Offset for LocatingSlice<I>
|
||||
where
|
||||
I: Stream,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn offset_from(&self, other: &Self) -> usize {
|
||||
self.offset_from(&other.checkpoint())
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Offset<<LocatingSlice<I> as Stream>::Checkpoint> for LocatingSlice<I>
|
||||
where
|
||||
I: Stream,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn offset_from(&self, other: &<LocatingSlice<I> as Stream>::Checkpoint) -> usize {
|
||||
self.checkpoint().offset_from(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> AsBytes for LocatingSlice<I>
|
||||
where
|
||||
I: AsBytes,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn as_bytes(&self) -> &[u8] {
|
||||
self.input.as_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> AsBStr for LocatingSlice<I>
|
||||
where
|
||||
I: AsBStr,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn as_bstr(&self) -> &[u8] {
|
||||
self.input.as_bstr()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, U> Compare<U> for LocatingSlice<I>
|
||||
where
|
||||
I: Compare<U>,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn compare(&self, other: U) -> CompareResult {
|
||||
self.input.compare(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, T> FindSlice<T> for LocatingSlice<I>
|
||||
where
|
||||
I: FindSlice<T>,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn find_slice(&self, substr: T) -> Option<core::ops::Range<usize>> {
|
||||
self.input.find_slice(substr)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> UpdateSlice for LocatingSlice<I>
|
||||
where
|
||||
I: UpdateSlice,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn update_slice(mut self, inner: Self::Slice) -> Self {
|
||||
self.input = I::update_slice(self.input, inner);
|
||||
self
|
||||
}
|
||||
}
|
||||
Vendored
+2011
File diff suppressed because it is too large.
Load diff
Vendored
+355
@@ -0,0 +1,355 @@
|
||||
use crate::stream::AsBStr;
|
||||
use crate::stream::AsBytes;
|
||||
use crate::stream::Checkpoint;
|
||||
use crate::stream::Compare;
|
||||
use crate::stream::CompareResult;
|
||||
use crate::stream::FindSlice;
|
||||
use crate::stream::Location;
|
||||
use crate::stream::Needed;
|
||||
use crate::stream::Offset;
|
||||
#[cfg(feature = "unstable-recover")]
|
||||
#[cfg(feature = "std")]
|
||||
use crate::stream::Recover;
|
||||
use crate::stream::SliceLen;
|
||||
use crate::stream::Stream;
|
||||
use crate::stream::StreamIsPartial;
|
||||
use crate::stream::UpdateSlice;
|
||||
|
||||
/// Mark the input as a partial buffer for streaming input.
|
||||
///
|
||||
/// Complete input means that we already have all of the data. This will be the common case with
|
||||
/// small files that can be read entirely to memory.
|
||||
///
|
||||
/// In contrast, streaming input assumes that we might not have all of the data.
|
||||
/// This can happen with some network protocol or large file parsers, where the
|
||||
/// input buffer can be full and need to be resized or refilled.
|
||||
/// - [`ErrMode::Incomplete`][crate::error::ErrMode::Incomplete] will report how much more data is needed.
|
||||
/// - [`Parser::complete_err`][crate::Parser::complete_err] transform
|
||||
/// [`ErrMode::Incomplete`][crate::error::ErrMode::Incomplete] to
|
||||
/// [`ErrMode::Backtrack`][crate::error::ErrMode::Backtrack]
|
||||
///
|
||||
/// See also [`StreamIsPartial`] to tell whether the input supports complete or partial parsing.
|
||||
///
|
||||
/// See also [Special Topics: Parsing Partial Input][crate::_topic::partial].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Here is how it works in practice:
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(feature = "ascii")] {
|
||||
/// # use winnow::{Result, error::ErrMode, error::Needed, error::ContextError, token, ascii, stream::Partial};
|
||||
/// # use winnow::prelude::*;
|
||||
///
|
||||
/// fn take_partial<'s>(i: &mut Partial<&'s [u8]>) -> ModalResult<&'s [u8], ContextError> {
|
||||
/// token::take(4u8).parse_next(i)
|
||||
/// }
|
||||
///
|
||||
/// fn take_complete<'s>(i: &mut &'s [u8]) -> ModalResult<&'s [u8], ContextError> {
|
||||
/// token::take(4u8).parse_next(i)
|
||||
/// }
|
||||
///
|
||||
/// // both parsers will take 4 bytes as expected
|
||||
/// assert_eq!(take_partial.parse_peek(Partial::new(&b"abcde"[..])), Ok((Partial::new(&b"e"[..]), &b"abcd"[..])));
|
||||
/// assert_eq!(take_complete.parse_peek(&b"abcde"[..]), Ok((&b"e"[..], &b"abcd"[..])));
|
||||
///
|
||||
/// // if the input is smaller than 4 bytes, the partial parser
|
||||
/// // will return `Incomplete` to indicate that we need more data
|
||||
/// assert_eq!(take_partial.parse_peek(Partial::new(&b"abc"[..])), Err(ErrMode::Incomplete(Needed::new(1))));
|
||||
///
|
||||
/// // but the complete parser will return an error
|
||||
/// assert!(take_complete.parse_peek(&b"abc"[..]).is_err());
|
||||
///
|
||||
/// // the alpha0 function takes 0 or more alphabetic characters
|
||||
/// fn alpha0_partial<'s>(i: &mut Partial<&'s str>) -> ModalResult<&'s str, ContextError> {
|
||||
/// ascii::alpha0.parse_next(i)
|
||||
/// }
|
||||
///
|
||||
/// fn alpha0_complete<'s>(i: &mut &'s str) -> ModalResult<&'s str, ContextError> {
|
||||
/// ascii::alpha0.parse_next(i)
|
||||
/// }
|
||||
///
|
||||
/// // if there's a clear limit to the taken characters, both parsers work the same way
|
||||
/// assert_eq!(alpha0_partial.parse_peek(Partial::new("abcd;")), Ok((Partial::new(";"), "abcd")));
|
||||
/// assert_eq!(alpha0_complete.parse_peek("abcd;"), Ok((";", "abcd")));
|
||||
///
|
||||
/// // but when there's no limit, the partial version returns `Incomplete`, because it cannot
|
||||
/// // know if more input data should be taken. The whole input could be "abcd;", or
|
||||
/// // "abcde;"
|
||||
/// assert_eq!(alpha0_partial.parse_peek(Partial::new("abcd")), Err(ErrMode::Incomplete(Needed::new(1))));
|
||||
///
|
||||
/// // while the complete version knows that all of the data is there
|
||||
/// assert_eq!(alpha0_complete.parse_peek("abcd"), Ok(("", "abcd")));
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Partial<I> {
|
||||
input: I,
|
||||
partial: bool,
|
||||
}
|
||||
|
||||
impl<I> Partial<I>
|
||||
where
|
||||
I: StreamIsPartial,
|
||||
{
|
||||
/// Create a partial input
|
||||
#[inline]
|
||||
pub fn new(input: I) -> Self {
|
||||
debug_assert!(
|
||||
!I::is_partial_supported(),
|
||||
"`Partial` can only wrap complete sources"
|
||||
);
|
||||
let partial = true;
|
||||
Self { input, partial }
|
||||
}
|
||||
|
||||
/// Extract the original [`Stream`]
|
||||
#[inline(always)]
|
||||
pub fn into_inner(self) -> I {
|
||||
self.input
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Default for Partial<I>
|
||||
where
|
||||
I: Default + StreamIsPartial,
|
||||
{
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self::new(I::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> core::ops::Deref for Partial<I> {
|
||||
type Target = I;
|
||||
|
||||
#[inline(always)]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.input
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: core::fmt::Display> core::fmt::Display for Partial<I> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
self.input.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> SliceLen for Partial<I>
|
||||
where
|
||||
I: SliceLen,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn slice_len(&self) -> usize {
|
||||
self.input.slice_len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: Stream> Stream for Partial<I> {
|
||||
type Token = <I as Stream>::Token;
|
||||
type Slice = <I as Stream>::Slice;
|
||||
|
||||
type IterOffsets = <I as Stream>::IterOffsets;
|
||||
|
||||
type Checkpoint = Checkpoint<I::Checkpoint, Self>;
|
||||
|
||||
#[inline(always)]
|
||||
fn iter_offsets(&self) -> Self::IterOffsets {
|
||||
self.input.iter_offsets()
|
||||
}
|
||||
#[inline(always)]
|
||||
fn eof_offset(&self) -> usize {
|
||||
self.input.eof_offset()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn next_token(&mut self) -> Option<Self::Token> {
|
||||
self.input.next_token()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn peek_token(&self) -> Option<Self::Token> {
|
||||
self.input.peek_token()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn offset_for<P>(&self, predicate: P) -> Option<usize>
|
||||
where
|
||||
P: Fn(Self::Token) -> bool,
|
||||
{
|
||||
self.input.offset_for(predicate)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn offset_at(&self, tokens: usize) -> Result<usize, Needed> {
|
||||
self.input.offset_at(tokens)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn next_slice(&mut self, offset: usize) -> Self::Slice {
|
||||
self.input.next_slice(offset)
|
||||
}
|
||||
#[inline(always)]
|
||||
unsafe fn next_slice_unchecked(&mut self, offset: usize) -> Self::Slice {
|
||||
// SAFETY: Passing up invariants
|
||||
unsafe { self.input.next_slice_unchecked(offset) }
|
||||
}
|
||||
#[inline(always)]
|
||||
fn peek_slice(&self, offset: usize) -> Self::Slice {
|
||||
self.input.peek_slice(offset)
|
||||
}
|
||||
#[inline(always)]
|
||||
unsafe fn peek_slice_unchecked(&self, offset: usize) -> Self::Slice {
|
||||
// SAFETY: Passing up invariants
|
||||
unsafe { self.input.peek_slice_unchecked(offset) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn checkpoint(&self) -> Self::Checkpoint {
|
||||
Checkpoint::<_, Self>::new(self.input.checkpoint())
|
||||
}
|
||||
#[inline(always)]
|
||||
fn reset(&mut self, checkpoint: &Self::Checkpoint) {
|
||||
self.input.reset(&checkpoint.inner);
|
||||
}
|
||||
|
||||
fn trace(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
self.input.trace(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Location for Partial<I>
|
||||
where
|
||||
I: Location,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn previous_token_end(&self) -> usize {
|
||||
self.input.previous_token_end()
|
||||
}
|
||||
#[inline(always)]
|
||||
fn current_token_start(&self) -> usize {
|
||||
self.input.current_token_start()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable-recover")]
|
||||
#[cfg(feature = "std")]
|
||||
impl<I, E> Recover<E> for Partial<I>
|
||||
where
|
||||
I: Recover<E>,
|
||||
I: Stream,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn record_err(
|
||||
&mut self,
|
||||
_token_start: &Self::Checkpoint,
|
||||
_err_start: &Self::Checkpoint,
|
||||
err: E,
|
||||
) -> Result<(), E> {
|
||||
Err(err)
|
||||
}
|
||||
|
||||
/// Report whether the [`Stream`] can save off errors for recovery
|
||||
#[inline(always)]
|
||||
fn is_recovery_supported() -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> StreamIsPartial for Partial<I>
|
||||
where
|
||||
I: StreamIsPartial,
|
||||
{
|
||||
type PartialState = bool;
|
||||
|
||||
#[inline]
|
||||
fn complete(&mut self) -> Self::PartialState {
|
||||
core::mem::replace(&mut self.partial, false)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn restore_partial(&mut self, state: Self::PartialState) {
|
||||
self.partial = state;
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn is_partial_supported() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn is_partial(&self) -> bool {
|
||||
self.partial
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Offset for Partial<I>
|
||||
where
|
||||
I: Stream,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn offset_from(&self, start: &Self) -> usize {
|
||||
self.offset_from(&start.checkpoint())
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Offset<<Partial<I> as Stream>::Checkpoint> for Partial<I>
|
||||
where
|
||||
I: Stream,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn offset_from(&self, other: &<Partial<I> as Stream>::Checkpoint) -> usize {
|
||||
self.checkpoint().offset_from(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> AsBytes for Partial<I>
|
||||
where
|
||||
I: AsBytes,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn as_bytes(&self) -> &[u8] {
|
||||
self.input.as_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> AsBStr for Partial<I>
|
||||
where
|
||||
I: AsBStr,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn as_bstr(&self) -> &[u8] {
|
||||
self.input.as_bstr()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, T> Compare<T> for Partial<I>
|
||||
where
|
||||
I: Compare<T>,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn compare(&self, t: T) -> CompareResult {
|
||||
self.input.compare(t)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, T> FindSlice<T> for Partial<I>
|
||||
where
|
||||
I: FindSlice<T>,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn find_slice(&self, substr: T) -> Option<core::ops::Range<usize>> {
|
||||
self.input.find_slice(substr)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> UpdateSlice for Partial<I>
|
||||
where
|
||||
I: UpdateSlice,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn update_slice(self, inner: Self::Slice) -> Self {
|
||||
Partial {
|
||||
input: I::update_slice(self.input, inner),
|
||||
partial: self.partial,
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+143
@@ -0,0 +1,143 @@
|
||||
/// A range bounded inclusively for counting parses performed
|
||||
///
|
||||
/// This is flexible in what can be converted to a [Range]:
|
||||
/// ```rust
|
||||
/// # #[cfg(all(feature = "std", feature = "parser"))] {
|
||||
/// # use winnow::prelude::*;
|
||||
/// # use winnow::token::any;
|
||||
/// # use winnow::combinator::repeat;
|
||||
/// # fn inner(input: &mut &str) -> ModalResult<char> {
|
||||
/// # any.parse_next(input)
|
||||
/// # }
|
||||
/// # let mut input = "0123456789012345678901234567890123456789";
|
||||
/// # let input = &mut input;
|
||||
/// let parser: Vec<_> = repeat(5, inner).parse_next(input).unwrap();
|
||||
/// # let mut input = "0123456789012345678901234567890123456789";
|
||||
/// # let input = &mut input;
|
||||
/// let parser: Vec<_> = repeat(.., inner).parse_next(input).unwrap();
|
||||
/// # let mut input = "0123456789012345678901234567890123456789";
|
||||
/// # let input = &mut input;
|
||||
/// let parser: Vec<_> = repeat(1.., inner).parse_next(input).unwrap();
|
||||
/// # let mut input = "0123456789012345678901234567890123456789";
|
||||
/// # let input = &mut input;
|
||||
/// let parser: Vec<_> = repeat(5..8, inner).parse_next(input).unwrap();
|
||||
/// # let mut input = "0123456789012345678901234567890123456789";
|
||||
/// # let input = &mut input;
|
||||
/// let parser: Vec<_> = repeat(5..=8, inner).parse_next(input).unwrap();
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(PartialEq, Eq, Copy, Clone)]
|
||||
pub struct Range {
|
||||
pub(crate) start_inclusive: usize,
|
||||
pub(crate) end_inclusive: Option<usize>,
|
||||
}
|
||||
|
||||
impl Range {
|
||||
#[inline(always)]
|
||||
fn raw(start_inclusive: usize, end_inclusive: Option<usize>) -> Self {
|
||||
Self {
|
||||
start_inclusive,
|
||||
end_inclusive,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::RangeBounds<usize> for Range {
|
||||
#[inline(always)]
|
||||
fn start_bound(&self) -> core::ops::Bound<&usize> {
|
||||
core::ops::Bound::Included(&self.start_inclusive)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn end_bound(&self) -> core::ops::Bound<&usize> {
|
||||
if let Some(end_inclusive) = &self.end_inclusive {
|
||||
core::ops::Bound::Included(end_inclusive)
|
||||
} else {
|
||||
core::ops::Bound::Unbounded
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<usize> for Range {
|
||||
#[inline(always)]
|
||||
fn from(fixed: usize) -> Self {
|
||||
(fixed..=fixed).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<core::ops::Range<usize>> for Range {
|
||||
#[inline(always)]
|
||||
fn from(range: core::ops::Range<usize>) -> Self {
|
||||
let start_inclusive = range.start;
|
||||
let end_inclusive = Some(range.end.saturating_sub(1));
|
||||
Self::raw(start_inclusive, end_inclusive)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<core::ops::RangeFull> for Range {
|
||||
#[inline(always)]
|
||||
fn from(_: core::ops::RangeFull) -> Self {
|
||||
let start_inclusive = 0;
|
||||
let end_inclusive = None;
|
||||
Self::raw(start_inclusive, end_inclusive)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<core::ops::RangeFrom<usize>> for Range {
|
||||
#[inline(always)]
|
||||
fn from(range: core::ops::RangeFrom<usize>) -> Self {
|
||||
let start_inclusive = range.start;
|
||||
let end_inclusive = None;
|
||||
Self::raw(start_inclusive, end_inclusive)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<core::ops::RangeTo<usize>> for Range {
|
||||
#[inline(always)]
|
||||
fn from(range: core::ops::RangeTo<usize>) -> Self {
|
||||
let start_inclusive = 0;
|
||||
let end_inclusive = Some(range.end.saturating_sub(1));
|
||||
Self::raw(start_inclusive, end_inclusive)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<core::ops::RangeInclusive<usize>> for Range {
|
||||
#[inline(always)]
|
||||
fn from(range: core::ops::RangeInclusive<usize>) -> Self {
|
||||
let start_inclusive = *range.start();
|
||||
let end_inclusive = Some(*range.end());
|
||||
Self::raw(start_inclusive, end_inclusive)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<core::ops::RangeToInclusive<usize>> for Range {
|
||||
#[inline(always)]
|
||||
fn from(range: core::ops::RangeToInclusive<usize>) -> Self {
|
||||
let start_inclusive = 0;
|
||||
let end_inclusive = Some(range.end);
|
||||
Self::raw(start_inclusive, end_inclusive)
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for Range {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
self.start_inclusive.fmt(f)?;
|
||||
match self.end_inclusive {
|
||||
Some(e) if e == self.start_inclusive => {}
|
||||
Some(e) => {
|
||||
"..=".fmt(f)?;
|
||||
e.fmt(f)?;
|
||||
}
|
||||
None => {
|
||||
"..".fmt(f)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for Range {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "{self}")
|
||||
}
|
||||
}
|
||||
+343
@@ -0,0 +1,343 @@
|
||||
use crate::error::FromRecoverableError;
|
||||
use crate::error::Needed;
|
||||
use crate::stream::AsBStr;
|
||||
use crate::stream::AsBytes;
|
||||
use crate::stream::Checkpoint;
|
||||
use crate::stream::Compare;
|
||||
use crate::stream::CompareResult;
|
||||
use crate::stream::FindSlice;
|
||||
use crate::stream::Location;
|
||||
use crate::stream::Offset;
|
||||
#[cfg(feature = "unstable-recover")]
|
||||
#[cfg(feature = "std")]
|
||||
use crate::stream::Recover;
|
||||
use crate::stream::SliceLen;
|
||||
use crate::stream::Stream;
|
||||
use crate::stream::StreamIsPartial;
|
||||
use crate::stream::UpdateSlice;
|
||||
|
||||
/// Allow recovering from parse errors, capturing them as the parser continues
|
||||
///
|
||||
/// Generally, this will be used indirectly via
|
||||
/// [`RecoverableParser::recoverable_parse`][crate::RecoverableParser::recoverable_parse].
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Recoverable<I, E>
|
||||
where
|
||||
I: Stream,
|
||||
{
|
||||
input: I,
|
||||
errors: Vec<E>,
|
||||
is_recoverable: bool,
|
||||
}
|
||||
|
||||
impl<I, E> Default for Recoverable<I, E>
|
||||
where
|
||||
I: Default + Stream,
|
||||
{
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self::new(I::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, E> Recoverable<I, E>
|
||||
where
|
||||
I: Stream,
|
||||
{
|
||||
/// Track recoverable errors with the stream
|
||||
#[inline]
|
||||
pub fn new(input: I) -> Self {
|
||||
Self {
|
||||
input,
|
||||
errors: Default::default(),
|
||||
is_recoverable: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Act as a normal stream
|
||||
#[inline]
|
||||
pub fn unrecoverable(input: I) -> Self {
|
||||
Self {
|
||||
input,
|
||||
errors: Default::default(),
|
||||
is_recoverable: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Access the current input and errors
|
||||
#[inline]
|
||||
pub fn into_parts(self) -> (I, Vec<E>) {
|
||||
(self.input, self.errors)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, E> AsRef<I> for Recoverable<I, E>
|
||||
where
|
||||
I: Stream,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn as_ref(&self) -> &I {
|
||||
&self.input
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, E> core::ops::Deref for Recoverable<I, E>
|
||||
where
|
||||
I: Stream,
|
||||
{
|
||||
type Target = I;
|
||||
|
||||
#[inline(always)]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.input
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: core::fmt::Display, E> core::fmt::Display for Recoverable<I, E>
|
||||
where
|
||||
I: Stream,
|
||||
{
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
core::fmt::Display::fmt(&self.input, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, E> SliceLen for Recoverable<I, E>
|
||||
where
|
||||
I: SliceLen,
|
||||
I: Stream,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn slice_len(&self) -> usize {
|
||||
self.input.slice_len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, E: core::fmt::Debug> Stream for Recoverable<I, E>
|
||||
where
|
||||
I: Stream,
|
||||
{
|
||||
type Token = <I as Stream>::Token;
|
||||
type Slice = <I as Stream>::Slice;
|
||||
|
||||
type IterOffsets = <I as Stream>::IterOffsets;
|
||||
|
||||
type Checkpoint = Checkpoint<I::Checkpoint, Self>;
|
||||
|
||||
#[inline(always)]
|
||||
fn iter_offsets(&self) -> Self::IterOffsets {
|
||||
self.input.iter_offsets()
|
||||
}
|
||||
#[inline(always)]
|
||||
fn eof_offset(&self) -> usize {
|
||||
self.input.eof_offset()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn next_token(&mut self) -> Option<Self::Token> {
|
||||
self.input.next_token()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn peek_token(&self) -> Option<Self::Token> {
|
||||
self.input.peek_token()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn offset_for<P>(&self, predicate: P) -> Option<usize>
|
||||
where
|
||||
P: Fn(Self::Token) -> bool,
|
||||
{
|
||||
self.input.offset_for(predicate)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn offset_at(&self, tokens: usize) -> Result<usize, Needed> {
|
||||
self.input.offset_at(tokens)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn next_slice(&mut self, offset: usize) -> Self::Slice {
|
||||
self.input.next_slice(offset)
|
||||
}
|
||||
#[inline(always)]
|
||||
unsafe fn next_slice_unchecked(&mut self, offset: usize) -> Self::Slice {
|
||||
// SAFETY: Passing up invariants
|
||||
unsafe { self.input.next_slice_unchecked(offset) }
|
||||
}
|
||||
#[inline(always)]
|
||||
fn peek_slice(&self, offset: usize) -> Self::Slice {
|
||||
self.input.peek_slice(offset)
|
||||
}
|
||||
#[inline(always)]
|
||||
unsafe fn peek_slice_unchecked(&self, offset: usize) -> Self::Slice {
|
||||
// SAFETY: Passing up invariants
|
||||
unsafe { self.input.peek_slice_unchecked(offset) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn checkpoint(&self) -> Self::Checkpoint {
|
||||
Checkpoint::<_, Self>::new(self.input.checkpoint())
|
||||
}
|
||||
#[inline(always)]
|
||||
fn reset(&mut self, checkpoint: &Self::Checkpoint) {
|
||||
self.input.reset(&checkpoint.inner);
|
||||
}
|
||||
|
||||
fn trace(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
self.input.trace(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, E> Location for Recoverable<I, E>
|
||||
where
|
||||
I: Location,
|
||||
I: Stream,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn previous_token_end(&self) -> usize {
|
||||
self.input.previous_token_end()
|
||||
}
|
||||
#[inline(always)]
|
||||
fn current_token_start(&self) -> usize {
|
||||
self.input.current_token_start()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, E, R> Recover<E> for Recoverable<I, R>
|
||||
where
|
||||
I: Stream,
|
||||
R: FromRecoverableError<Self, E>,
|
||||
R: core::fmt::Debug,
|
||||
E: crate::error::ParserError<Self>,
|
||||
{
|
||||
fn record_err(
|
||||
&mut self,
|
||||
token_start: &Self::Checkpoint,
|
||||
err_start: &Self::Checkpoint,
|
||||
err: E,
|
||||
) -> Result<(), E> {
|
||||
if self.is_recoverable {
|
||||
if err.is_incomplete() {
|
||||
Err(err)
|
||||
} else {
|
||||
self.errors
|
||||
.push(R::from_recoverable_error(token_start, err_start, self, err));
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
|
||||
/// Report whether the [`Stream`] can save off errors for recovery
|
||||
#[inline(always)]
|
||||
fn is_recovery_supported() -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, E> StreamIsPartial for Recoverable<I, E>
|
||||
where
|
||||
I: StreamIsPartial,
|
||||
I: Stream,
|
||||
{
|
||||
type PartialState = I::PartialState;
|
||||
|
||||
#[inline]
|
||||
fn complete(&mut self) -> Self::PartialState {
|
||||
self.input.complete()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn restore_partial(&mut self, state: Self::PartialState) {
|
||||
self.input.restore_partial(state);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn is_partial_supported() -> bool {
|
||||
I::is_partial_supported()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn is_partial(&self) -> bool {
|
||||
self.input.is_partial()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, E> Offset for Recoverable<I, E>
|
||||
where
|
||||
I: Stream,
|
||||
E: core::fmt::Debug,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn offset_from(&self, other: &Self) -> usize {
|
||||
self.offset_from(&other.checkpoint())
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, E> Offset<<Recoverable<I, E> as Stream>::Checkpoint> for Recoverable<I, E>
|
||||
where
|
||||
I: Stream,
|
||||
E: core::fmt::Debug,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn offset_from(&self, other: &<Recoverable<I, E> as Stream>::Checkpoint) -> usize {
|
||||
self.checkpoint().offset_from(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, E> AsBytes for Recoverable<I, E>
|
||||
where
|
||||
I: Stream,
|
||||
I: AsBytes,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn as_bytes(&self) -> &[u8] {
|
||||
self.input.as_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, E> AsBStr for Recoverable<I, E>
|
||||
where
|
||||
I: Stream,
|
||||
I: AsBStr,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn as_bstr(&self) -> &[u8] {
|
||||
self.input.as_bstr()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, E, U> Compare<U> for Recoverable<I, E>
|
||||
where
|
||||
I: Stream,
|
||||
I: Compare<U>,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn compare(&self, other: U) -> CompareResult {
|
||||
self.input.compare(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, E, T> FindSlice<T> for Recoverable<I, E>
|
||||
where
|
||||
I: Stream,
|
||||
I: FindSlice<T>,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn find_slice(&self, substr: T) -> Option<core::ops::Range<usize>> {
|
||||
self.input.find_slice(substr)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, E> UpdateSlice for Recoverable<I, E>
|
||||
where
|
||||
I: Stream,
|
||||
I: UpdateSlice,
|
||||
E: core::fmt::Debug,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn update_slice(mut self, inner: Self::Slice) -> Self {
|
||||
self.input = I::update_slice(self.input, inner);
|
||||
self
|
||||
}
|
||||
}
|
||||
Vendored
+308
@@ -0,0 +1,308 @@
|
||||
use crate::stream::AsBStr;
|
||||
use crate::stream::AsBytes;
|
||||
use crate::stream::Checkpoint;
|
||||
use crate::stream::Compare;
|
||||
use crate::stream::CompareResult;
|
||||
use crate::stream::FindSlice;
|
||||
use crate::stream::Location;
|
||||
use crate::stream::Needed;
|
||||
use crate::stream::Offset;
|
||||
#[cfg(feature = "unstable-recover")]
|
||||
#[cfg(feature = "std")]
|
||||
use crate::stream::Recover;
|
||||
use crate::stream::SliceLen;
|
||||
use crate::stream::Stream;
|
||||
use crate::stream::StreamIsPartial;
|
||||
use crate::stream::UpdateSlice;
|
||||
|
||||
/// Thread global state through your parsers
|
||||
///
|
||||
/// Use cases
|
||||
/// - Recursion checks
|
||||
/// - Error recovery
|
||||
/// - Debugging
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # #[cfg(feature = "ascii")] {
|
||||
/// # use std::cell::Cell;
|
||||
/// # use winnow::prelude::*;
|
||||
/// # use winnow::stream::Stateful;
|
||||
/// # use winnow::ascii::alpha1;
|
||||
/// # type Error = ();
|
||||
///
|
||||
/// #[derive(Debug)]
|
||||
/// struct State<'s>(&'s mut u32);
|
||||
///
|
||||
/// impl<'s> State<'s> {
|
||||
/// fn count(&mut self) {
|
||||
/// *self.0 += 1;
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// type Stream<'is> = Stateful<&'is str, State<'is>>;
|
||||
///
|
||||
/// fn word<'s>(i: &mut Stream<'s>) -> ModalResult<&'s str> {
|
||||
/// i.state.count();
|
||||
/// alpha1.parse_next(i)
|
||||
/// }
|
||||
///
|
||||
/// let data = "Hello";
|
||||
/// let mut state = 0;
|
||||
/// let input = Stream { input: data, state: State(&mut state) };
|
||||
/// let output = word.parse(input).unwrap();
|
||||
/// assert_eq!(state, 1);
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||
#[doc(alias = "LocatingSliceSpan")]
|
||||
pub struct Stateful<I, S> {
|
||||
/// Inner input being wrapped in state
|
||||
pub input: I,
|
||||
/// User-provided state
|
||||
pub state: S,
|
||||
}
|
||||
|
||||
impl<I, S> AsRef<I> for Stateful<I, S> {
|
||||
#[inline(always)]
|
||||
fn as_ref(&self) -> &I {
|
||||
&self.input
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, S> core::ops::Deref for Stateful<I, S> {
|
||||
type Target = I;
|
||||
|
||||
#[inline(always)]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: core::fmt::Display, S> core::fmt::Display for Stateful<I, S> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
self.input.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, S> SliceLen for Stateful<I, S>
|
||||
where
|
||||
I: SliceLen,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn slice_len(&self) -> usize {
|
||||
self.input.slice_len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: Stream, S: core::fmt::Debug> Stream for Stateful<I, S> {
|
||||
type Token = <I as Stream>::Token;
|
||||
type Slice = <I as Stream>::Slice;
|
||||
|
||||
type IterOffsets = <I as Stream>::IterOffsets;
|
||||
|
||||
type Checkpoint = Checkpoint<I::Checkpoint, Self>;
|
||||
|
||||
#[inline(always)]
|
||||
fn iter_offsets(&self) -> Self::IterOffsets {
|
||||
self.input.iter_offsets()
|
||||
}
|
||||
#[inline(always)]
|
||||
fn eof_offset(&self) -> usize {
|
||||
self.input.eof_offset()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn next_token(&mut self) -> Option<Self::Token> {
|
||||
self.input.next_token()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn peek_token(&self) -> Option<Self::Token> {
|
||||
self.input.peek_token()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn offset_for<P>(&self, predicate: P) -> Option<usize>
|
||||
where
|
||||
P: Fn(Self::Token) -> bool,
|
||||
{
|
||||
self.input.offset_for(predicate)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn offset_at(&self, tokens: usize) -> Result<usize, Needed> {
|
||||
self.input.offset_at(tokens)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn next_slice(&mut self, offset: usize) -> Self::Slice {
|
||||
self.input.next_slice(offset)
|
||||
}
|
||||
#[inline(always)]
|
||||
unsafe fn next_slice_unchecked(&mut self, offset: usize) -> Self::Slice {
|
||||
// SAFETY: Passing up invariants
|
||||
unsafe { self.input.next_slice_unchecked(offset) }
|
||||
}
|
||||
#[inline(always)]
|
||||
fn peek_slice(&self, offset: usize) -> Self::Slice {
|
||||
self.input.peek_slice(offset)
|
||||
}
|
||||
#[inline(always)]
|
||||
unsafe fn peek_slice_unchecked(&self, offset: usize) -> Self::Slice {
|
||||
// SAFETY: Passing up invariants
|
||||
unsafe { self.input.peek_slice_unchecked(offset) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn checkpoint(&self) -> Self::Checkpoint {
|
||||
Checkpoint::<_, Self>::new(self.input.checkpoint())
|
||||
}
|
||||
#[inline(always)]
|
||||
fn reset(&mut self, checkpoint: &Self::Checkpoint) {
|
||||
self.input.reset(&checkpoint.inner);
|
||||
}
|
||||
|
||||
fn trace(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
self.input.trace(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, S> Location for Stateful<I, S>
|
||||
where
|
||||
I: Location,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn previous_token_end(&self) -> usize {
|
||||
self.input.previous_token_end()
|
||||
}
|
||||
#[inline(always)]
|
||||
fn current_token_start(&self) -> usize {
|
||||
self.input.current_token_start()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable-recover")]
|
||||
#[cfg(feature = "std")]
|
||||
impl<I, E, S> Recover<E> for Stateful<I, S>
|
||||
where
|
||||
I: Recover<E>,
|
||||
I: Stream,
|
||||
S: Clone + core::fmt::Debug,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn record_err(
|
||||
&mut self,
|
||||
_token_start: &Self::Checkpoint,
|
||||
_err_start: &Self::Checkpoint,
|
||||
err: E,
|
||||
) -> Result<(), E> {
|
||||
Err(err)
|
||||
}
|
||||
|
||||
/// Report whether the [`Stream`] can save off errors for recovery
|
||||
#[inline(always)]
|
||||
fn is_recovery_supported() -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, S> StreamIsPartial for Stateful<I, S>
|
||||
where
|
||||
I: StreamIsPartial,
|
||||
{
|
||||
type PartialState = I::PartialState;
|
||||
|
||||
#[inline]
|
||||
fn complete(&mut self) -> Self::PartialState {
|
||||
self.input.complete()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn restore_partial(&mut self, state: Self::PartialState) {
|
||||
self.input.restore_partial(state);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn is_partial_supported() -> bool {
|
||||
I::is_partial_supported()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn is_partial(&self) -> bool {
|
||||
self.input.is_partial()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, S> Offset for Stateful<I, S>
|
||||
where
|
||||
I: Stream,
|
||||
S: Clone + core::fmt::Debug,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn offset_from(&self, start: &Self) -> usize {
|
||||
self.offset_from(&start.checkpoint())
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, S> Offset<<Stateful<I, S> as Stream>::Checkpoint> for Stateful<I, S>
|
||||
where
|
||||
I: Stream,
|
||||
S: core::fmt::Debug,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn offset_from(&self, other: &<Stateful<I, S> as Stream>::Checkpoint) -> usize {
|
||||
self.checkpoint().offset_from(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, S> AsBytes for Stateful<I, S>
|
||||
where
|
||||
I: AsBytes,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn as_bytes(&self) -> &[u8] {
|
||||
self.input.as_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, S> AsBStr for Stateful<I, S>
|
||||
where
|
||||
I: AsBStr,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn as_bstr(&self) -> &[u8] {
|
||||
self.input.as_bstr()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, S, U> Compare<U> for Stateful<I, S>
|
||||
where
|
||||
I: Compare<U>,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn compare(&self, other: U) -> CompareResult {
|
||||
self.input.compare(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, S, T> FindSlice<T> for Stateful<I, S>
|
||||
where
|
||||
I: FindSlice<T>,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn find_slice(&self, substr: T) -> Option<core::ops::Range<usize>> {
|
||||
self.input.find_slice(substr)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, S> UpdateSlice for Stateful<I, S>
|
||||
where
|
||||
I: UpdateSlice,
|
||||
S: Clone + core::fmt::Debug,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn update_slice(mut self, inner: Self::Slice) -> Self {
|
||||
self.input = I::update_slice(self.input, inner);
|
||||
self
|
||||
}
|
||||
}
|
||||
Vendored
+171
@@ -0,0 +1,171 @@
|
||||
#![cfg(feature = "parser")]
|
||||
|
||||
use crate::error::ErrMode;
|
||||
use crate::error::ErrMode::Backtrack;
|
||||
use crate::error::InputError;
|
||||
use crate::token::literal;
|
||||
use crate::{
|
||||
combinator::{separated, separated_pair},
|
||||
ModalResult, Parser,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
#[test]
|
||||
fn test_fxhashmap_compiles() {
|
||||
let input = "a=b";
|
||||
fn pair(i: &mut &str) -> ModalResult<(char, char)> {
|
||||
let out = separated_pair('a', '=', 'b').parse_next(i)?;
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
let _: rustc_hash::FxHashMap<char, char> = separated(0.., pair, ',').parse(input).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_offset_u8() {
|
||||
let s = b"abcd123";
|
||||
let a = &s[..];
|
||||
let b = &a[2..];
|
||||
let c = &a[..4];
|
||||
let d = &a[3..5];
|
||||
assert_eq!(b.offset_from(&a), 2);
|
||||
assert_eq!(c.offset_from(&a), 0);
|
||||
assert_eq!(d.offset_from(&a), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_offset_str() {
|
||||
let a = "abcřèÂßÇd123";
|
||||
let b = &a[7..];
|
||||
let c = &a[..5];
|
||||
let d = &a[5..9];
|
||||
assert_eq!(b.offset_from(&a), 7);
|
||||
assert_eq!(c.offset_from(&a), 0);
|
||||
assert_eq!(d.offset_from(&a), 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_partial_complete() {
|
||||
let mut i = Partial::new(&b""[..]);
|
||||
assert!(Partial::<&[u8]>::is_partial_supported());
|
||||
|
||||
assert!(i.is_partial(), "incomplete by default");
|
||||
let incomplete_state = i.complete();
|
||||
assert!(!i.is_partial(), "the stream should be marked as complete");
|
||||
|
||||
i.restore_partial(incomplete_state);
|
||||
assert!(i.is_partial(), "incomplete stream state should be restored");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_custom_slice() {
|
||||
type Token = usize;
|
||||
type TokenSlice<'i> = &'i [Token];
|
||||
|
||||
let mut tokens: TokenSlice<'_> = &[1, 2, 3, 4];
|
||||
|
||||
let input = &mut tokens;
|
||||
let start = input.checkpoint();
|
||||
let _ = input.next_token();
|
||||
let _ = input.next_token();
|
||||
let offset = input.offset_from(&start);
|
||||
assert_eq!(offset, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_literal_support_char() {
|
||||
assert_eq!(
|
||||
literal::<_, _, ErrMode<InputError<_>>>('π').parse_peek("π"),
|
||||
Ok(("", "π"))
|
||||
);
|
||||
assert_eq!(
|
||||
literal::<_, _, ErrMode<InputError<_>>>('π').parse_peek("π3.14"),
|
||||
Ok(("3.14", "π"))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
literal::<_, _, ErrMode<InputError<_>>>("π").parse_peek("π3.14"),
|
||||
Ok(("3.14", "π"))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
literal::<_, _, ErrMode<InputError<_>>>('-').parse_peek("π"),
|
||||
Err(Backtrack(InputError::at("π")))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
literal::<_, Partial<&[u8]>, ErrMode<InputError<_>>>('π')
|
||||
.parse_peek(Partial::new(b"\xCF\x80")),
|
||||
Ok((Partial::new(Default::default()), "π".as_bytes()))
|
||||
);
|
||||
assert_eq!(
|
||||
literal::<_, &[u8], ErrMode<InputError<_>>>('π').parse_peek(b"\xCF\x80"),
|
||||
Ok((Default::default(), "π".as_bytes()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
literal::<_, Partial<&[u8]>, ErrMode<InputError<_>>>('π')
|
||||
.parse_peek(Partial::new(b"\xCF\x803.14")),
|
||||
Ok((Partial::new(&b"3.14"[..]), "π".as_bytes()))
|
||||
);
|
||||
assert_eq!(
|
||||
literal::<_, &[u8], ErrMode<InputError<_>>>('π').parse_peek(b"\xCF\x80"),
|
||||
Ok((Default::default(), "π".as_bytes()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
literal::<_, &[u8], ErrMode<InputError<_>>>('π').parse_peek(b"\xCF\x803.14"),
|
||||
Ok((&b"3.14"[..], "π".as_bytes()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
literal::<_, &[u8], ErrMode<InputError<_>>>('-').parse_peek(b"\xCF\x80"),
|
||||
Err(Backtrack(InputError::at(&b"\xCF\x80"[..],)))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tokenslice_location() {
|
||||
#[derive(Clone, Debug)]
|
||||
struct Token {
|
||||
span: core::ops::Range<usize>,
|
||||
}
|
||||
|
||||
impl Location for Token {
|
||||
#[inline(always)]
|
||||
fn previous_token_end(&self) -> usize {
|
||||
self.span.end
|
||||
}
|
||||
#[inline(always)]
|
||||
fn current_token_start(&self) -> usize {
|
||||
self.span.start
|
||||
}
|
||||
}
|
||||
|
||||
let input = [
|
||||
Token { span: 1..9 },
|
||||
Token { span: 11..19 },
|
||||
Token { span: 21..29 },
|
||||
];
|
||||
let mut input = TokenSlice::new(&input);
|
||||
assert_eq!(input.previous_token_end(), 1);
|
||||
|
||||
// Parse operation
|
||||
assert_eq!(input.current_token_start(), 1);
|
||||
let _ = input.next_token();
|
||||
assert_eq!(input.previous_token_end(), 9);
|
||||
|
||||
// Parse operation
|
||||
assert_eq!(input.current_token_start(), 11);
|
||||
let _ = input.next_token();
|
||||
assert_eq!(input.previous_token_end(), 19);
|
||||
|
||||
// Parse operation
|
||||
assert_eq!(input.current_token_start(), 21);
|
||||
let _ = input.next_token();
|
||||
assert_eq!(input.previous_token_end(), 29);
|
||||
|
||||
assert_eq!(input.current_token_start(), 29);
|
||||
}
|
||||
Vendored
+308
@@ -0,0 +1,308 @@
|
||||
use crate::stream::Checkpoint;
|
||||
use crate::stream::Compare;
|
||||
use crate::stream::CompareResult;
|
||||
use crate::stream::Location;
|
||||
use crate::stream::Needed;
|
||||
use crate::stream::Offset;
|
||||
#[cfg(feature = "unstable-recover")]
|
||||
#[cfg(feature = "std")]
|
||||
use crate::stream::Recover;
|
||||
use crate::stream::SliceLen;
|
||||
use crate::stream::Stream;
|
||||
use crate::stream::StreamIsPartial;
|
||||
use crate::stream::UpdateSlice;
|
||||
use core::iter::Enumerate;
|
||||
use core::slice::Iter;
|
||||
|
||||
/// Specialized input for parsing lexed tokens
|
||||
///
|
||||
/// Helpful impls
|
||||
/// - Any `PartialEq` type (e.g. a `TokenKind` or `&str`) can be used with
|
||||
/// [`literal`][crate::token::literal]
|
||||
/// - A `PartialEq` for `&str` allows for using `&str` as a parser for tokens
|
||||
/// - [`ContainsToken`][crate::stream::ContainsToken] for `T` to for parsing with token sets
|
||||
/// - [`Location`] for `T` to extract spans from tokens
|
||||
///
|
||||
/// See also [Lexing and Parsing][crate::_topic::lexing].
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub struct TokenSlice<'t, T> {
|
||||
initial: &'t [T],
|
||||
input: &'t [T],
|
||||
}
|
||||
|
||||
impl<'t, T> TokenSlice<'t, T>
|
||||
where
|
||||
T: core::fmt::Debug + Clone,
|
||||
{
|
||||
/// Make a stream to parse tokens
|
||||
#[inline]
|
||||
pub fn new(input: &'t [T]) -> Self {
|
||||
Self {
|
||||
initial: input,
|
||||
input,
|
||||
}
|
||||
}
|
||||
|
||||
/// Reset the stream to the start
|
||||
///
|
||||
/// This is useful for formats that encode a graph with addresses relative to the start of the
|
||||
/// input.
|
||||
#[doc(alias = "fseek")]
|
||||
#[inline]
|
||||
pub fn reset_to_start(&mut self) {
|
||||
let start = self.initial.checkpoint();
|
||||
self.input.reset(&start);
|
||||
}
|
||||
|
||||
/// Iterate over consumed tokens starting with the last emitted
|
||||
///
|
||||
/// This is intended to help build up appropriate context when reporting errors.
|
||||
#[inline]
|
||||
pub fn previous_tokens(&self) -> impl Iterator<Item = &'t T> {
|
||||
let offset = self.input.offset_from(&self.initial);
|
||||
self.initial[0..offset].iter().rev()
|
||||
}
|
||||
}
|
||||
|
||||
/// Track locations by implementing [`Location`] on the Token.
|
||||
impl<T> TokenSlice<'_, T>
|
||||
where
|
||||
T: Location,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn previous_token_end(&self) -> Option<usize> {
|
||||
let index = self.input.offset_from(&self.initial);
|
||||
index
|
||||
.checked_sub(1)
|
||||
.map(|i| self.initial[i].previous_token_end())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn current_token_start(&self) -> Option<usize> {
|
||||
self.input.first().map(|t| t.current_token_start())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for TokenSlice<'_, T>
|
||||
where
|
||||
T: core::fmt::Debug + Clone,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self::new(&[])
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> core::ops::Deref for TokenSlice<'_, T> {
|
||||
type Target = [T];
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.input
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: core::fmt::Debug> core::fmt::Debug for TokenSlice<'_, T> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
self.input.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> SliceLen for TokenSlice<'_, T> {
|
||||
#[inline(always)]
|
||||
fn slice_len(&self) -> usize {
|
||||
self.input.slice_len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'t, T> Stream for TokenSlice<'t, T>
|
||||
where
|
||||
T: core::fmt::Debug + Clone,
|
||||
{
|
||||
type Token = &'t T;
|
||||
type Slice = &'t [T];
|
||||
|
||||
type IterOffsets = Enumerate<Iter<'t, T>>;
|
||||
|
||||
type Checkpoint = Checkpoint<&'t [T], Self>;
|
||||
|
||||
#[inline(always)]
|
||||
fn iter_offsets(&self) -> Self::IterOffsets {
|
||||
self.input.iter().enumerate()
|
||||
}
|
||||
#[inline(always)]
|
||||
fn eof_offset(&self) -> usize {
|
||||
self.input.eof_offset()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn next_token(&mut self) -> Option<Self::Token> {
|
||||
let (token, next) = self.input.split_first()?;
|
||||
self.input = next;
|
||||
Some(token)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn peek_token(&self) -> Option<Self::Token> {
|
||||
self.input.first()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn offset_for<P>(&self, predicate: P) -> Option<usize>
|
||||
where
|
||||
P: Fn(Self::Token) -> bool,
|
||||
{
|
||||
self.input.iter().position(predicate)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn offset_at(&self, tokens: usize) -> Result<usize, Needed> {
|
||||
self.input.offset_at(tokens)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn next_slice(&mut self, offset: usize) -> Self::Slice {
|
||||
self.input.next_slice(offset)
|
||||
}
|
||||
#[inline(always)]
|
||||
unsafe fn next_slice_unchecked(&mut self, offset: usize) -> Self::Slice {
|
||||
// SAFETY: Passing up invariants
|
||||
unsafe { self.input.next_slice_unchecked(offset) }
|
||||
}
|
||||
#[inline(always)]
|
||||
fn peek_slice(&self, offset: usize) -> Self::Slice {
|
||||
self.input.peek_slice(offset)
|
||||
}
|
||||
#[inline(always)]
|
||||
unsafe fn peek_slice_unchecked(&self, offset: usize) -> Self::Slice {
|
||||
// SAFETY: Passing up invariants
|
||||
unsafe { self.input.peek_slice_unchecked(offset) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn checkpoint(&self) -> Self::Checkpoint {
|
||||
Checkpoint::<_, Self>::new(self.input)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn reset(&mut self, checkpoint: &Self::Checkpoint) {
|
||||
self.input = checkpoint.inner;
|
||||
}
|
||||
|
||||
fn trace(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
self.input.trace(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Location for TokenSlice<'_, T>
|
||||
where
|
||||
T: Location,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn previous_token_end(&self) -> usize {
|
||||
self.previous_token_end()
|
||||
.or_else(|| self.current_token_start())
|
||||
.unwrap_or(0)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn current_token_start(&self) -> usize {
|
||||
self.current_token_start()
|
||||
.or_else(|| self.previous_token_end())
|
||||
.unwrap_or(0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable-recover")]
|
||||
#[cfg(feature = "std")]
|
||||
impl<T, E> Recover<E> for TokenSlice<'_, T>
|
||||
where
|
||||
T: core::fmt::Debug + Clone,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn record_err(
|
||||
&mut self,
|
||||
_token_start: &Self::Checkpoint,
|
||||
_err_start: &Self::Checkpoint,
|
||||
err: E,
|
||||
) -> Result<(), E> {
|
||||
Err(err)
|
||||
}
|
||||
|
||||
/// Report whether the [`Stream`] can save off errors for recovery
|
||||
#[inline(always)]
|
||||
fn is_recovery_supported() -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl<'t, T> StreamIsPartial for TokenSlice<'t, T>
|
||||
where
|
||||
T: core::fmt::Debug + Clone,
|
||||
{
|
||||
type PartialState = <&'t [T] as StreamIsPartial>::PartialState;
|
||||
|
||||
#[inline]
|
||||
fn complete(&mut self) -> Self::PartialState {
|
||||
#![allow(clippy::semicolon_if_nothing_returned)]
|
||||
self.input.complete()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn restore_partial(&mut self, state: Self::PartialState) {
|
||||
self.input.restore_partial(state);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn is_partial_supported() -> bool {
|
||||
<&[T] as StreamIsPartial>::is_partial_supported()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn is_partial(&self) -> bool {
|
||||
self.input.is_partial()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Offset for TokenSlice<'_, T>
|
||||
where
|
||||
T: core::fmt::Debug + Clone,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn offset_from(&self, other: &Self) -> usize {
|
||||
self.offset_from(&other.checkpoint())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Offset<<TokenSlice<'_, T> as Stream>::Checkpoint> for TokenSlice<'_, T>
|
||||
where
|
||||
T: core::fmt::Debug + Clone,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn offset_from(&self, other: &<TokenSlice<'_, T> as Stream>::Checkpoint) -> usize {
|
||||
self.checkpoint().offset_from(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, O> Compare<O> for TokenSlice<'_, T>
|
||||
where
|
||||
T: PartialEq<O> + Eq,
|
||||
{
|
||||
#[inline]
|
||||
fn compare(&self, t: O) -> CompareResult {
|
||||
if let Some(token) = self.first() {
|
||||
if *token == t {
|
||||
CompareResult::Ok(1)
|
||||
} else {
|
||||
CompareResult::Error
|
||||
}
|
||||
} else {
|
||||
CompareResult::Incomplete
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> UpdateSlice for TokenSlice<'_, T>
|
||||
where
|
||||
T: core::fmt::Debug + Clone,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn update_slice(mut self, inner: Self::Slice) -> Self {
|
||||
self.input = <&[T] as UpdateSlice>::update_slice(self.input, inner);
|
||||
self
|
||||
}
|
||||
}
|
||||
Vendored
+1096
File diff suppressed because it is too large.
Load diff
Vendored
+2895
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user