Vendor things

This commit is contained in:
John Doty 2024-03-08 11:03:01 -08:00
parent 5deceec006
commit 977e3c17e5
19434 changed files with 10682014 additions and 0 deletions

File diff suppressed because one or more lines are too long

1422
third-party/vendor/winnow/Cargo.lock generated vendored Normal file

File diff suppressed because it is too large Load diff

260
third-party/vendor/winnow/Cargo.toml vendored Normal file
View file

@ -0,0 +1,260 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
rust-version = "1.64.0"
name = "winnow"
version = "0.5.40"
include = [
"build.rs",
"src/**/*",
"Cargo.toml",
"Cargo.lock",
"LICENSE*",
"README.md",
"benches/**/*",
"examples/**/*",
]
autoexamples = false
description = "A byte-oriented, zero-copy, parser combinators library"
readme = "README.md"
keywords = [
"parser",
"parser-combinators",
"parsing",
"streaming",
"bit",
]
categories = ["parsing"]
license = "MIT"
repository = "https://github.com/winnow-rs/winnow"
[package.metadata.docs.rs]
cargo-args = [
"-Zunstable-options",
"-Zrustdoc-scrape-examples",
]
features = ["unstable-doc"]
rustdoc-args = [
"--cfg",
"docsrs",
]
[[package.metadata.release.pre-release-replacements]]
file = "CHANGELOG.md"
min = 1
replace = "{{version}}"
search = "Unreleased"
[[package.metadata.release.pre-release-replacements]]
exactly = 1
file = "CHANGELOG.md"
replace = "...{{tag_name}}"
search = '\.\.\.HEAD'
[[package.metadata.release.pre-release-replacements]]
file = "CHANGELOG.md"
min = 1
replace = "{{date}}"
search = "ReleaseDate"
[[package.metadata.release.pre-release-replacements]]
exactly = 1
file = "CHANGELOG.md"
replace = """
<!-- next-header -->
## [Unreleased] - ReleaseDate
"""
search = "<!-- next-header -->"
[[package.metadata.release.pre-release-replacements]]
exactly = 1
file = "CHANGELOG.md"
replace = """
<!-- next-url -->
[Unreleased]: https://github.com/winnow-rs/winnow/compare/{{tag_name}}...HEAD"""
search = "<!-- next-url -->"
[[package.metadata.release.pre-release-replacements]]
exactly = 1
file = "src/lib.rs"
replace = "blob/v{{version}}/CHANGELOG.md"
search = 'blob/v.+\..+\..+/CHANGELOG.md'
[profile.bench]
lto = true
codegen-units = 1
debug = 2
[[example]]
name = "arithmetic"
test = true
required-features = ["alloc"]
[[example]]
name = "css"
test = true
required-features = ["alloc"]
[[example]]
name = "custom_error"
test = true
required-features = ["alloc"]
[[example]]
name = "http"
required-features = ["alloc"]
[[example]]
name = "ini"
test = true
required-features = ["std"]
[[example]]
name = "json"
test = true
required-features = ["std"]
[[example]]
name = "ndjson"
test = true
required-features = ["std"]
[[example]]
name = "json_iterator"
required-features = ["std"]
[[example]]
name = "iterator"
[[example]]
name = "s_expression"
required-features = ["alloc"]
[[example]]
name = "string"
required-features = ["alloc"]
[[bench]]
name = "arithmetic"
path = "examples/arithmetic/bench.rs"
harness = false
required-features = ["alloc"]
[[bench]]
name = "contains_token"
harness = false
[[bench]]
name = "find_slice"
harness = false
[[bench]]
name = "iter"
harness = false
[[bench]]
name = "next_slice"
harness = false
[[bench]]
name = "number"
harness = false
[[bench]]
name = "http"
path = "examples/http/bench.rs"
harness = false
required-features = ["alloc"]
[[bench]]
name = "ini"
path = "examples/ini/bench.rs"
harness = false
required-features = ["std"]
[[bench]]
name = "json"
path = "examples/json/bench.rs"
harness = false
required-features = ["std"]
[dependencies.anstream]
version = "0.3.2"
optional = true
[dependencies.anstyle]
version = "1.0.1"
optional = true
[dependencies.is-terminal]
version = "0.4.9"
optional = true
[dependencies.memchr]
version = "2.5"
optional = true
default-features = false
[dependencies.terminal_size]
version = "0.2.6"
optional = true
[dev-dependencies.circular]
version = "0.3.0"
[dev-dependencies.criterion]
version = "0.5.1"
[dev-dependencies.doc-comment]
version = "0.3"
[dev-dependencies.escargot]
version = "0.5.7"
[dev-dependencies.lexopt]
version = "0.3.0"
[dev-dependencies.proptest]
version = "1.2.0"
[dev-dependencies.rustc-hash]
version = "1.1.0"
[dev-dependencies.snapbox]
version = "0.4.11"
features = ["examples"]
[dev-dependencies.term-transcript]
version = "0.2.0"
[features]
alloc = []
debug = [
"dep:anstream",
"dep:anstyle",
"dep:is-terminal",
"dep:terminal_size",
]
default = ["std"]
simd = ["dep:memchr"]
std = [
"alloc",
"memchr?/std",
]
unstable-doc = [
"alloc",
"std",
"simd",
"unstable-recover",
]
unstable-recover = []

18
third-party/vendor/winnow/LICENSE-MIT vendored Normal file
View file

@ -0,0 +1,18 @@
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

27
third-party/vendor/winnow/README.md vendored Normal file
View file

@ -0,0 +1,27 @@
# winnow, making parsing a breeze
[![LICENSE](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
[![Build Status](https://github.com/winnow-rs/winnow/actions/workflows/ci.yml/badge.svg)](https://github.com/winnow-rs/winnow/actions/workflows/ci.yml)
[![Coverage Status](https://coveralls.io/repos/github/winnow-rs/winnow/badge.svg?branch=main)](https://coveralls.io/github/winnow-rs/winnow?branch=main)
[![Crates.io Version](https://img.shields.io/crates/v/winnow.svg)](https://crates.io/crates/winnow)
## About
Build up a parser for your format of choice with the building blocks provided by `winnow`.
For more details, see:
- [Tutorial](https://docs.rs/winnow/latest/winnow/_tutorial/index.html)
- [Special Topics](https://docs.rs/winnow/latest/winnow/_topic/index.html)
- [Why winnow? How does it compare to ...?](https://docs.rs/winnow/latest/winnow/_topic/why/index.html)
- [API Reference](https://docs.rs/winnow)
- [List of combinators](https://docs.rs/winnow/latest/winnow/combinator/index.html)
# Contributors
winnow is the fruit of the work of many contributors over the years, many
thanks for your help! In particular, thanks to [Geal](https://github.com/Geal)
for the original [`nom` crate](https://crates.io/crates/nom).
<a href="https://github.com/winnow-rs/winnow/graphs/contributors">
<img src="https://contributors-img.web.app/image?repo=winnow-rs/winnow" />
</a>

View file

@ -0,0 +1,114 @@
use criterion::black_box;
use winnow::combinator::alt;
use winnow::combinator::repeat;
use winnow::prelude::*;
use winnow::token::take_till;
use winnow::token::take_while;
fn contains_token(c: &mut criterion::Criterion) {
let data = [
("contiguous", CONTIGUOUS),
("interleaved", INTERLEAVED),
("canada", CANADA),
];
let mut group = c.benchmark_group("contains_token");
for (name, sample) in data {
let len = sample.len();
group.throughput(criterion::Throughput::Bytes(len as u64));
group.bench_with_input(criterion::BenchmarkId::new("slice", name), &len, |b, _| {
b.iter(|| black_box(parser_slice.parse_peek(black_box(sample)).unwrap()));
});
group.bench_with_input(criterion::BenchmarkId::new("array", name), &len, |b, _| {
b.iter(|| black_box(parser_array.parse_peek(black_box(sample)).unwrap()));
});
group.bench_with_input(criterion::BenchmarkId::new("tuple", name), &len, |b, _| {
b.iter(|| black_box(parser_tuple.parse_peek(black_box(sample)).unwrap()));
});
group.bench_with_input(
criterion::BenchmarkId::new("closure-or", name),
&len,
|b, _| {
b.iter(|| black_box(parser_closure_or.parse_peek(black_box(sample)).unwrap()));
},
);
group.bench_with_input(
criterion::BenchmarkId::new("closure-matches", name),
&len,
|b, _| {
b.iter(|| {
black_box(
parser_closure_matches
.parse_peek(black_box(sample))
.unwrap(),
)
});
},
);
}
group.finish();
}
fn parser_slice(input: &mut &str) -> PResult<usize> {
let contains = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'][..];
repeat(
0..,
alt((take_while(1.., contains), take_till(1.., contains))),
)
.parse_next(input)
}
fn parser_array(input: &mut &str) -> PResult<usize> {
let contains = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
repeat(
0..,
alt((take_while(1.., contains), take_till(1.., contains))),
)
.parse_next(input)
}
fn parser_tuple(input: &mut &str) -> PResult<usize> {
let contains = ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9');
repeat(
0..,
alt((take_while(1.., contains), take_till(1.., contains))),
)
.parse_next(input)
}
fn parser_closure_or(input: &mut &str) -> PResult<usize> {
let contains = |c: char| {
c == '0'
|| c == '1'
|| c == '2'
|| c == '3'
|| c == '4'
|| c == '5'
|| c == '6'
|| c == '7'
|| c == '8'
|| c == '9'
};
repeat(
0..,
alt((take_while(1.., contains), take_till(1.., contains))),
)
.parse_next(input)
}
fn parser_closure_matches(input: &mut &str) -> PResult<usize> {
let contains = |c: char| matches!(c, '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9');
repeat(
0..,
alt((take_while(1.., contains), take_till(1.., contains))),
)
.parse_next(input)
}
const CONTIGUOUS: &str = "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789";
const INTERLEAVED: &str = "0123456789abc0123456789ab0123456789ab0123456789ab0123456789ab0123456789ab0123456789ab0123456789ab0123456789ab0123456789ab0123456789ab0123456789ab0123456789ab0123456789ab0123456789ab0123456789ab0123456789ab0123456789ab0123456789ab0123456789ab0123456789ab0123456789ab0123456789ab";
const CANADA: &str = include_str!("../third_party/nativejson-benchmark/data/canada.json");
criterion::criterion_group!(benches, contains_token);
criterion::criterion_main!(benches);

View file

@ -0,0 +1,50 @@
use criterion::black_box;
use winnow::combinator::repeat;
use winnow::prelude::*;
use winnow::token::take_until;
fn find_slice(c: &mut criterion::Criterion) {
let empty = "";
let start_byte = "\r".repeat(100);
let start_slice = "\r\n".repeat(100);
let small = format!("{:>10}\r\n", "").repeat(100);
let large = format!("{:>10000}\r\n", "").repeat(100);
let data = [
("empty", (empty, empty)),
("start", (&start_byte, &start_slice)),
("medium", (&small, &small)),
("large", (&large, &large)),
];
let mut group = c.benchmark_group("find_slice");
for (name, samples) in data {
group.bench_with_input(
criterion::BenchmarkId::new("byte", name),
samples.0,
|b, sample| {
b.iter(|| black_box(parser_byte.parse_peek(black_box(sample)).unwrap()));
},
);
group.bench_with_input(
criterion::BenchmarkId::new("slice", name),
samples.1,
|b, sample| {
b.iter(|| black_box(parser_slice.parse_peek(black_box(sample)).unwrap()));
},
);
}
group.finish();
}
fn parser_byte(input: &mut &str) -> PResult<usize> {
repeat(0.., (take_until(0.., "\r"), "\r")).parse_next(input)
}
fn parser_slice(input: &mut &str) -> PResult<usize> {
repeat(0.., (take_until(0.., "\r\n"), "\r\n")).parse_next(input)
}
criterion::criterion_group!(benches, find_slice);
criterion::criterion_main!(benches);

View file

@ -0,0 +1,120 @@
use criterion::black_box;
use winnow::combinator::opt;
use winnow::prelude::*;
use winnow::stream::AsChar;
use winnow::stream::Stream as _;
use winnow::token::one_of;
fn iter(c: &mut criterion::Criterion) {
let data = [
("contiguous", CONTIGUOUS.as_bytes()),
("interleaved", INTERLEAVED.as_bytes()),
("canada", CANADA.as_bytes()),
];
let mut group = c.benchmark_group("iter");
for (name, sample) in data {
let len = sample.len();
group.throughput(criterion::Throughput::Bytes(len as u64));
group.bench_with_input(
criterion::BenchmarkId::new("iterate", name),
&len,
|b, _| {
b.iter(|| black_box(iterate.parse_peek(black_box(sample)).unwrap()));
},
);
group.bench_with_input(
criterion::BenchmarkId::new("next_token", name),
&len,
|b, _| {
b.iter(|| black_box(next_token.parse_peek(black_box(sample)).unwrap()));
},
);
group.bench_with_input(
criterion::BenchmarkId::new("opt(one_of)", name),
&len,
|b, _| {
b.iter(|| black_box(opt_one_of.parse_peek(black_box(sample)).unwrap()));
},
);
group.bench_with_input(
criterion::BenchmarkId::new("take_while", name),
&len,
|b, _| {
b.iter(|| black_box(take_while.parse_peek(black_box(sample)).unwrap()));
},
);
group.bench_with_input(criterion::BenchmarkId::new("repeat", name), &len, |b, _| {
b.iter(|| black_box(repeat.parse_peek(black_box(sample)).unwrap()));
});
}
group.finish();
}
fn iterate(input: &mut &[u8]) -> PResult<usize> {
let mut count = 0;
for byte in input.iter() {
if byte.is_dec_digit() {
count += 1;
}
}
input.finish();
Ok(count)
}
fn next_token(input: &mut &[u8]) -> PResult<usize> {
let mut count = 0;
while let Some(byte) = input.next_token() {
if byte.is_dec_digit() {
count += 1;
}
}
Ok(count)
}
fn opt_one_of(input: &mut &[u8]) -> PResult<usize> {
let mut count = 0;
while !input.is_empty() {
while opt(one_of(AsChar::is_dec_digit))
.parse_next(input)?
.is_some()
{
count += 1;
}
while opt(one_of(|b: u8| !b.is_dec_digit()))
.parse_next(input)?
.is_some()
{}
}
Ok(count)
}
fn take_while(input: &mut &[u8]) -> PResult<usize> {
let mut count = 0;
while !input.is_empty() {
count += winnow::token::take_while(0.., AsChar::is_dec_digit)
.parse_next(input)?
.len();
let _ = winnow::token::take_while(0.., |b: u8| !b.is_dec_digit()).parse_next(input)?;
}
Ok(count)
}
fn repeat(input: &mut &[u8]) -> PResult<usize> {
let mut count = 0;
while !input.is_empty() {
count += winnow::combinator::repeat(0.., one_of(AsChar::is_dec_digit))
.map(|count: usize| count)
.parse_next(input)?;
winnow::combinator::repeat(0.., one_of(|b: u8| !b.is_dec_digit())).parse_next(input)?;
}
Ok(count)
}
const CONTIGUOUS: &str = "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789";
const INTERLEAVED: &str = "0123456789abc0123456789ab0123456789ab0123456789ab0123456789ab0123456789ab0123456789ab0123456789ab0123456789ab0123456789ab0123456789ab0123456789ab0123456789ab0123456789ab0123456789ab0123456789ab0123456789ab0123456789ab0123456789ab0123456789ab0123456789ab0123456789ab0123456789ab";
const CANADA: &str = include_str!("../third_party/nativejson-benchmark/data/canada.json");
criterion::criterion_group!(benches, iter);
criterion::criterion_main!(benches);

View file

@ -0,0 +1,133 @@
use criterion::black_box;
use winnow::combinator::repeat;
use winnow::prelude::*;
use winnow::token::one_of;
use winnow::token::tag;
fn next_slice(c: &mut criterion::Criterion) {
let mut group = c.benchmark_group("next_slice");
let name = "ascii";
let sample = "h".repeat(100);
let sample = sample.as_str();
group.bench_with_input(
criterion::BenchmarkId::new("char", name),
sample,
|b, sample| {
b.iter(|| black_box(parser_ascii_char.parse_peek(black_box(sample)).unwrap()));
},
);
group.bench_with_input(
criterion::BenchmarkId::new("str", name),
sample,
|b, sample| {
b.iter(|| black_box(parser_ascii_str.parse_peek(black_box(sample)).unwrap()));
},
);
group.bench_with_input(
criterion::BenchmarkId::new("one_of", name),
sample,
|b, sample| {
b.iter(|| black_box(parser_ascii_one_of.parse_peek(black_box(sample)).unwrap()));
},
);
group.bench_with_input(
criterion::BenchmarkId::new("tag_char", name),
sample,
|b, sample| {
b.iter(|| black_box(parser_ascii_tag_char.parse_peek(black_box(sample)).unwrap()));
},
);
group.bench_with_input(
criterion::BenchmarkId::new("tag_str", name),
sample,
|b, sample| {
b.iter(|| black_box(parser_ascii_tag_str.parse_peek(black_box(sample)).unwrap()));
},
);
let name = "utf8";
let sample = "🧑".repeat(100);
let sample = sample.as_str();
group.bench_with_input(
criterion::BenchmarkId::new("char", name),
sample,
|b, sample| {
b.iter(|| black_box(parser_utf8_char.parse_peek(black_box(sample)).unwrap()));
},
);
group.bench_with_input(
criterion::BenchmarkId::new("str", name),
sample,
|b, sample| {
b.iter(|| black_box(parser_utf8_str.parse_peek(black_box(sample)).unwrap()));
},
);
group.bench_with_input(
criterion::BenchmarkId::new("one_of", name),
sample,
|b, sample| {
b.iter(|| black_box(parser_utf8_one_of.parse_peek(black_box(sample)).unwrap()));
},
);
group.bench_with_input(
criterion::BenchmarkId::new("tag_char", name),
sample,
|b, sample| {
b.iter(|| black_box(parser_utf8_tag_char.parse_peek(black_box(sample)).unwrap()));
},
);
group.bench_with_input(
criterion::BenchmarkId::new("tag_str", name),
sample,
|b, sample| {
b.iter(|| black_box(parser_utf8_tag_str.parse_peek(black_box(sample)).unwrap()));
},
);
group.finish();
}
fn parser_ascii_char(input: &mut &str) -> PResult<usize> {
repeat(0.., 'h').parse_next(input)
}
fn parser_ascii_str(input: &mut &str) -> PResult<usize> {
repeat(0.., "h").parse_next(input)
}
fn parser_ascii_one_of(input: &mut &str) -> PResult<usize> {
repeat(0.., one_of('h')).parse_next(input)
}
fn parser_ascii_tag_char(input: &mut &str) -> PResult<usize> {
repeat(0.., tag('h')).parse_next(input)
}
fn parser_ascii_tag_str(input: &mut &str) -> PResult<usize> {
repeat(0.., tag("h")).parse_next(input)
}
fn parser_utf8_char(input: &mut &str) -> PResult<usize> {
repeat(0.., '🧑').parse_next(input)
}
fn parser_utf8_str(input: &mut &str) -> PResult<usize> {
repeat(0.., "🧑").parse_next(input)
}
fn parser_utf8_one_of(input: &mut &str) -> PResult<usize> {
repeat(0.., one_of('🧑')).parse_next(input)
}
fn parser_utf8_tag_char(input: &mut &str) -> PResult<usize> {
repeat(0.., tag('🧑')).parse_next(input)
}
fn parser_utf8_tag_str(input: &mut &str) -> PResult<usize> {
repeat(0.., tag("🧑")).parse_next(input)
}
criterion::criterion_group!(benches, next_slice);
criterion::criterion_main!(benches);

View file

@ -0,0 +1,70 @@
#[macro_use]
extern crate criterion;
use criterion::Criterion;
use winnow::ascii::float;
use winnow::binary::be_u64;
use winnow::error::ErrMode;
use winnow::error::ErrorKind;
use winnow::error::InputError;
use winnow::error::ParserError;
use winnow::prelude::*;
use winnow::stream::ParseSlice;
type Stream<'i> = &'i [u8];
fn parser(i: &mut Stream<'_>) -> PResult<u64> {
be_u64.parse_next(i)
}
fn number(c: &mut Criterion) {
let data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
parser
.parse_peek(&data[..])
.expect("should parse correctly");
c.bench_function("number", move |b| {
b.iter(|| parser.parse_peek(&data[..]).unwrap());
});
}
fn float_bytes(c: &mut Criterion) {
println!(
"float_bytes result: {:?}",
float::<_, f64, InputError<_>>.parse_peek(&b"-1.234E-12"[..])
);
c.bench_function("float bytes", |b| {
b.iter(|| float::<_, f64, InputError<_>>.parse_peek(&b"-1.234E-12"[..]));
});
}
fn float_str(c: &mut Criterion) {
println!(
"float_str result: {:?}",
float::<_, f64, InputError<_>>.parse_peek("-1.234E-12")
);
c.bench_function("float str", |b| {
b.iter(|| float::<_, f64, InputError<_>>.parse_peek("-1.234E-12"));
});
}
fn std_float(input: &mut &[u8]) -> PResult<f64> {
match input.parse_slice() {
Some(n) => Ok(n),
None => Err(ErrMode::from_error_kind(input, ErrorKind::Slice)),
}
}
fn std_float_bytes(c: &mut Criterion) {
println!(
"std_float_bytes result: {:?}",
std_float.parse_peek(&b"-1.234E-12"[..])
);
c.bench_function("std_float bytes", |b| {
b.iter(|| std_float.parse_peek(&b"-1.234E-12"[..]));
});
}
criterion_group!(benches, number, float_bytes, std_float_bytes, float_str);
criterion_main!(benches);

View file

@ -0,0 +1,33 @@
mod parser;
mod parser_ast;
mod parser_lexer;
use winnow::prelude::*;
#[allow(clippy::eq_op, clippy::erasing_op)]
fn arithmetic(c: &mut criterion::Criterion) {
let data = " 2*2 / ( 5 - 1) + 3 / 4 * (2 - 7 + 567 *12 /2) + 3*(1+2*( 45 /2))";
let expected = 2 * 2 / (5 - 1) + 3 * (1 + 2 * (45 / 2));
assert_eq!(parser::expr.parse(data), Ok(expected));
assert_eq!(
parser_ast::expr.parse(data).map(|ast| ast.eval()),
Ok(expected)
);
assert_eq!(
parser_lexer::expr2.parse(data).map(|ast| ast.eval()),
Ok(expected)
);
c.bench_function("direct", |b| {
b.iter(|| parser::expr.parse(data).unwrap());
});
c.bench_function("ast", |b| {
b.iter(|| parser_ast::expr.parse(data).unwrap().eval());
});
c.bench_function("lexer", |b| {
b.iter(|| parser_lexer::expr2.parse_peek(data).unwrap());
});
}
criterion::criterion_group!(benches, arithmetic);
criterion::criterion_main!(benches);

View file

@ -0,0 +1,86 @@
use winnow::prelude::*;
mod parser;
mod parser_ast;
mod parser_lexer;
fn main() -> Result<(), lexopt::Error> {
let args = Args::parse()?;
let input = args.input.as_deref().unwrap_or("1 + 1");
if let Err(err) = calc(input, args.implementation) {
println!("FAILED");
println!("{}", err);
}
Ok(())
}
fn calc(
input: &str,
imp: Impl,
) -> Result<(), winnow::error::ParseError<&str, winnow::error::ContextError>> {
println!("{} =", input);
match imp {
Impl::Eval => {
let result = parser::expr.parse(input)?;
println!(" {}", result);
}
Impl::Ast => {
let result = parser_ast::expr.parse(input)?;
println!(" {:#?}={}", result, result.eval());
}
Impl::Lexer => {
let tokens = parser_lexer::lex.parse(input)?;
println!(" {:#?}", tokens);
let result = parser_lexer::expr.parse(tokens.as_slice()).unwrap();
println!(" {:#?}={}", result, result.eval());
}
}
Ok(())
}
#[derive(Default)]
struct Args {
input: Option<String>,
implementation: Impl,
}
enum Impl {
Eval,
Ast,
Lexer,
}
impl Default for Impl {
fn default() -> Self {
Self::Eval
}
}
impl Args {
fn parse() -> Result<Self, lexopt::Error> {
use lexopt::prelude::*;
let mut res = Args::default();
let mut args = lexopt::Parser::from_env();
while let Some(arg) = args.next()? {
match arg {
Long("impl") => {
res.implementation = args.value()?.parse_with(|s| match s {
"eval" => Ok(Impl::Eval),
"ast" => Ok(Impl::Ast),
"lexer" => Ok(Impl::Lexer),
_ => Err("expected `eval`, `ast`"),
})?;
}
Value(input) => {
res.input = Some(input.string()?);
}
_ => return Err(arg.unexpected()),
}
}
Ok(res)
}
}

View file

@ -0,0 +1,135 @@
use std::str::FromStr;
use winnow::prelude::*;
use winnow::{
ascii::{digit1 as digits, multispace0 as multispaces},
combinator::alt,
combinator::delimited,
combinator::repeat,
token::one_of,
};
// Parser definition
pub fn expr(i: &mut &str) -> PResult<i64> {
let init = term.parse_next(i)?;
repeat(0.., (one_of(['+', '-']), term))
.fold(
move || init,
|acc, (op, val): (char, i64)| {
if op == '+' {
acc + val
} else {
acc - val
}
},
)
.parse_next(i)
}
// We read an initial factor and for each time we find
// a * or / operator followed by another factor, we do
// the math by folding everything
fn term(i: &mut &str) -> PResult<i64> {
let init = factor.parse_next(i)?;
repeat(0.., (one_of(['*', '/']), factor))
.fold(
move || init,
|acc, (op, val): (char, i64)| {
if op == '*' {
acc * val
} else {
acc / val
}
},
)
.parse_next(i)
}
// We transform an integer string into a i64, ignoring surrounding whitespace
// We look for a digit suite, and try to convert it.
// If either str::from_utf8 or FromStr::from_str fail,
// we fallback to the parens parser defined above
fn factor(i: &mut &str) -> PResult<i64> {
delimited(
multispaces,
alt((digits.try_map(FromStr::from_str), parens)),
multispaces,
)
.parse_next(i)
}
// We parse any expr surrounded by parens, ignoring all whitespace around those
fn parens(i: &mut &str) -> PResult<i64> {
delimited('(', expr, ')').parse_next(i)
}
#[test]
fn factor_test() {
let input = "3";
let expected = Ok(("", 3));
assert_eq!(factor.parse_peek(input), expected);
let input = " 12";
let expected = Ok(("", 12));
assert_eq!(factor.parse_peek(input), expected);
let input = "537 ";
let expected = Ok(("", 537));
assert_eq!(factor.parse_peek(input), expected);
let input = " 24 ";
let expected = Ok(("", 24));
assert_eq!(factor.parse_peek(input), expected);
}
#[test]
fn term_test() {
let input = " 12 *2 / 3";
let expected = Ok(("", 8));
assert_eq!(term.parse_peek(input), expected);
let input = " 12 *2 / 3";
let expected = Ok(("", 8));
assert_eq!(term.parse_peek(input), expected);
let input = " 2* 3 *2 *2 / 3";
let expected = Ok(("", 8));
assert_eq!(term.parse_peek(input), expected);
let input = " 48 / 3/2";
let expected = Ok(("", 8));
assert_eq!(term.parse_peek(input), expected);
}
#[test]
fn expr_test() {
let input = " 1 + 2 ";
let expected = Ok(("", 3));
assert_eq!(expr.parse_peek(input), expected);
let input = " 12 + 6 - 4+ 3";
let expected = Ok(("", 17));
assert_eq!(expr.parse_peek(input), expected);
let input = " 1 + 2*3 + 4";
let expected = Ok(("", 11));
assert_eq!(expr.parse_peek(input), expected);
}
#[test]
fn parens_test() {
let input = " ( 2 )";
let expected = Ok(("", 2));
assert_eq!(expr.parse_peek(input), expected);
let input = " 2* ( 3 + 4 ) ";
let expected = Ok(("", 14));
assert_eq!(expr.parse_peek(input), expected);
let input = " 2*2 / ( 5 - 1) + 3";
let expected = Ok(("", 4));
assert_eq!(expr.parse_peek(input), expected);
}

View file

@ -0,0 +1,182 @@
use std::fmt;
use std::fmt::{Debug, Display, Formatter};
use std::str::FromStr;
use winnow::prelude::*;
use winnow::{
ascii::{digit1 as digits, multispace0 as multispaces},
combinator::alt,
combinator::delimited,
combinator::repeat,
token::one_of,
};
#[derive(Debug, Clone)]
pub enum Expr {
Value(i64),
Add(Box<Expr>, Box<Expr>),
Sub(Box<Expr>, Box<Expr>),
Mul(Box<Expr>, Box<Expr>),
Div(Box<Expr>, Box<Expr>),
Paren(Box<Expr>),
}
impl Expr {
pub fn eval(&self) -> i64 {
match self {
Self::Value(v) => *v,
Self::Add(lhs, rhs) => lhs.eval() + rhs.eval(),
Self::Sub(lhs, rhs) => lhs.eval() - rhs.eval(),
Self::Mul(lhs, rhs) => lhs.eval() * rhs.eval(),
Self::Div(lhs, rhs) => lhs.eval() / rhs.eval(),
Self::Paren(expr) => expr.eval(),
}
}
}
impl Display for Expr {
fn fmt(&self, format: &mut Formatter<'_>) -> fmt::Result {
use Expr::{Add, Div, Mul, Paren, Sub, Value};
match *self {
Value(val) => write!(format, "{}", val),
Add(ref left, ref right) => write!(format, "{} + {}", left, right),
Sub(ref left, ref right) => write!(format, "{} - {}", left, right),
Mul(ref left, ref right) => write!(format, "{} * {}", left, right),
Div(ref left, ref right) => write!(format, "{} / {}", left, right),
Paren(ref expr) => write!(format, "({})", expr),
}
}
}
pub fn expr(i: &mut &str) -> PResult<Expr> {
let init = term.parse_next(i)?;
repeat(0.., (one_of(['+', '-']), term))
.fold(
move || init.clone(),
|acc, (op, val): (char, Expr)| {
if op == '+' {
Expr::Add(Box::new(acc), Box::new(val))
} else {
Expr::Sub(Box::new(acc), Box::new(val))
}
},
)
.parse_next(i)
}
fn term(i: &mut &str) -> PResult<Expr> {
let init = factor.parse_next(i)?;
repeat(0.., (one_of(['*', '/']), factor))
.fold(
move || init.clone(),
|acc, (op, val): (char, Expr)| {
if op == '*' {
Expr::Mul(Box::new(acc), Box::new(val))
} else {
Expr::Div(Box::new(acc), Box::new(val))
}
},
)
.parse_next(i)
}
fn factor(i: &mut &str) -> PResult<Expr> {
delimited(
multispaces,
alt((digits.try_map(FromStr::from_str).map(Expr::Value), parens)),
multispaces,
)
.parse_next(i)
}
fn parens(i: &mut &str) -> PResult<Expr> {
delimited("(", expr, ")")
.map(|e| Expr::Paren(Box::new(e)))
.parse_next(i)
}
#[test]
fn factor_test() {
let input = "3";
let expected = Ok(("", String::from("Value(3)")));
assert_eq!(factor.map(|e| format!("{e:?}")).parse_peek(input), expected);
let input = " 12";
let expected = Ok(("", String::from("Value(12)")));
assert_eq!(factor.map(|e| format!("{e:?}")).parse_peek(input), expected);
let input = "537 ";
let expected = Ok(("", String::from("Value(537)")));
assert_eq!(factor.map(|e| format!("{e:?}")).parse_peek(input), expected);
let input = " 24 ";
let expected = Ok(("", String::from("Value(24)")));
assert_eq!(factor.map(|e| format!("{e:?}")).parse_peek(input), expected);
}
#[test]
fn term_test() {
let input = " 12 *2 / 3";
let expected = Ok(("", String::from("Div(Mul(Value(12), Value(2)), Value(3))")));
assert_eq!(term.map(|e| format!("{e:?}")).parse_peek(input), expected);
let input = " 12 *2 / 3";
let expected = Ok(("", String::from("Div(Mul(Value(12), Value(2)), Value(3))")));
assert_eq!(term.map(|e| format!("{e:?}")).parse_peek(input), expected);
let input = " 2* 3 *2 *2 / 3";
let expected = Ok((
"",
String::from("Div(Mul(Mul(Mul(Value(2), Value(3)), Value(2)), Value(2)), Value(3))"),
));
assert_eq!(term.map(|e| format!("{e:?}")).parse_peek(input), expected);
let input = " 48 / 3/2";
let expected = Ok(("", String::from("Div(Div(Value(48), Value(3)), Value(2))")));
assert_eq!(term.map(|e| format!("{e:?}")).parse_peek(input), expected);
}
#[test]
fn expr_test() {
let input = " 1 + 2 ";
let expected = Ok(("", String::from("Add(Value(1), Value(2))")));
assert_eq!(expr.map(|e| format!("{e:?}")).parse_peek(input), expected);
let input = " 12 + 6 - 4+ 3";
let expected = Ok((
"",
String::from("Add(Sub(Add(Value(12), Value(6)), Value(4)), Value(3))"),
));
assert_eq!(expr.map(|e| format!("{e:?}")).parse_peek(input), expected);
let input = " 1 + 2*3 + 4";
let expected = Ok((
"",
String::from("Add(Add(Value(1), Mul(Value(2), Value(3))), Value(4))"),
));
assert_eq!(expr.map(|e| format!("{e:?}")).parse_peek(input), expected);
}
#[test]
fn parens_test() {
let input = " ( 2 )";
let expected = Ok(("", String::from("Paren(Value(2))")));
assert_eq!(expr.map(|e| format!("{e:?}")).parse_peek(input), expected);
let input = " 2* ( 3 + 4 ) ";
let expected = Ok((
"",
String::from("Mul(Value(2), Paren(Add(Value(3), Value(4))))"),
));
assert_eq!(expr.map(|e| format!("{e:?}")).parse_peek(input), expected);
let input = " 2*2 / ( 5 - 1) + 3";
let expected = Ok((
"",
String::from("Add(Div(Mul(Value(2), Value(2)), Paren(Sub(Value(5), Value(1)))), Value(3))"),
));
assert_eq!(expr.map(|e| format!("{e:?}")).parse_peek(input), expected);
}

View file

@ -0,0 +1,300 @@
use std::fmt;
use std::fmt::{Debug, Display, Formatter};
use std::str::FromStr;
use winnow::prelude::*;
use winnow::{
ascii::{digit1 as digits, multispace0 as multispaces},
combinator::alt,
combinator::dispatch,
combinator::fail,
combinator::peek,
combinator::repeat,
combinator::{delimited, preceded, terminated},
token::any,
token::one_of,
};
#[derive(Debug, Clone)]
pub enum Expr {
Value(i64),
Add(Box<Expr>, Box<Expr>),
Sub(Box<Expr>, Box<Expr>),
Mul(Box<Expr>, Box<Expr>),
Div(Box<Expr>, Box<Expr>),
Paren(Box<Expr>),
}
impl Expr {
pub fn eval(&self) -> i64 {
match self {
Self::Value(v) => *v,
Self::Add(lhs, rhs) => lhs.eval() + rhs.eval(),
Self::Sub(lhs, rhs) => lhs.eval() - rhs.eval(),
Self::Mul(lhs, rhs) => lhs.eval() * rhs.eval(),
Self::Div(lhs, rhs) => lhs.eval() / rhs.eval(),
Self::Paren(expr) => expr.eval(),
}
}
}
impl Display for Expr {
fn fmt(&self, format: &mut Formatter<'_>) -> fmt::Result {
use Expr::{Add, Div, Mul, Paren, Sub, Value};
match *self {
Value(val) => write!(format, "{}", val),
Add(ref left, ref right) => write!(format, "{} + {}", left, right),
Sub(ref left, ref right) => write!(format, "{} - {}", left, right),
Mul(ref left, ref right) => write!(format, "{} * {}", left, right),
Div(ref left, ref right) => write!(format, "{} / {}", left, right),
Paren(ref expr) => write!(format, "({})", expr),
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Token {
Value(i64),
Oper(Oper),
OpenParen,
CloseParen,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Oper {
Add,
Sub,
Mul,
Div,
}
impl winnow::stream::ContainsToken<Token> for Token {
#[inline(always)]
fn contains_token(&self, token: Token) -> bool {
*self == token
}
}
impl winnow::stream::ContainsToken<Token> for &'_ [Token] {
#[inline]
fn contains_token(&self, token: Token) -> bool {
self.iter().any(|t| *t == token)
}
}
impl<const LEN: usize> winnow::stream::ContainsToken<Token> for &'_ [Token; LEN] {
#[inline]
fn contains_token(&self, token: Token) -> bool {
self.iter().any(|t| *t == token)
}
}
impl<const LEN: usize> winnow::stream::ContainsToken<Token> for [Token; LEN] {
#[inline]
fn contains_token(&self, token: Token) -> bool {
self.iter().any(|t| *t == token)
}
}
#[allow(dead_code)]
pub fn expr2(i: &mut &str) -> PResult<Expr> {
let tokens = lex.parse_next(i)?;
expr.parse_next(&mut tokens.as_slice())
}
pub fn lex(i: &mut &str) -> PResult<Vec<Token>> {
preceded(multispaces, repeat(1.., terminated(token, multispaces))).parse_next(i)
}
fn token(i: &mut &str) -> PResult<Token> {
dispatch! {peek(any);
'0'..='9' => digits.try_map(FromStr::from_str).map(Token::Value),
'(' => '('.value(Token::OpenParen),
')' => ')'.value(Token::CloseParen),
'+' => '+'.value(Token::Oper(Oper::Add)),
'-' => '-'.value(Token::Oper(Oper::Sub)),
'*' => '*'.value(Token::Oper(Oper::Mul)),
'/' => '/'.value(Token::Oper(Oper::Div)),
_ => fail,
}
.parse_next(i)
}
pub fn expr(i: &mut &[Token]) -> PResult<Expr> {
let init = term.parse_next(i)?;
repeat(
0..,
(
one_of([Token::Oper(Oper::Add), Token::Oper(Oper::Sub)]),
term,
),
)
.fold(
move || init.clone(),
|acc, (op, val): (Token, Expr)| {
if op == Token::Oper(Oper::Add) {
Expr::Add(Box::new(acc), Box::new(val))
} else {
Expr::Sub(Box::new(acc), Box::new(val))
}
},
)
.parse_next(i)
}
fn term(i: &mut &[Token]) -> PResult<Expr> {
let init = factor.parse_next(i)?;
repeat(
0..,
(
one_of([Token::Oper(Oper::Mul), Token::Oper(Oper::Div)]),
factor,
),
)
.fold(
move || init.clone(),
|acc, (op, val): (Token, Expr)| {
if op == Token::Oper(Oper::Mul) {
Expr::Mul(Box::new(acc), Box::new(val))
} else {
Expr::Div(Box::new(acc), Box::new(val))
}
},
)
.parse_next(i)
}
fn factor(i: &mut &[Token]) -> PResult<Expr> {
alt((
one_of(|t| matches!(t, Token::Value(_))).map(|t| match t {
Token::Value(v) => Expr::Value(v),
_ => unreachable!(),
}),
parens,
))
.parse_next(i)
}
fn parens(i: &mut &[Token]) -> PResult<Expr> {
delimited(one_of(Token::OpenParen), expr, one_of(Token::CloseParen))
.map(|e| Expr::Paren(Box::new(e)))
.parse_next(i)
}
#[test]
fn lex_test() {
let input = "3";
let expected = Ok(String::from(r#"("", [Value(3)])"#));
assert_eq!(lex.parse_peek(input).map(|e| format!("{e:?}")), expected);
let input = " 24 ";
let expected = Ok(String::from(r#"("", [Value(24)])"#));
assert_eq!(lex.parse_peek(input).map(|e| format!("{e:?}")), expected);
let input = " 12 *2 / 3";
let expected = Ok(String::from(
r#"("", [Value(12), Oper(Mul), Value(2), Oper(Div), Value(3)])"#,
));
assert_eq!(lex.parse_peek(input).map(|e| format!("{e:?}")), expected);
let input = " 2*2 / ( 5 - 1) + 3";
let expected = Ok(String::from(
r#"("", [Value(2), Oper(Mul), Value(2), Oper(Div), OpenParen, Value(5), Oper(Sub), Value(1), CloseParen, Oper(Add), Value(3)])"#,
));
assert_eq!(lex.parse_peek(input).map(|e| format!("{e:?}")), expected);
}
#[test]
fn factor_test() {
let input = "3";
let expected = Ok(String::from("Value(3)"));
let input = lex.parse(input).unwrap();
assert_eq!(factor.map(|e| format!("{e:?}")).parse(&input), expected);
let input = " 12";
let expected = Ok(String::from("Value(12)"));
let input = lex.parse(input).unwrap();
assert_eq!(factor.map(|e| format!("{e:?}")).parse(&input), expected);
let input = "537 ";
let expected = Ok(String::from("Value(537)"));
let input = lex.parse(input).unwrap();
assert_eq!(factor.map(|e| format!("{e:?}")).parse(&input), expected);
let input = " 24 ";
let expected = Ok(String::from("Value(24)"));
let input = lex.parse(input).unwrap();
assert_eq!(factor.map(|e| format!("{e:?}")).parse(&input), expected);
}
#[test]
fn term_test() {
let input = " 12 *2 / 3";
let expected = Ok(String::from("Div(Mul(Value(12), Value(2)), Value(3))"));
let input = lex.parse(input).unwrap();
assert_eq!(term.map(|e| format!("{e:?}")).parse(&input), expected);
let input = " 12 *2 / 3";
let expected = Ok(String::from("Div(Mul(Value(12), Value(2)), Value(3))"));
let input = lex.parse(input).unwrap();
assert_eq!(term.map(|e| format!("{e:?}")).parse(&input), expected);
let input = " 2* 3 *2 *2 / 3";
let expected = Ok(String::from(
"Div(Mul(Mul(Mul(Value(2), Value(3)), Value(2)), Value(2)), Value(3))",
));
let input = lex.parse(input).unwrap();
assert_eq!(term.map(|e| format!("{e:?}")).parse(&input), expected);
let input = " 48 / 3/2";
let expected = Ok(String::from("Div(Div(Value(48), Value(3)), Value(2))"));
let input = lex.parse(input).unwrap();
assert_eq!(term.map(|e| format!("{e:?}")).parse(&input), expected);
}
#[test]
fn expr_test() {
let input = " 1 + 2 ";
let expected = Ok(String::from("Add(Value(1), Value(2))"));
let input = lex.parse(input).unwrap();
assert_eq!(expr.map(|e| format!("{e:?}")).parse(&input), expected);
let input = " 12 + 6 - 4+ 3";
let expected = Ok(String::from(
"Add(Sub(Add(Value(12), Value(6)), Value(4)), Value(3))",
));
let input = lex.parse(input).unwrap();
assert_eq!(expr.map(|e| format!("{e:?}")).parse(&input), expected);
let input = " 1 + 2*3 + 4";
let expected = Ok(String::from(
"Add(Add(Value(1), Mul(Value(2), Value(3))), Value(4))",
));
let input = lex.parse(input).unwrap();
assert_eq!(expr.map(|e| format!("{e:?}")).parse(&input), expected);
}
#[test]
fn parens_test() {
let input = " ( 2 )";
let expected = Ok(String::from("Paren(Value(2))"));
let input = lex.parse(input).unwrap();
assert_eq!(expr.map(|e| format!("{e:?}")).parse(&input), expected);
let input = " 2* ( 3 + 4 ) ";
let expected = Ok(String::from(
"Mul(Value(2), Paren(Add(Value(3), Value(4))))",
));
let input = lex.parse(input).unwrap();
assert_eq!(expr.map(|e| format!("{e:?}")).parse(&input), expected);
let input = " 2*2 / ( 5 - 1) + 3";
let expected = Ok(String::from(
"Add(Div(Mul(Value(2), Value(2)), Paren(Sub(Value(5), Value(1)))), Value(3))",
));
let input = lex.parse(input).unwrap();
assert_eq!(expr.map(|e| format!("{e:?}")).parse(&input), expected);
}

View file

@ -0,0 +1,62 @@
use winnow::prelude::*;
mod parser;
use parser::hex_color;
fn main() -> Result<(), lexopt::Error> {
let args = Args::parse()?;
let input = args.input.as_deref().unwrap_or("#AAAAAA");
println!("{} =", input);
match hex_color.parse(input) {
Ok(result) => {
println!(" {:?}", result);
}
Err(err) => {
println!(" {}", err);
}
}
Ok(())
}
#[derive(Default)]
struct Args {
input: Option<String>,
}
impl Args {
fn parse() -> Result<Self, lexopt::Error> {
use lexopt::prelude::*;
let mut res = Args::default();
let mut args = lexopt::Parser::from_env();
while let Some(arg) = args.next()? {
match arg {
Value(input) => {
res.input = Some(input.string()?);
}
_ => return Err(arg.unexpected()),
}
}
Ok(res)
}
}
#[test]
fn parse_color() {
assert_eq!(
hex_color.parse_peek("#2F14DF"),
Ok((
"",
parser::Color {
red: 47,
green: 20,
blue: 223,
}
))
);
}

View file

@ -0,0 +1,35 @@
use winnow::combinator::seq;
use winnow::prelude::*;
use winnow::token::take_while;
#[derive(Debug, Eq, PartialEq)]
pub struct Color {
pub red: u8,
pub green: u8,
pub blue: u8,
}
impl std::str::FromStr for Color {
// The error must be owned
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
hex_color.parse(s).map_err(|e| e.to_string())
}
}
pub fn hex_color(input: &mut &str) -> PResult<Color> {
seq!(Color {
_: '#',
red: hex_primary,
green: hex_primary,
blue: hex_primary
})
.parse_next(input)
}
fn hex_primary(input: &mut &str) -> PResult<u8> {
take_while(2, |c: char| c.is_ascii_hexdigit())
.try_map(|input| u8::from_str_radix(input, 16))
.parse_next(input)
}

View file

@ -0,0 +1,40 @@
use winnow::error::ErrMode;
use winnow::error::ErrorKind;
use winnow::error::ParserError;
use winnow::prelude::*;
#[derive(Debug, PartialEq, Eq)]
pub enum CustomError<I> {
MyError,
Nom(I, ErrorKind),
}
impl<I: Clone> ParserError<I> for CustomError<I> {
fn from_error_kind(input: &I, kind: ErrorKind) -> Self {
CustomError::Nom(input.clone(), kind)
}
fn append(self, _: &I, _: ErrorKind) -> Self {
self
}
}
pub fn parse<'s>(_input: &mut &'s str) -> PResult<&'s str, CustomError<&'s str>> {
Err(ErrMode::Backtrack(CustomError::MyError))
}
fn main() {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let err = parse.parse_next(&mut "").unwrap_err();
match err {
ErrMode::Backtrack(e) => assert_eq!(e, CustomError::MyError),
_ => panic!("Unexpected error: {:?}", err),
}
}
}

View file

@ -0,0 +1,36 @@
mod parser;
mod parser_streaming;
fn one_test(c: &mut criterion::Criterion) {
let data = &b"GET / HTTP/1.1
Host: www.reddit.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:15.0) Gecko/20100101 Firefox/15.0.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
"[..];
let mut http_group = c.benchmark_group("http");
http_group.throughput(criterion::Throughput::Bytes(data.len() as u64));
http_group.bench_with_input(
criterion::BenchmarkId::new("complete", data.len()),
data,
|b, data| {
b.iter(|| parser::parse(data).unwrap());
},
);
http_group.bench_with_input(
criterion::BenchmarkId::new("streaming", data.len()),
data,
|b, data| {
b.iter(|| parser_streaming::parse(data).unwrap());
},
);
http_group.finish();
}
criterion::criterion_group!(http, one_test);
criterion::criterion_main!(http);

View file

@ -0,0 +1,47 @@
mod parser;
fn main() -> Result<(), lexopt::Error> {
let args = Args::parse()?;
let input = args.input.as_deref().unwrap_or(
"GET / HTTP/1.1
Host: www.reddit.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:15.0) Gecko/20100101 Firefox/15.0.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
",
);
if let Some(result) = parser::parse(input.as_bytes()) {
println!(" {:#?}", result);
}
Ok(())
}
#[derive(Default)]
struct Args {
input: Option<String>,
}
impl Args {
fn parse() -> Result<Self, lexopt::Error> {
use lexopt::prelude::*;
let mut res = Args::default();
let mut args = lexopt::Parser::from_env();
while let Some(arg) = args.next()? {
match arg {
Value(input) => {
res.input = Some(input.string()?);
}
_ => return Err(arg.unexpected()),
}
}
Ok(res)
}
}

View file

@ -0,0 +1,137 @@
use winnow::combinator::seq;
use winnow::prelude::*;
use winnow::{ascii::line_ending, combinator::repeat, token::take_while};
pub type Stream<'i> = &'i [u8];
#[rustfmt::skip]
#[derive(Debug)]
#[allow(dead_code)]
pub struct Request<'a> {
method: &'a [u8],
uri: &'a [u8],
version: &'a [u8],
}
#[derive(Debug)]
#[allow(dead_code)]
pub struct Header<'a> {
name: &'a [u8],
value: Vec<&'a [u8]>,
}
pub fn parse(data: &[u8]) -> Option<Vec<(Request<'_>, Vec<Header<'_>>)>> {
let mut buf = data;
let mut v = Vec::new();
loop {
match request(&mut buf) {
Ok(r) => {
v.push(r);
if buf.is_empty() {
//println!("{}", i);
break;
}
}
Err(e) => {
println!("error: {:?}", e);
return None;
}
}
}
Some(v)
}
fn request<'s>(input: &mut Stream<'s>) -> PResult<(Request<'s>, Vec<Header<'s>>)> {
let req = request_line(input)?;
let h = repeat(1.., message_header).parse_next(input)?;
let _ = line_ending.parse_next(input)?;
Ok((req, h))
}
fn request_line<'s>(input: &mut Stream<'s>) -> PResult<Request<'s>> {
seq!( Request {
method: take_while(1.., is_token),
_: take_while(1.., is_space),
uri: take_while(1.., is_not_space),
_: take_while(1.., is_space),
version: http_version,
_: line_ending,
})
.parse_next(input)
}
fn http_version<'s>(input: &mut Stream<'s>) -> PResult<&'s [u8]> {
let _ = "HTTP/".parse_next(input)?;
let version = take_while(1.., is_version).parse_next(input)?;
Ok(version)
}
fn message_header_value<'s>(input: &mut Stream<'s>) -> PResult<&'s [u8]> {
let _ = take_while(1.., is_horizontal_space).parse_next(input)?;
let data = take_while(1.., till_line_ending).parse_next(input)?;
let _ = line_ending.parse_next(input)?;
Ok(data)
}
fn message_header<'s>(input: &mut Stream<'s>) -> PResult<Header<'s>> {
seq!(Header {
name: take_while(1.., is_token),
_: ':',
value: repeat(1.., message_header_value),
})
.parse_next(input)
}
#[rustfmt::skip]
#[allow(clippy::match_same_arms)]
#[allow(clippy::match_like_matches_macro)]
fn is_token(c: u8) -> bool {
match c {
128..=255 => false,
0..=31 => false,
b'(' => false,
b')' => false,
b'<' => false,
b'>' => false,
b'@' => false,
b',' => false,
b';' => false,
b':' => false,
b'\\' => false,
b'"' => false,
b'/' => false,
b'[' => false,
b']' => false,
b'?' => false,
b'=' => false,
b'{' => false,
b'}' => false,
b' ' => false,
_ => true,
}
}
fn is_version(c: u8) -> bool {
c.is_ascii_digit() || c == b'.'
}
fn till_line_ending(c: u8) -> bool {
c != b'\r' && c != b'\n'
}
fn is_space(c: u8) -> bool {
c == b' '
}
fn is_not_space(c: u8) -> bool {
c != b' '
}
fn is_horizontal_space(c: u8) -> bool {
c == b' ' || c == b'\t'
}

View file

@ -0,0 +1,138 @@
use winnow::combinator::seq;
use winnow::{
ascii::line_ending, combinator::repeat, prelude::*, stream::Partial, token::take_while,
};
pub type Stream<'i> = Partial<&'i [u8]>;
#[rustfmt::skip]
#[derive(Debug)]
#[allow(dead_code)]
pub struct Request<'a> {
method: &'a [u8],
uri: &'a [u8],
version: &'a [u8],
}
#[derive(Debug)]
#[allow(dead_code)]
pub struct Header<'a> {
name: &'a [u8],
value: Vec<&'a [u8]>,
}
pub fn parse(data: &[u8]) -> Option<Vec<(Request<'_>, Vec<Header<'_>>)>> {
let mut buf = Partial::new(data);
let mut v = Vec::new();
loop {
match request(&mut buf) {
Ok(r) => {
v.push(r);
if buf.is_empty() {
//println!("{}", i);
break;
}
}
Err(e) => {
println!("error: {:?}", e);
return None;
}
}
}
Some(v)
}
fn request<'s>(input: &mut Stream<'s>) -> PResult<(Request<'s>, Vec<Header<'s>>)> {
let req = request_line(input)?;
let h = repeat(1.., message_header).parse_next(input)?;
let _ = line_ending.parse_next(input)?;
Ok((req, h))
}
fn request_line<'s>(input: &mut Stream<'s>) -> PResult<Request<'s>> {
seq!( Request {
method: take_while(1.., is_token),
_: take_while(1.., is_space),
uri: take_while(1.., is_not_space),
_: take_while(1.., is_space),
version: http_version,
_: line_ending,
})
.parse_next(input)
}
fn http_version<'s>(input: &mut Stream<'s>) -> PResult<&'s [u8]> {
let _ = "HTTP/".parse_next(input)?;
let version = take_while(1.., is_version).parse_next(input)?;
Ok(version)
}
fn message_header_value<'s>(input: &mut Stream<'s>) -> PResult<&'s [u8]> {
let _ = take_while(1.., is_horizontal_space).parse_next(input)?;
let data = take_while(1.., till_line_ending).parse_next(input)?;
let _ = line_ending.parse_next(input)?;
Ok(data)
}
fn message_header<'s>(input: &mut Stream<'s>) -> PResult<Header<'s>> {
seq!(Header {
name: take_while(1.., is_token),
_: ':',
value: repeat(1.., message_header_value),
})
.parse_next(input)
}
#[rustfmt::skip]
#[allow(clippy::match_same_arms)]
#[allow(clippy::match_like_matches_macro)]
fn is_token(c: u8) -> bool {
match c {
128..=255 => false,
0..=31 => false,
b'(' => false,
b')' => false,
b'<' => false,
b'>' => false,
b'@' => false,
b',' => false,
b';' => false,
b':' => false,
b'\\' => false,
b'"' => false,
b'/' => false,
b'[' => false,
b']' => false,
b'?' => false,
b'=' => false,
b'{' => false,
b'}' => false,
b' ' => false,
_ => true,
}
}
fn is_version(c: u8) -> bool {
c.is_ascii_digit() || c == b'.'
}
fn till_line_ending(c: u8) -> bool {
c != b'\r' && c != b'\n'
}
fn is_space(c: u8) -> bool {
c == b' '
}
fn is_not_space(c: u8) -> bool {
c != b' '
}
fn is_horizontal_space(c: u8) -> bool {
c == b' ' || c == b'\t'
}

View file

@ -0,0 +1,61 @@
use winnow::combinator::repeat;
use winnow::prelude::*;
mod parser;
mod parser_str;
fn bench_ini(c: &mut criterion::Criterion) {
let str = "[owner]
name=John Doe
organization=Acme Widgets Inc.
[database]
server=192.0.2.62
port=143
file=payroll.dat
\0";
let mut group = c.benchmark_group("ini");
group.throughput(criterion::Throughput::Bytes(str.len() as u64));
group.bench_function(criterion::BenchmarkId::new("bytes", str.len()), |b| {
b.iter(|| parser::categories.parse_peek(str.as_bytes()).unwrap());
});
group.bench_function(criterion::BenchmarkId::new("str", str.len()), |b| {
b.iter(|| parser_str::categories.parse_peek(str).unwrap())
});
}
fn bench_ini_keys_and_values(c: &mut criterion::Criterion) {
let str = "server=192.0.2.62
port=143
file=payroll.dat
\0";
fn acc<'s>(i: &mut parser::Stream<'s>) -> PResult<Vec<(&'s str, &'s str)>> {
repeat(0.., parser::key_value).parse_next(i)
}
let mut group = c.benchmark_group("ini keys and values");
group.throughput(criterion::Throughput::Bytes(str.len() as u64));
group.bench_function(criterion::BenchmarkId::new("bytes", str.len()), |b| {
b.iter(|| acc.parse_peek(str.as_bytes()).unwrap());
});
}
fn bench_ini_key_value(c: &mut criterion::Criterion) {
let str = "server=192.0.2.62\n";
let mut group = c.benchmark_group("ini key value");
group.throughput(criterion::Throughput::Bytes(str.len() as u64));
group.bench_function(criterion::BenchmarkId::new("bytes", str.len()), |b| {
b.iter(|| parser::key_value.parse_peek(str.as_bytes()).unwrap());
});
}
criterion::criterion_group!(
benches,
bench_ini,
bench_ini_keys_and_values,
bench_ini_key_value
);
criterion::criterion_main!(benches);

View file

@ -0,0 +1,60 @@
use winnow::prelude::*;
mod parser;
mod parser_str;
fn main() -> Result<(), lexopt::Error> {
let args = Args::parse()?;
let input = args.input.as_deref().unwrap_or("1 + 1");
if args.binary {
match parser::categories.parse(input.as_bytes()) {
Ok(result) => {
println!(" {:?}", result);
}
Err(err) => {
println!(" {:?}", err);
}
}
} else {
match parser_str::categories.parse(input) {
Ok(result) => {
println!(" {:?}", result);
}
Err(err) => {
println!(" {}", err);
}
}
}
Ok(())
}
#[derive(Default)]
struct Args {
input: Option<String>,
binary: bool,
}
impl Args {
fn parse() -> Result<Self, lexopt::Error> {
use lexopt::prelude::*;
let mut res = Args::default();
let mut args = lexopt::Parser::from_env();
while let Some(arg) = args.next()? {
match arg {
Long("binary") => {
res.binary = true;
}
Value(input) => {
res.input = Some(input.string()?);
}
_ => return Err(arg.unexpected()),
}
}
Ok(res)
}
}

View file

@ -0,0 +1,146 @@
use std::collections::HashMap;
use std::str;
use winnow::prelude::*;
use winnow::{
ascii::{alphanumeric1 as alphanumeric, multispace0 as multispace, space0 as space},
combinator::opt,
combinator::repeat,
combinator::{delimited, separated_pair, terminated},
token::take_while,
};
pub type Stream<'i> = &'i [u8];
pub fn categories<'s>(i: &mut Stream<'s>) -> PResult<HashMap<&'s str, HashMap<&'s str, &'s str>>> {
repeat(
0..,
separated_pair(
category,
opt(multispace),
repeat(0.., terminated(key_value, opt(multispace))),
),
)
.parse_next(i)
}
fn category<'s>(i: &mut Stream<'s>) -> PResult<&'s str> {
delimited('[', take_while(0.., |c| c != b']'), ']')
.try_map(str::from_utf8)
.parse_next(i)
}
pub fn key_value<'s>(i: &mut Stream<'s>) -> PResult<(&'s str, &'s str)> {
let key = alphanumeric.try_map(str::from_utf8).parse_next(i)?;
let _ = (opt(space), '=', opt(space)).parse_next(i)?;
let val = take_while(0.., |c| c != b'\n' && c != b';')
.try_map(str::from_utf8)
.parse_next(i)?;
let _ = opt((';', take_while(0.., |c| c != b'\n'))).parse_next(i)?;
Ok((key, val))
}
#[test]
fn parse_category_test() {
let ini_file = &b"[category]
parameter=value
key = value2"[..];
let ini_without_category = &b"\n\nparameter=value
key = value2"[..];
let res = category.parse_peek(ini_file);
println!("{:?}", res);
match res {
Ok((i, o)) => println!("i: {:?} | o: {:?}", str::from_utf8(i), o),
_ => println!("error"),
}
assert_eq!(res, Ok((ini_without_category, "category")));
}
#[test]
fn parse_key_value_test() {
let ini_file = &b"parameter=value
key = value2"[..];
let ini_without_key_value = &b"\nkey = value2"[..];
let res = key_value.parse_peek(ini_file);
println!("{:?}", res);
match res {
Ok((i, (o1, o2))) => println!("i: {:?} | o: ({:?},{:?})", str::from_utf8(i), o1, o2),
_ => println!("error"),
}
assert_eq!(res, Ok((ini_without_key_value, ("parameter", "value"))));
}
#[test]
fn parse_key_value_with_space_test() {
let ini_file = &b"parameter = value
key = value2"[..];
let ini_without_key_value = &b"\nkey = value2"[..];
let res = key_value.parse_peek(ini_file);
println!("{:?}", res);
match res {
Ok((i, (o1, o2))) => println!("i: {:?} | o: ({:?},{:?})", str::from_utf8(i), o1, o2),
_ => println!("error"),
}
assert_eq!(res, Ok((ini_without_key_value, ("parameter", "value"))));
}
#[test]
fn parse_key_value_with_comment_test() {
let ini_file = &b"parameter=value;abc
key = value2"[..];
let ini_without_key_value = &b"\nkey = value2"[..];
let res = key_value.parse_peek(ini_file);
println!("{:?}", res);
match res {
Ok((i, (o1, o2))) => println!("i: {:?} | o: ({:?},{:?})", str::from_utf8(i), o1, o2),
_ => println!("error"),
}
assert_eq!(res, Ok((ini_without_key_value, ("parameter", "value"))));
}
#[test]
fn parse_multiple_categories_test() {
let ini_file = &b"[abcd]
parameter=value;abc
key = value2
[category]
parameter3=value3
key4 = value4
"[..];
let ini_after_parser = &b""[..];
let res = categories.parse_peek(ini_file);
//println!("{:?}", res);
match res {
Ok((i, ref o)) => println!("i: {:?} | o: {:?}", str::from_utf8(i), o),
_ => println!("error"),
}
let mut expected_1: HashMap<&str, &str> = HashMap::new();
expected_1.insert("parameter", "value");
expected_1.insert("key", "value2");
let mut expected_2: HashMap<&str, &str> = HashMap::new();
expected_2.insert("parameter3", "value3");
expected_2.insert("key4", "value4");
let mut expected_h: HashMap<&str, HashMap<&str, &str>> = HashMap::new();
expected_h.insert("abcd", expected_1);
expected_h.insert("category", expected_2);
assert_eq!(res, Ok((ini_after_parser, expected_h)));
}

View file

@ -0,0 +1,208 @@
use std::collections::HashMap;
use winnow::prelude::*;
use winnow::{
ascii::{alphanumeric1 as alphanumeric, space0 as space},
combinator::opt,
combinator::repeat,
combinator::{delimited, terminated},
token::{take_till, take_while},
};
pub type Stream<'i> = &'i str;
pub fn categories<'s>(
input: &mut Stream<'s>,
) -> PResult<HashMap<&'s str, HashMap<&'s str, &'s str>>> {
repeat(0.., category_and_keys).parse_next(input)
}
fn category_and_keys<'s>(i: &mut Stream<'s>) -> PResult<(&'s str, HashMap<&'s str, &'s str>)> {
(category, keys_and_values).parse_next(i)
}
fn category<'s>(i: &mut Stream<'s>) -> PResult<&'s str> {
terminated(
delimited('[', take_while(0.., |c| c != ']'), ']'),
opt(take_while(1.., [' ', '\r', '\n'])),
)
.parse_next(i)
}
fn keys_and_values<'s>(input: &mut Stream<'s>) -> PResult<HashMap<&'s str, &'s str>> {
repeat(0.., key_value).parse_next(input)
}
fn key_value<'s>(i: &mut Stream<'s>) -> PResult<(&'s str, &'s str)> {
let key = alphanumeric.parse_next(i)?;
let _ = (opt(space), "=", opt(space)).parse_next(i)?;
let val = take_till(0.., is_line_ending_or_comment).parse_next(i)?;
let _ = opt(space).parse_next(i)?;
let _ = opt((";", till_line_ending)).parse_next(i)?;
let _ = opt(space_or_line_ending).parse_next(i)?;
Ok((key, val))
}
fn is_line_ending_or_comment(chr: char) -> bool {
chr == ';' || chr == '\n'
}
fn till_line_ending<'s>(i: &mut Stream<'s>) -> PResult<&'s str> {
take_while(0.., |c| c != '\r' && c != '\n').parse_next(i)
}
fn space_or_line_ending<'s>(i: &mut Stream<'s>) -> PResult<&'s str> {
take_while(1.., [' ', '\r', '\n']).parse_next(i)
}
#[test]
fn parse_category_test() {
let ini_file = "[category]
parameter=value
key = value2";
let ini_without_category = "parameter=value
key = value2";
let res = category.parse_peek(ini_file);
println!("{:?}", res);
match res {
Ok((i, o)) => println!("i: {} | o: {:?}", i, o),
_ => println!("error"),
}
assert_eq!(res, Ok((ini_without_category, "category")));
}
#[test]
fn parse_key_value_test() {
let ini_file = "parameter=value
key = value2";
let ini_without_key_value = "key = value2";
let res = key_value.parse_peek(ini_file);
println!("{:?}", res);
match res {
Ok((i, (o1, o2))) => println!("i: {} | o: ({:?},{:?})", i, o1, o2),
_ => println!("error"),
}
assert_eq!(res, Ok((ini_without_key_value, ("parameter", "value"))));
}
#[test]
fn parse_key_value_with_space_test() {
let ini_file = "parameter = value
key = value2";
let ini_without_key_value = "key = value2";
let res = key_value.parse_peek(ini_file);
println!("{:?}", res);
match res {
Ok((i, (o1, o2))) => println!("i: {} | o: ({:?},{:?})", i, o1, o2),
_ => println!("error"),
}
assert_eq!(res, Ok((ini_without_key_value, ("parameter", "value"))));
}
#[test]
fn parse_key_value_with_comment_test() {
let ini_file = "parameter=value;abc
key = value2";
let ini_without_key_value = "key = value2";
let res = key_value.parse_peek(ini_file);
println!("{:?}", res);
match res {
Ok((i, (o1, o2))) => println!("i: {} | o: ({:?},{:?})", i, o1, o2),
_ => println!("error"),
}
assert_eq!(res, Ok((ini_without_key_value, ("parameter", "value"))));
}
#[test]
fn parse_multiple_keys_and_values_test() {
let ini_file = "parameter=value;abc
key = value2
[category]";
let ini_without_key_value = "[category]";
let res = keys_and_values.parse_peek(ini_file);
println!("{:?}", res);
match res {
Ok((i, ref o)) => println!("i: {} | o: {:?}", i, o),
_ => println!("error"),
}
let mut expected: HashMap<&str, &str> = HashMap::new();
expected.insert("parameter", "value");
expected.insert("key", "value2");
assert_eq!(res, Ok((ini_without_key_value, expected)));
}
#[test]
fn parse_category_then_multiple_keys_and_values_test() {
//FIXME: there can be an empty line or a comment line after a category
let ini_file = "[abcd]
parameter=value;abc
key = value2
[category]";
let ini_after_parser = "[category]";
let res = category_and_keys.parse_peek(ini_file);
println!("{:?}", res);
match res {
Ok((i, ref o)) => println!("i: {} | o: {:?}", i, o),
_ => println!("error"),
}
let mut expected_h: HashMap<&str, &str> = HashMap::new();
expected_h.insert("parameter", "value");
expected_h.insert("key", "value2");
assert_eq!(res, Ok((ini_after_parser, ("abcd", expected_h))));
}
#[test]
fn parse_multiple_categories_test() {
let ini_file = "[abcd]
parameter=value;abc
key = value2
[category]
parameter3=value3
key4 = value4
";
let res = categories.parse_peek(ini_file);
//println!("{:?}", res);
match res {
Ok((i, ref o)) => println!("i: {} | o: {:?}", i, o),
_ => println!("error"),
}
let mut expected_1: HashMap<&str, &str> = HashMap::new();
expected_1.insert("parameter", "value");
expected_1.insert("key", "value2");
let mut expected_2: HashMap<&str, &str> = HashMap::new();
expected_2.insert("parameter3", "value3");
expected_2.insert("key4", "value4");
let mut expected_h: HashMap<&str, HashMap<&str, &str>> = HashMap::new();
expected_h.insert("abcd", expected_1);
expected_h.insert("category", expected_2);
assert_eq!(res, Ok(("", expected_h)));
}

View file

@ -0,0 +1,77 @@
use std::collections::HashMap;
use std::iter::Iterator;
use winnow::ascii::alphanumeric1;
use winnow::combinator::iterator;
use winnow::combinator::{separated_pair, terminated};
use winnow::prelude::*;
fn main() {
let mut data = "abcabcabcabc";
fn parser<'s>(i: &mut &'s str) -> PResult<&'s str> {
"abc".parse_next(i)
}
// `from_fn` (available from Rust 1.34) can create an iterator
// from a closure
let it = std::iter::from_fn(move || {
match parser.parse_next(&mut data) {
// when successful, a parser returns a tuple of
// the remaining input and the output value.
// So we replace the captured input data with the
// remaining input, to be parsed on the next call
Ok(o) => Some(o),
_ => None,
}
});
for value in it {
println!("parser returned: {}", value);
}
println!("\n********************\n");
let mut data = "abcabcabcabc";
// if `from_fn` is not available, it is possible to fold
// over an iterator of functions
let res = std::iter::repeat(parser)
.take(3)
.try_fold(Vec::new(), |mut acc, mut parser| {
parser.parse_next(&mut data).map(|o| {
acc.push(o);
acc
})
});
// will print "parser iterator returned: Ok(("abc", ["abc", "abc", "abc"]))"
println!("\nparser iterator returned: {:?}", res);
println!("\n********************\n");
let data = "key1:value1,key2:value2,key3:value3,;";
// `winnow::combinator::iterator` will return an iterator
// producing the parsed values. Compared to the previous
// solutions:
// - we can work with a normal iterator like `from_fn`
// - we can get the remaining input afterwards, like with the `try_fold` trick
let mut winnow_it = iterator(
data,
terminated(separated_pair(alphanumeric1, ":", alphanumeric1), ","),
);
let res = winnow_it
.map(|(k, v)| (k.to_uppercase(), v))
.collect::<HashMap<_, _>>();
let parser_result: PResult<(_, _), ()> = winnow_it.finish();
let (remaining_input, ()) = parser_result.unwrap();
// will print "iterator returned {"key1": "value1", "key3": "value3", "key2": "value2"}, remaining input is ';'"
println!(
"iterator returned {:?}, remaining input is '{}'",
res, remaining_input
);
}

View file

@ -0,0 +1,70 @@
use winnow::prelude::*;
use winnow::Partial;
mod json;
mod parser;
mod parser_dispatch;
mod parser_partial;
fn json_bench(c: &mut criterion::Criterion) {
let data = [("small", SMALL), ("canada", CANADA)];
let mut group = c.benchmark_group("json");
for (name, sample) in data {
let len = sample.len();
group.throughput(criterion::Throughput::Bytes(len as u64));
group.bench_with_input(criterion::BenchmarkId::new("basic", name), &len, |b, _| {
type Error<'i> = winnow::error::InputError<parser::Stream<'i>>;
b.iter(|| parser::json::<Error>.parse_peek(sample).unwrap());
});
group.bench_with_input(criterion::BenchmarkId::new("unit", name), &len, |b, _| {
type Error<'i> = ();
b.iter(|| parser::json::<Error>.parse_peek(sample).unwrap());
});
group.bench_with_input(
criterion::BenchmarkId::new("context", name),
&len,
|b, _| {
type Error<'i> = winnow::error::ContextError<parser::Stream<'i>>;
b.iter(|| parser::json::<Error>.parse_peek(sample).unwrap());
},
);
group.bench_with_input(
criterion::BenchmarkId::new("dispatch", name),
&len,
|b, _| {
type Error<'i> = winnow::error::InputError<parser_dispatch::Stream<'i>>;
b.iter(|| parser_dispatch::json::<Error>.parse_peek(sample).unwrap());
},
);
group.bench_with_input(
criterion::BenchmarkId::new("streaming", name),
&len,
|b, _| {
type Error<'i> = winnow::error::InputError<parser_partial::Stream<'i>>;
b.iter(|| {
parser_partial::json::<Error>
.parse_peek(Partial::new(sample))
.unwrap()
});
},
);
}
group.finish();
}
const SMALL: &str = " { \"a\"\t: 42,
\"b\": [ \"x\", \"y\", 12 ,\"\\u2014\", \"\\uD83D\\uDE10\"] ,
\"c\": { \"hello\" : \"world\"
}
} ";
const CANADA: &str = include_str!("../../third_party/nativejson-benchmark/data/canada.json");
criterion::criterion_group!(benches, json_bench,);
criterion::criterion_main!(benches);

View file

@ -0,0 +1,11 @@
use std::collections::HashMap;
#[derive(Debug, PartialEq, Clone)]
pub enum JsonValue {
Null,
Boolean(bool),
Str(String),
Num(f64),
Array(Vec<JsonValue>),
Object(HashMap<String, JsonValue>),
}

View file

@ -0,0 +1,98 @@
mod json;
mod parser;
mod parser_dispatch;
#[allow(dead_code)]
mod parser_partial;
use winnow::error::ErrorKind;
use winnow::prelude::*;
fn main() -> Result<(), lexopt::Error> {
let args = Args::parse()?;
let data = args.input.as_deref().unwrap_or(if args.invalid {
" { \"a\"\t: 42,
\"b\": [ \"x\", \"y\", 12 ] ,
\"c\": { 1\"hello\" : \"world\"
}
} "
} else {
" { \"a\"\t: 42,
\"b\": [ \"x\", \"y\", 12 ] ,
\"c\": { \"hello\" : \"world\"
}
} "
});
let result = match args.implementation {
Impl::Naive => parser::json::<ErrorKind>.parse(data),
Impl::Dispatch => parser_dispatch::json::<ErrorKind>.parse(data),
};
match result {
Ok(json) => {
println!("{:#?}", json);
}
Err(err) => {
if args.pretty {
println!("{}", err);
} else {
println!("{:#?}", err);
}
}
}
Ok(())
}
#[derive(Default)]
struct Args {
input: Option<String>,
invalid: bool,
pretty: bool,
implementation: Impl,
}
enum Impl {
Naive,
Dispatch,
}
impl Default for Impl {
fn default() -> Self {
Self::Naive
}
}
impl Args {
fn parse() -> Result<Self, lexopt::Error> {
use lexopt::prelude::*;
let mut res = Args::default();
let mut args = lexopt::Parser::from_env();
while let Some(arg) = args.next()? {
match arg {
Long("invalid") => {
res.invalid = true;
}
Long("pretty") => {
// Only case where pretty matters
res.pretty = true;
res.invalid = true;
}
Long("impl") => {
res.implementation = args.value()?.parse_with(|s| match s {
"naive" => Ok(Impl::Naive),
"dispatch" => Ok(Impl::Dispatch),
_ => Err("expected `naive`, `dispatch`"),
})?;
}
Value(input) => {
res.input = Some(input.string()?);
}
_ => return Err(arg.unexpected()),
}
}
Ok(res)
}
}

View file

@ -0,0 +1,328 @@
use std::collections::HashMap;
use std::str;
use winnow::prelude::*;
use winnow::{
ascii::float,
combinator::alt,
combinator::cut_err,
combinator::{delimited, preceded, separated_pair, terminated},
combinator::{repeat, separated},
error::{AddContext, ParserError},
token::{any, none_of, take, take_while},
};
use crate::json::JsonValue;
pub type Stream<'i> = &'i str;
/// The root element of a JSON parser is any value
///
/// A parser has the following signature:
/// `&mut Stream -> PResult<Output, InputError>`, with `PResult` defined as:
/// `type PResult<O, E = (I, ErrorKind)> = Result<O, Err<E>>;`
///
/// most of the times you can ignore the error type and use the default (but this
/// examples shows custom error types later on!)
///
/// Here we use `&str` as input type, but parsers can be generic over
/// the input type, work directly with `&[u8]`, or any other type that
/// implements the required traits.
pub fn json<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, &'static str>>(
input: &mut Stream<'i>,
) -> PResult<JsonValue, E> {
delimited(ws, json_value, ws).parse_next(input)
}
/// `alt` is a combinator that tries multiple parsers one by one, until
/// one of them succeeds
fn json_value<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, &'static str>>(
input: &mut Stream<'i>,
) -> PResult<JsonValue, E> {
// `alt` combines the each value parser. It returns the result of the first
// successful parser, or an error
alt((
null.value(JsonValue::Null),
boolean.map(JsonValue::Boolean),
string.map(JsonValue::Str),
float.map(JsonValue::Num),
array.map(JsonValue::Array),
object.map(JsonValue::Object),
))
.parse_next(input)
}
/// `tag(string)` generates a parser that recognizes the argument string.
///
/// This also shows returning a sub-slice of the original input
fn null<'i, E: ParserError<Stream<'i>>>(input: &mut Stream<'i>) -> PResult<&'i str, E> {
// This is a parser that returns `"null"` if it sees the string "null", and
// an error otherwise
"null".parse_next(input)
}
/// We can combine `tag` with other functions, like `value` which returns a given constant value on
/// success.
fn boolean<'i, E: ParserError<Stream<'i>>>(input: &mut Stream<'i>) -> PResult<bool, E> {
// This is a parser that returns `true` if it sees the string "true", and
// an error otherwise
let parse_true = "true".value(true);
// This is a parser that returns `false` if it sees the string "false", and
// an error otherwise
let parse_false = "false".value(false);
alt((parse_true, parse_false)).parse_next(input)
}
/// This parser gathers all `char`s up into a `String`with a parse to recognize the double quote
/// character, before the string (using `preceded`) and after the string (using `terminated`).
fn string<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, &'static str>>(
input: &mut Stream<'i>,
) -> PResult<String, E> {
preceded(
'\"',
// `cut_err` transforms an `ErrMode::Backtrack(e)` to `ErrMode::Cut(e)`, signaling to
// combinators like `alt` that they should not try other parsers. We were in the
// right branch (since we found the `"` character) but encountered an error when
// parsing the string
cut_err(terminated(
repeat(0.., character).fold(String::new, |mut string, c| {
string.push(c);
string
}),
'\"',
)),
)
// `context` lets you add a static string to errors to provide more information in the
// error chain (to indicate which parser had an error)
.context("string")
.parse_next(input)
}
/// You can mix the above declarative parsing with an imperative style to handle more unique cases,
/// like escaping
fn character<'i, E: ParserError<Stream<'i>>>(input: &mut Stream<'i>) -> PResult<char, E> {
let c = none_of('\"').parse_next(input)?;
if c == '\\' {
alt((
any.verify_map(|c| {
Some(match c {
'"' | '\\' | '/' => c,
'b' => '\x08',
'f' => '\x0C',
'n' => '\n',
'r' => '\r',
't' => '\t',
_ => return None,
})
}),
preceded('u', unicode_escape),
))
.parse_next(input)
} else {
Ok(c)
}
}
fn unicode_escape<'i, E: ParserError<Stream<'i>>>(input: &mut Stream<'i>) -> PResult<char, E> {
alt((
// Not a surrogate
u16_hex
.verify(|cp| !(0xD800..0xE000).contains(cp))
.map(|cp| cp as u32),
// See https://en.wikipedia.org/wiki/UTF-16#Code_points_from_U+010000_to_U+10FFFF for details
separated_pair(u16_hex, "\\u", u16_hex)
.verify(|(high, low)| (0xD800..0xDC00).contains(high) && (0xDC00..0xE000).contains(low))
.map(|(high, low)| {
let high_ten = (high as u32) - 0xD800;
let low_ten = (low as u32) - 0xDC00;
(high_ten << 10) + low_ten + 0x10000
}),
))
.verify_map(
// Could be probably replaced with .unwrap() or _unchecked due to the verify checks
std::char::from_u32,
)
.parse_next(input)
}
fn u16_hex<'i, E: ParserError<Stream<'i>>>(input: &mut Stream<'i>) -> PResult<u16, E> {
take(4usize)
.verify_map(|s| u16::from_str_radix(s, 16).ok())
.parse_next(input)
}
/// Some combinators, like `separated` or `repeat`, will call a parser repeatedly,
/// accumulating results in a `Vec`, until it encounters an error.
/// If you want more control on the parser application, check out the `iterator`
/// combinator (cf `examples/iterator.rs`)
fn array<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, &'static str>>(
input: &mut Stream<'i>,
) -> PResult<Vec<JsonValue>, E> {
preceded(
('[', ws),
cut_err(terminated(
separated(0.., json_value, (ws, ',', ws)),
(ws, ']'),
)),
)
.context("array")
.parse_next(input)
}
fn object<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, &'static str>>(
input: &mut Stream<'i>,
) -> PResult<HashMap<String, JsonValue>, E> {
preceded(
('{', ws),
cut_err(terminated(
separated(0.., key_value, (ws, ',', ws)),
(ws, '}'),
)),
)
.context("object")
.parse_next(input)
}
fn key_value<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, &'static str>>(
input: &mut Stream<'i>,
) -> PResult<(String, JsonValue), E> {
separated_pair(string, cut_err((ws, ':', ws)), json_value).parse_next(input)
}
/// Parser combinators are constructed from the bottom up:
/// first we write parsers for the smallest elements (here a space character),
/// then we'll combine them in larger parsers
fn ws<'i, E: ParserError<Stream<'i>>>(input: &mut Stream<'i>) -> PResult<&'i str, E> {
// Combinators like `take_while` return a function. That function is the
// parser,to which we can pass the input
take_while(0.., WS).parse_next(input)
}
const WS: &[char] = &[' ', '\t', '\r', '\n'];
#[cfg(test)]
mod test {
#[allow(clippy::useless_attribute)]
#[allow(dead_code)] // its dead for benches
use super::*;
#[allow(clippy::useless_attribute)]
#[allow(dead_code)] // its dead for benches
type Error<'i> = winnow::error::InputError<&'i str>;
#[test]
fn json_string() {
assert_eq!(
string::<Error<'_>>.parse_peek("\"\""),
Ok(("", "".to_string()))
);
assert_eq!(
string::<Error<'_>>.parse_peek("\"abc\""),
Ok(("", "abc".to_string()))
);
assert_eq!(
string::<Error<'_>>
.parse_peek("\"abc\\\"\\\\\\/\\b\\f\\n\\r\\t\\u0001\\u2014\u{2014}def\""),
Ok(("", "abc\"\\/\x08\x0C\n\r\t\x01——def".to_string())),
);
assert_eq!(
string::<Error<'_>>.parse_peek("\"\\uD83D\\uDE10\""),
Ok(("", "😐".to_string()))
);
assert!(string::<Error<'_>>.parse_peek("\"").is_err());
assert!(string::<Error<'_>>.parse_peek("\"abc").is_err());
assert!(string::<Error<'_>>.parse_peek("\"\\\"").is_err());
assert!(string::<Error<'_>>.parse_peek("\"\\u123\"").is_err());
assert!(string::<Error<'_>>.parse_peek("\"\\uD800\"").is_err());
assert!(string::<Error<'_>>
.parse_peek("\"\\uD800\\uD800\"")
.is_err());
assert!(string::<Error<'_>>.parse_peek("\"\\uDC00\"").is_err());
}
#[test]
fn json_object() {
use JsonValue::{Num, Object, Str};
let input = r#"{"a":42,"b":"x"}"#;
let expected = Object(
vec![
("a".to_string(), Num(42.0)),
("b".to_string(), Str("x".to_string())),
]
.into_iter()
.collect(),
);
assert_eq!(json::<Error<'_>>.parse_peek(input), Ok(("", expected)));
}
#[test]
fn json_array() {
use JsonValue::{Array, Num, Str};
let input = r#"[42,"x"]"#;
let expected = Array(vec![Num(42.0), Str("x".to_string())]);
assert_eq!(json::<Error<'_>>.parse_peek(input), Ok(("", expected)));
}
#[test]
fn json_whitespace() {
use JsonValue::{Array, Boolean, Null, Num, Object, Str};
let input = r#"
{
"null" : null,
"true" :true ,
"false": false ,
"number" : 123e4 ,
"string" : " abc 123 " ,
"array" : [ false , 1 , "two" ] ,
"object" : { "a" : 1.0 , "b" : "c" } ,
"empty_array" : [ ] ,
"empty_object" : { }
}
"#;
assert_eq!(
json::<Error<'_>>.parse_peek(input),
Ok((
"",
Object(
vec![
("null".to_string(), Null),
("true".to_string(), Boolean(true)),
("false".to_string(), Boolean(false)),
("number".to_string(), Num(123e4)),
("string".to_string(), Str(" abc 123 ".to_string())),
(
"array".to_string(),
Array(vec![Boolean(false), Num(1.0), Str("two".to_string())])
),
(
"object".to_string(),
Object(
vec![
("a".to_string(), Num(1.0)),
("b".to_string(), Str("c".to_string())),
]
.into_iter()
.collect()
)
),
("empty_array".to_string(), Array(vec![]),),
("empty_object".to_string(), Object(HashMap::new()),),
]
.into_iter()
.collect()
)
))
);
}
}

View file

@ -0,0 +1,335 @@
use std::collections::HashMap;
use std::str;
use winnow::prelude::*;
use winnow::{
ascii::float,
combinator::cut_err,
combinator::empty,
combinator::fail,
combinator::peek,
combinator::{alt, dispatch},
combinator::{delimited, preceded, separated_pair, terminated},
combinator::{repeat, separated},
error::{AddContext, ParserError},
token::{any, none_of, take, take_while},
};
use crate::json::JsonValue;
pub type Stream<'i> = &'i str;
/// The root element of a JSON parser is any value
///
/// A parser has the following signature:
/// `&mut Stream -> PResult<Output, InputError>`, with `PResult` defined as:
/// `type PResult<O, E = ErrorKind> = Result<O, ErrMode<E>>;`
///
/// most of the times you can ignore the error type and use the default (but this
/// examples shows custom error types later on!)
///
/// Here we use `&str` as input type, but parsers can be generic over
/// the input type, work directly with `&[u8]`, or any other type that
/// implements the required traits.
pub fn json<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, &'static str>>(
input: &mut Stream<'i>,
) -> PResult<JsonValue, E> {
delimited(ws, json_value, ws).parse_next(input)
}
/// `alt` is a combinator that tries multiple parsers one by one, until
/// one of them succeeds
fn json_value<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, &'static str>>(
input: &mut Stream<'i>,
) -> PResult<JsonValue, E> {
// `dispatch` gives you `match`-like behavior compared to `alt` successively trying different
// implementations.
dispatch!(peek(any);
'n' => null.value(JsonValue::Null),
't' => true_.map(JsonValue::Boolean),
'f' => false_.map(JsonValue::Boolean),
'"' => string.map(JsonValue::Str),
'+' => float.map(JsonValue::Num),
'-' => float.map(JsonValue::Num),
'0'..='9' => float.map(JsonValue::Num),
'[' => array.map(JsonValue::Array),
'{' => object.map(JsonValue::Object),
_ => fail,
)
.parse_next(input)
}
/// `tag(string)` generates a parser that recognizes the argument string.
///
/// This also shows returning a sub-slice of the original input
fn null<'i, E: ParserError<Stream<'i>>>(input: &mut Stream<'i>) -> PResult<&'i str, E> {
// This is a parser that returns `"null"` if it sees the string "null", and
// an error otherwise
"null".parse_next(input)
}
/// We can combine `tag` with other functions, like `value` which returns a given constant value on
/// success.
fn true_<'i, E: ParserError<Stream<'i>>>(input: &mut Stream<'i>) -> PResult<bool, E> {
// This is a parser that returns `true` if it sees the string "true", and
// an error otherwise
"true".value(true).parse_next(input)
}
/// We can combine `tag` with other functions, like `value` which returns a given constant value on
/// success.
fn false_<'i, E: ParserError<Stream<'i>>>(input: &mut Stream<'i>) -> PResult<bool, E> {
// This is a parser that returns `false` if it sees the string "false", and
// an error otherwise
"false".value(false).parse_next(input)
}
/// This parser gathers all `char`s up into a `String`with a parse to recognize the double quote
/// character, before the string (using `preceded`) and after the string (using `terminated`).
fn string<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, &'static str>>(
input: &mut Stream<'i>,
) -> PResult<String, E> {
preceded(
'\"',
// `cut_err` transforms an `ErrMode::Backtrack(e)` to `ErrMode::Cut(e)`, signaling to
// combinators like `alt` that they should not try other parsers. We were in the
// right branch (since we found the `"` character) but encountered an error when
// parsing the string
cut_err(terminated(
repeat(0.., character).fold(String::new, |mut string, c| {
string.push(c);
string
}),
'\"',
)),
)
// `context` lets you add a static string to errors to provide more information in the
// error chain (to indicate which parser had an error)
.context("string")
.parse_next(input)
}
/// You can mix the above declarative parsing with an imperative style to handle more unique cases,
/// like escaping
fn character<'i, E: ParserError<Stream<'i>>>(input: &mut Stream<'i>) -> PResult<char, E> {
let c = none_of('\"').parse_next(input)?;
if c == '\\' {
dispatch!(any;
'"' => empty.value('"'),
'\\' => empty.value('\\'),
'/' => empty.value('/'),
'b' => empty.value('\x08'),
'f' => empty.value('\x0C'),
'n' => empty.value('\n'),
'r' => empty.value('\r'),
't' => empty.value('\t'),
'u' => unicode_escape,
_ => fail,
)
.parse_next(input)
} else {
Ok(c)
}
}
fn unicode_escape<'i, E: ParserError<Stream<'i>>>(input: &mut Stream<'i>) -> PResult<char, E> {
alt((
// Not a surrogate
u16_hex
.verify(|cp| !(0xD800..0xE000).contains(cp))
.map(|cp| cp as u32),
// See https://en.wikipedia.org/wiki/UTF-16#Code_points_from_U+010000_to_U+10FFFF for details
separated_pair(u16_hex, "\\u", u16_hex)
.verify(|(high, low)| (0xD800..0xDC00).contains(high) && (0xDC00..0xE000).contains(low))
.map(|(high, low)| {
let high_ten = (high as u32) - 0xD800;
let low_ten = (low as u32) - 0xDC00;
(high_ten << 10) + low_ten + 0x10000
}),
))
.verify_map(
// Could be probably replaced with .unwrap() or _unchecked due to the verify checks
std::char::from_u32,
)
.parse_next(input)
}
fn u16_hex<'i, E: ParserError<Stream<'i>>>(input: &mut Stream<'i>) -> PResult<u16, E> {
take(4usize)
.verify_map(|s| u16::from_str_radix(s, 16).ok())
.parse_next(input)
}
/// Some combinators, like `separated` or `repeat`, will call a parser repeatedly,
/// accumulating results in a `Vec`, until it encounters an error.
/// If you want more control on the parser application, check out the `iterator`
/// combinator (cf `examples/iterator.rs`)
fn array<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, &'static str>>(
input: &mut Stream<'i>,
) -> PResult<Vec<JsonValue>, E> {
preceded(
('[', ws),
cut_err(terminated(
separated(0.., json_value, (ws, ',', ws)),
(ws, ']'),
)),
)
.context("array")
.parse_next(input)
}
fn object<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, &'static str>>(
input: &mut Stream<'i>,
) -> PResult<HashMap<String, JsonValue>, E> {
preceded(
('{', ws),
cut_err(terminated(
separated(0.., key_value, (ws, ',', ws)),
(ws, '}'),
)),
)
.context("object")
.parse_next(input)
}
fn key_value<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, &'static str>>(
input: &mut Stream<'i>,
) -> PResult<(String, JsonValue), E> {
separated_pair(string, cut_err((ws, ':', ws)), json_value).parse_next(input)
}
/// Parser combinators are constructed from the bottom up:
/// first we write parsers for the smallest elements (here a space character),
/// then we'll combine them in larger parsers
fn ws<'i, E: ParserError<Stream<'i>>>(input: &mut Stream<'i>) -> PResult<&'i str, E> {
// Combinators like `take_while` return a function. That function is the
// parser,to which we can pass the input
take_while(0.., WS).parse_next(input)
}
const WS: &[char] = &[' ', '\t', '\r', '\n'];
#[cfg(test)]
mod test {
#[allow(clippy::useless_attribute)]
#[allow(dead_code)] // its dead for benches
use super::*;
#[allow(clippy::useless_attribute)]
#[allow(dead_code)] // its dead for benches
type Error<'i> = winnow::error::InputError<&'i str>;
#[test]
fn json_string() {
assert_eq!(
string::<Error<'_>>.parse_peek("\"\""),
Ok(("", "".to_string()))
);
assert_eq!(
string::<Error<'_>>.parse_peek("\"abc\""),
Ok(("", "abc".to_string()))
);
assert_eq!(
string::<Error<'_>>
.parse_peek("\"abc\\\"\\\\\\/\\b\\f\\n\\r\\t\\u0001\\u2014\u{2014}def\""),
Ok(("", "abc\"\\/\x08\x0C\n\r\t\x01——def".to_string())),
);
assert_eq!(
string::<Error<'_>>.parse_peek("\"\\uD83D\\uDE10\""),
Ok(("", "😐".to_string()))
);
assert!(string::<Error<'_>>.parse_peek("\"").is_err());
assert!(string::<Error<'_>>.parse_peek("\"abc").is_err());
assert!(string::<Error<'_>>.parse_peek("\"\\\"").is_err());
assert!(string::<Error<'_>>.parse_peek("\"\\u123\"").is_err());
assert!(string::<Error<'_>>.parse_peek("\"\\uD800\"").is_err());
assert!(string::<Error<'_>>
.parse_peek("\"\\uD800\\uD800\"")
.is_err());
assert!(string::<Error<'_>>.parse_peek("\"\\uDC00\"").is_err());
}
#[test]
fn json_object() {
use JsonValue::{Num, Object, Str};
let input = r#"{"a":42,"b":"x"}"#;
let expected = Object(
vec![
("a".to_string(), Num(42.0)),
("b".to_string(), Str("x".to_string())),
]
.into_iter()
.collect(),
);
assert_eq!(json::<Error<'_>>.parse_peek(input), Ok(("", expected)));
}
#[test]
fn json_array() {
use JsonValue::{Array, Num, Str};
let input = r#"[42,"x"]"#;
let expected = Array(vec![Num(42.0), Str("x".to_string())]);
assert_eq!(json::<Error<'_>>.parse_peek(input), Ok(("", expected)));
}
#[test]
fn json_whitespace() {
use JsonValue::{Array, Boolean, Null, Num, Object, Str};
let input = r#"
{
"null" : null,
"true" :true ,
"false": false ,
"number" : 123e4 ,
"string" : " abc 123 " ,
"array" : [ false , 1 , "two" ] ,
"object" : { "a" : 1.0 , "b" : "c" } ,
"empty_array" : [ ] ,
"empty_object" : { }
}
"#;
assert_eq!(
json::<Error<'_>>.parse_peek(input),
Ok((
"",
Object(
vec![
("null".to_string(), Null),
("true".to_string(), Boolean(true)),
("false".to_string(), Boolean(false)),
("number".to_string(), Num(123e4)),
("string".to_string(), Str(" abc 123 ".to_string())),
(
"array".to_string(),
Array(vec![Boolean(false), Num(1.0), Str("two".to_string())])
),
(
"object".to_string(),
Object(
vec![
("a".to_string(), Num(1.0)),
("b".to_string(), Str("c".to_string())),
]
.into_iter()
.collect()
)
),
("empty_array".to_string(), Array(vec![]),),
("empty_object".to_string(), Object(HashMap::new()),),
]
.into_iter()
.collect()
)
))
);
}
}

View file

@ -0,0 +1,354 @@
use std::collections::HashMap;
use std::str;
use winnow::prelude::*;
use winnow::{
ascii::float,
combinator::alt,
combinator::{cut_err, rest},
combinator::{delimited, preceded, separated_pair, terminated},
combinator::{repeat, separated},
error::{AddContext, ParserError},
stream::Partial,
token::{any, none_of, take, take_while},
};
use crate::json::JsonValue;
pub type Stream<'i> = Partial<&'i str>;
/// The root element of a JSON parser is any value
///
/// A parser has the following signature:
/// `&mut Stream -> PResult<Output, InputError>`, with `PResult` defined as:
/// `type PResult<O, E = ErrorKind> = Result<O, ErrMode<E>>;`
///
/// most of the times you can ignore the error type and use the default (but this
/// examples shows custom error types later on!)
///
/// Here we use `&str` as input type, but parsers can be generic over
/// the input type, work directly with `&[u8]`, or any other type that
/// implements the required traits.
pub fn json<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, &'static str>>(
input: &mut Stream<'i>,
) -> PResult<JsonValue, E> {
delimited(ws, json_value, ws_or_eof).parse_next(input)
}
/// `alt` is a combinator that tries multiple parsers one by one, until
/// one of them succeeds
fn json_value<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, &'static str>>(
input: &mut Stream<'i>,
) -> PResult<JsonValue, E> {
// `alt` combines the each value parser. It returns the result of the first
// successful parser, or an error
alt((
null.value(JsonValue::Null),
boolean.map(JsonValue::Boolean),
string.map(JsonValue::Str),
float.map(JsonValue::Num),
array.map(JsonValue::Array),
object.map(JsonValue::Object),
))
.parse_next(input)
}
/// `tag(string)` generates a parser that recognizes the argument string.
///
/// This also shows returning a sub-slice of the original input
fn null<'i, E: ParserError<Stream<'i>>>(input: &mut Stream<'i>) -> PResult<&'i str, E> {
// This is a parser that returns `"null"` if it sees the string "null", and
// an error otherwise
"null".parse_next(input)
}
/// We can combine `tag` with other functions, like `value` which returns a given constant value on
/// success.
fn boolean<'i, E: ParserError<Stream<'i>>>(input: &mut Stream<'i>) -> PResult<bool, E> {
// This is a parser that returns `true` if it sees the string "true", and
// an error otherwise
let parse_true = "true".value(true);
// This is a parser that returns `false` if it sees the string "false", and
// an error otherwise
let parse_false = "false".value(false);
alt((parse_true, parse_false)).parse_next(input)
}
/// This parser gathers all `char`s up into a `String`with a parse to recognize the double quote
/// character, before the string (using `preceded`) and after the string (using `terminated`).
fn string<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, &'static str>>(
input: &mut Stream<'i>,
) -> PResult<String, E> {
preceded(
'\"',
// `cut_err` transforms an `ErrMode::Backtrack(e)` to `ErrMode::Cut(e)`, signaling to
// combinators like `alt` that they should not try other parsers. We were in the
// right branch (since we found the `"` character) but encountered an error when
// parsing the string
cut_err(terminated(
repeat(0.., character).fold(String::new, |mut string, c| {
string.push(c);
string
}),
'\"',
)),
)
// `context` lets you add a static string to errors to provide more information in the
// error chain (to indicate which parser had an error)
.context("string")
.parse_next(input)
}
/// You can mix the above declarative parsing with an imperative style to handle more unique cases,
/// like escaping
fn character<'i, E: ParserError<Stream<'i>>>(input: &mut Stream<'i>) -> PResult<char, E> {
let c = none_of('\"').parse_next(input)?;
if c == '\\' {
alt((
any.verify_map(|c| {
Some(match c {
'"' | '\\' | '/' => c,
'b' => '\x08',
'f' => '\x0C',
'n' => '\n',
'r' => '\r',
't' => '\t',
_ => return None,
})
}),
preceded('u', unicode_escape),
))
.parse_next(input)
} else {
Ok(c)
}
}
fn unicode_escape<'i, E: ParserError<Stream<'i>>>(input: &mut Stream<'i>) -> PResult<char, E> {
alt((
// Not a surrogate
u16_hex
.verify(|cp| !(0xD800..0xE000).contains(cp))
.map(|cp| cp as u32),
// See https://en.wikipedia.org/wiki/UTF-16#Code_points_from_U+010000_to_U+10FFFF for details
separated_pair(u16_hex, "\\u", u16_hex)
.verify(|(high, low)| (0xD800..0xDC00).contains(high) && (0xDC00..0xE000).contains(low))
.map(|(high, low)| {
let high_ten = (high as u32) - 0xD800;
let low_ten = (low as u32) - 0xDC00;
(high_ten << 10) + low_ten + 0x10000
}),
))
.verify_map(
// Could be probably replaced with .unwrap() or _unchecked due to the verify checks
std::char::from_u32,
)
.parse_next(input)
}
fn u16_hex<'i, E: ParserError<Stream<'i>>>(input: &mut Stream<'i>) -> PResult<u16, E> {
take(4usize)
.verify_map(|s| u16::from_str_radix(s, 16).ok())
.parse_next(input)
}
/// Some combinators, like `separated` or `repeat`, will call a parser repeatedly,
/// accumulating results in a `Vec`, until it encounters an error.
/// If you want more control on the parser application, check out the `iterator`
/// combinator (cf `examples/iterator.rs`)
fn array<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, &'static str>>(
input: &mut Stream<'i>,
) -> PResult<Vec<JsonValue>, E> {
preceded(
('[', ws),
cut_err(terminated(
separated(0.., json_value, (ws, ',', ws)),
(ws, ']'),
)),
)
.context("array")
.parse_next(input)
}
fn object<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, &'static str>>(
input: &mut Stream<'i>,
) -> PResult<HashMap<String, JsonValue>, E> {
preceded(
('{', ws),
cut_err(terminated(
separated(0.., key_value, (ws, ',', ws)),
(ws, '}'),
)),
)
.context("object")
.parse_next(input)
}
fn key_value<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, &'static str>>(
input: &mut Stream<'i>,
) -> PResult<(String, JsonValue), E> {
separated_pair(string, cut_err((ws, ':', ws)), json_value).parse_next(input)
}
/// Parser combinators are constructed from the bottom up:
/// first we write parsers for the smallest elements (here a space character),
/// then we'll combine them in larger parsers
fn ws<'i, E: ParserError<Stream<'i>>>(input: &mut Stream<'i>) -> PResult<&'i str, E> {
// Combinators like `take_while` return a function. That function is the
// parser,to which we can pass the input
take_while(0.., WS).parse_next(input)
}
fn ws_or_eof<'i, E: ParserError<Stream<'i>>>(input: &mut Stream<'i>) -> PResult<&'i str, E> {
rest.verify(|s: &str| s.chars().all(|c| WS.contains(&c)))
.parse_next(input)
}
const WS: &[char] = &[' ', '\t', '\r', '\n'];
#[cfg(test)]
mod test {
#[allow(clippy::useless_attribute)]
#[allow(dead_code)] // its dead for benches
use super::*;
#[allow(clippy::useless_attribute)]
#[allow(dead_code)] // its dead for benches
type Error<'i> = winnow::error::InputError<Partial<&'i str>>;
#[test]
fn json_string() {
assert_eq!(
string::<Error<'_>>.parse_peek(Partial::new("\"\"")),
Ok((Partial::new(""), "".to_string()))
);
assert_eq!(
string::<Error<'_>>.parse_peek(Partial::new("\"abc\"")),
Ok((Partial::new(""), "abc".to_string()))
);
assert_eq!(
string::<Error<'_>>.parse_peek(Partial::new(
"\"abc\\\"\\\\\\/\\b\\f\\n\\r\\t\\u0001\\u2014\u{2014}def\""
)),
Ok((
Partial::new(""),
"abc\"\\/\x08\x0C\n\r\t\x01——def".to_string()
)),
);
assert_eq!(
string::<Error<'_>>.parse_peek(Partial::new("\"\\uD83D\\uDE10\"")),
Ok((Partial::new(""), "😐".to_string()))
);
assert!(string::<Error<'_>>.parse_peek(Partial::new("\"")).is_err());
assert!(string::<Error<'_>>
.parse_peek(Partial::new("\"abc"))
.is_err());
assert!(string::<Error<'_>>
.parse_peek(Partial::new("\"\\\""))
.is_err());
assert!(string::<Error<'_>>
.parse_peek(Partial::new("\"\\u123\""))
.is_err());
assert!(string::<Error<'_>>
.parse_peek(Partial::new("\"\\uD800\""))
.is_err());
assert!(string::<Error<'_>>
.parse_peek(Partial::new("\"\\uD800\\uD800\""))
.is_err());
assert!(string::<Error<'_>>
.parse_peek(Partial::new("\"\\uDC00\""))
.is_err());
}
#[test]
fn json_object() {
use JsonValue::{Num, Object, Str};
let input = r#"{"a":42,"b":"x"}"#;
let expected = Object(
vec![
("a".to_string(), Num(42.0)),
("b".to_string(), Str("x".to_string())),
]
.into_iter()
.collect(),
);
assert_eq!(
json::<Error<'_>>.parse_peek(Partial::new(input)),
Ok((Partial::new(""), expected))
);
}
#[test]
fn json_array() {
use JsonValue::{Array, Num, Str};
let input = r#"[42,"x"]"#;
let expected = Array(vec![Num(42.0), Str("x".to_string())]);
assert_eq!(
json::<Error<'_>>.parse_peek(Partial::new(input)),
Ok((Partial::new(""), expected))
);
}
#[test]
fn json_whitespace() {
use JsonValue::{Array, Boolean, Null, Num, Object, Str};
let input = r#"
{
"null" : null,
"true" :true ,
"false": false ,
"number" : 123e4 ,
"string" : " abc 123 " ,
"array" : [ false , 1 , "two" ] ,
"object" : { "a" : 1.0 , "b" : "c" } ,
"empty_array" : [ ] ,
"empty_object" : { }
}
"#;
assert_eq!(
json::<Error<'_>>.parse_peek(Partial::new(input)),
Ok((
Partial::new(""),
Object(
vec![
("null".to_string(), Null),
("true".to_string(), Boolean(true)),
("false".to_string(), Boolean(false)),
("number".to_string(), Num(123e4)),
("string".to_string(), Str(" abc 123 ".to_string())),
(
"array".to_string(),
Array(vec![Boolean(false), Num(1.0), Str("two".to_string())])
),
(
"object".to_string(),
Object(
vec![
("a".to_string(), Num(1.0)),
("b".to_string(), Str("c".to_string())),
]
.into_iter()
.collect()
)
),
("empty_array".to_string(), Array(vec![]),),
("empty_object".to_string(), Object(HashMap::new()),),
]
.into_iter()
.collect()
)
))
);
}
}

View file

@ -0,0 +1,311 @@
use std::collections::HashMap;
use winnow::prelude::*;
use winnow::{
ascii::{alphanumeric1 as alphanumeric, escaped, float},
combinator::alt,
combinator::cut_err,
combinator::separated,
combinator::{preceded, separated_pair, terminated},
error::ParserError,
error::StrContext,
stream::Offset,
token::one_of,
token::{tag, take_while},
};
use std::cell::Cell;
use std::str;
#[derive(Clone, Debug)]
pub struct JsonValue<'a, 'b> {
input: &'a str,
pub offset: &'b Cell<usize>,
}
impl<'a, 'b: 'a> JsonValue<'a, 'b> {
pub fn new(input: &'a str, offset: &'b Cell<usize>) -> JsonValue<'a, 'b> {
JsonValue { input, offset }
}
pub fn offset(&self, input: &'a str) {
let offset = input.offset_from(&self.input);
self.offset.set(offset);
}
pub fn data(&self) -> &'a str {
&self.input[self.offset.get()..]
}
pub fn string(&self) -> Option<&'a str> {
println!("string()");
let mut data = self.data();
match string(&mut data) {
Ok(s) => {
self.offset(data);
println!("-> {}", s);
Some(s)
}
_ => None,
}
}
pub fn boolean(&self) -> Option<bool> {
println!("boolean()");
let mut data = self.data();
match boolean(&mut data) {
Ok(o) => {
self.offset(data);
println!("-> {}", o);
Some(o)
}
_ => None,
}
}
pub fn number(&self) -> Option<f64> {
println!("number()");
let mut data = self.data();
match float::<_, _, ()>.parse_next(&mut data) {
Ok(o) => {
self.offset(data);
println!("-> {}", o);
Some(o)
}
_ => None,
}
}
pub fn array(&self) -> Option<impl Iterator<Item = JsonValue<'a, 'b>>> {
println!("array()");
let mut data = self.data();
match tag::<_, _, ()>("[").parse_next(&mut data) {
Err(_) => None,
Ok(_) => {
println!("[");
self.offset(data);
let mut first = true;
let mut done = false;
let mut previous = std::usize::MAX;
let v = self.clone();
Some(std::iter::from_fn(move || {
if done {
return None;
}
// if we ignored one of the items, skip over the value
if v.offset.get() == previous {
println!("skipping value");
if value(&mut data).is_ok() {
v.offset(data);
}
}
if tag::<_, _, ()>("]").parse_next(&mut data).is_ok() {
println!("]");
v.offset(data);
done = true;
return None;
}
if first {
first = false;
} else {
match tag::<_, _, ()>(",").parse_next(&mut data) {
Ok(_) => {
println!(",");
v.offset(data);
}
Err(_) => {
done = true;
return None;
}
}
}
println!("-> {}", v.data());
previous = v.offset.get();
Some(v.clone())
}))
}
}
}
pub fn object(&self) -> Option<impl Iterator<Item = (&'a str, JsonValue<'a, 'b>)>> {
println!("object()");
let mut data = self.data();
match tag::<_, _, ()>("{").parse_next(&mut data) {
Err(_) => None,
Ok(_) => {
self.offset(data);
println!("{{");
let mut first = true;
let mut done = false;
let mut previous = std::usize::MAX;
let v = self.clone();
Some(std::iter::from_fn(move || {
if done {
return None;
}
// if we ignored one of the items, skip over the value
if v.offset.get() == previous {
println!("skipping value");
if value(&mut data).is_ok() {
v.offset(data);
}
}
if tag::<_, _, ()>("}").parse_next(&mut data).is_ok() {
println!("}}");
v.offset(data);
done = true;
return None;
}
if first {
first = false;
} else {
match tag::<_, _, ()>(",").parse_next(&mut data) {
Ok(_) => {
println!(",");
v.offset(data);
}
Err(_) => {
done = true;
return None;
}
}
}
match string(&mut data) {
Ok(key) => {
v.offset(data);
match tag::<_, _, ()>(":").parse_next(&mut data) {
Err(_) => None,
Ok(_) => {
v.offset(data);
previous = v.offset.get();
println!("-> {} => {}", key, v.data());
Some((key, v.clone()))
}
}
}
_ => None,
}
}))
}
}
}
}
fn sp<'a, E: ParserError<&'a str>>(i: &mut &'a str) -> PResult<&'a str, E> {
let chars = " \t\r\n";
take_while(0.., move |c| chars.contains(c)).parse_next(i)
}
fn parse_str<'a, E: ParserError<&'a str>>(i: &mut &'a str) -> PResult<&'a str, E> {
escaped(alphanumeric, '\\', one_of(['"', 'n', '\\'])).parse_next(i)
}
fn string<'s>(i: &mut &'s str) -> PResult<&'s str> {
preceded('\"', cut_err(terminated(parse_str, '\"')))
.context(StrContext::Label("string"))
.parse_next(i)
}
fn boolean(input: &mut &str) -> PResult<bool> {
alt(("false".map(|_| false), "true".map(|_| true))).parse_next(input)
}
fn array(i: &mut &str) -> PResult<()> {
preceded(
'[',
cut_err(terminated(
separated(0.., value, preceded(sp, ',')),
preceded(sp, ']'),
)),
)
.context(StrContext::Label("array"))
.parse_next(i)
}
fn key_value<'s>(i: &mut &'s str) -> PResult<(&'s str, ())> {
separated_pair(preceded(sp, string), cut_err(preceded(sp, ':')), value).parse_next(i)
}
fn hash(i: &mut &str) -> PResult<()> {
preceded(
'{',
cut_err(terminated(
separated(0.., key_value, preceded(sp, ',')),
preceded(sp, '}'),
)),
)
.context(StrContext::Label("map"))
.parse_next(i)
}
fn value(i: &mut &str) -> PResult<()> {
preceded(
sp,
alt((
hash,
array,
string.map(|_| ()),
float::<_, f64, _>.map(|_| ()),
boolean.map(|_| ()),
)),
)
.parse_next(i)
}
/// object(input) -> iterator over (key, `JsonValue`)
/// array(input) -> iterator over `JsonValue`
///
/// JsonValue.string -> iterator over String (returns None after first successful call)
///
/// object(input).filter(|(k, _)| k == "users").flatten(|(_, v)| v.object()).filter(|(k, _)| k == "city").flatten(|(_,v)| v.string())
fn main() {
/*let data = "{
\"users\": {
\"user1\" : { \"city\": \"Nantes\", \"country\": \"France\" },
\"user2\" : { \"city\": \"Bruxelles\", \"country\": \"Belgium\" },
\"user3\": { \"city\": \"Paris\", \"country\": \"France\", \"age\": 30 }
},
\"countries\": [\"France\", \"Belgium\"]
}";
*/
let data = "{\"users\":{\"user1\":{\"city\":\"Nantes\",\"country\":\"France\"},\"user2\":{\"city\":\"Bruxelles\",\"country\":\"Belgium\"},\"user3\":{\"city\":\"Paris\",\"country\":\"France\",\"age\":30}},\"countries\":[\"France\",\"Belgium\"]}";
let offset = Cell::new(0);
{
let parser = JsonValue::new(data, &offset);
if let Some(o) = parser.object() {
let s: HashMap<&str, &str> = o
.filter(|(k, _)| *k == "users")
.filter_map(|(_, v)| v.object())
.flatten()
.filter_map(|(user, v)| v.object().map(|o| (user, o)))
.flat_map(|(user, o)| {
o.filter(|(k, _)| *k == "city")
.filter_map(move |(_, v)| v.string().map(|s| (user, s)))
})
.collect();
println!("res = {:?}", s);
}
};
}

View file

@ -0,0 +1,158 @@
{"reason":"compiler-artifact","package_id":"proc-macro2 1.0.46 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/proc-macro2-1.0.46/Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/proc-macro2-1.0.46/build.rs","edition":"2018","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","proc-macro"],"filenames":["/home/epage/src/personal/winnow/target/debug/build/proc-macro2-d6a7808ec27a845d/build-script-build"],"executable":null,"fresh":true}
{"reason":"build-script-executed","package_id":"proc-macro2 1.0.46 (registry+https://github.com/rust-lang/crates.io-index)","linked_libs":[],"linked_paths":[],"cfgs":["use_proc_macro","wrap_proc_macro"],"env":[],"out_dir":"/home/epage/src/personal/winnow/target/debug/build/proc-macro2-e500f83d0dabcc00/out"}
{"reason":"compiler-artifact","package_id":"quote 1.0.21 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/quote-1.0.21/Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/quote-1.0.21/build.rs","edition":"2018","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","proc-macro"],"filenames":["/home/epage/src/personal/winnow/target/debug/build/quote-e70da9bace8e108a/build-script-build"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"libc 0.2.139 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/libc-0.2.139/Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/libc-0.2.139/build.rs","edition":"2015","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","extra_traits","std"],"filenames":["/home/epage/src/personal/winnow/target/debug/build/libc-ea536a8e67e0b7eb/build-script-build"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"unicode-ident 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/unicode-ident-1.0.5/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"unicode-ident","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/unicode-ident-1.0.5/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libunicode_ident-e72d3e3fa5fdcbf4.rlib","/home/epage/src/personal/winnow/target/debug/deps/libunicode_ident-e72d3e3fa5fdcbf4.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"proc-macro2 1.0.46 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/proc-macro2-1.0.46/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"proc-macro2","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/proc-macro2-1.0.46/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","proc-macro"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libproc_macro2-559e547b03a7ac1e.rlib","/home/epage/src/personal/winnow/target/debug/deps/libproc_macro2-559e547b03a7ac1e.rmeta"],"executable":null,"fresh":true}
{"reason":"build-script-executed","package_id":"libc 0.2.139 (registry+https://github.com/rust-lang/crates.io-index)","linked_libs":[],"linked_paths":[],"cfgs":["freebsd11","libc_priv_mod_use","libc_union","libc_const_size_of","libc_align","libc_int128","libc_core_cvoid","libc_packedN","libc_cfg_target_vendor","libc_non_exhaustive","libc_ptr_addr_of","libc_underscore_const_names","libc_const_extern_fn"],"env":[],"out_dir":"/home/epage/src/personal/winnow/target/debug/build/libc-94d2f48bd38a8056/out"}
{"reason":"build-script-executed","package_id":"quote 1.0.21 (registry+https://github.com/rust-lang/crates.io-index)","linked_libs":[],"linked_paths":[],"cfgs":[],"env":[],"out_dir":"/home/epage/src/personal/winnow/target/debug/build/quote-a2754428d152a498/out"}
{"reason":"compiler-artifact","package_id":"syn 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/syn-1.0.102/Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/syn-1.0.102/build.rs","edition":"2018","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["clone-impls","default","derive","parsing","printing","proc-macro","quote"],"filenames":["/home/epage/src/personal/winnow/target/debug/build/syn-c9e8af729632e4e4/build-script-build"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/cfg-if-1.0.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"cfg-if","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/cfg-if-1.0.0/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libcfg_if-047a17fcf848a7e5.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"autocfg 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/autocfg-1.1.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"autocfg","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/autocfg-1.1.0/src/lib.rs","edition":"2015","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libautocfg-25db3455927a66e1.rlib","/home/epage/src/personal/winnow/target/debug/deps/libautocfg-25db3455927a66e1.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"serde_derive 1.0.145 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/serde_derive-1.0.145/Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/serde_derive-1.0.145/build.rs","edition":"2015","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default"],"filenames":["/home/epage/src/personal/winnow/target/debug/build/serde_derive-fcc2f4aec2a2d4ab/build-script-build"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"serde 1.0.145 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.145/Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.145/build.rs","edition":"2015","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","derive","serde_derive","std"],"filenames":["/home/epage/src/personal/winnow/target/debug/build/serde-3f78a53b92e21d4d/build-script-build"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"libc 0.2.139 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/libc-0.2.139/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"libc","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/libc-0.2.139/src/lib.rs","edition":"2015","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","extra_traits","std"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/liblibc-5b6dd9f3e6fc0120.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"quote 1.0.21 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/quote-1.0.21/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"quote","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/quote-1.0.21/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","proc-macro"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libquote-9a0c3a96b2cdc59c.rlib","/home/epage/src/personal/winnow/target/debug/deps/libquote-9a0c3a96b2cdc59c.rmeta"],"executable":null,"fresh":true}
{"reason":"build-script-executed","package_id":"syn 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)","linked_libs":[],"linked_paths":[],"cfgs":["syn_disable_nightly_tests"],"env":[],"out_dir":"/home/epage/src/personal/winnow/target/debug/build/syn-0a9a191063f1b2fc/out"}
{"reason":"build-script-executed","package_id":"serde_derive 1.0.145 (registry+https://github.com/rust-lang/crates.io-index)","linked_libs":[],"linked_paths":[],"cfgs":[],"env":[],"out_dir":"/home/epage/src/personal/winnow/target/debug/build/serde_derive-ead5e900bac8546f/out"}
{"reason":"build-script-executed","package_id":"serde 1.0.145 (registry+https://github.com/rust-lang/crates.io-index)","linked_libs":[],"linked_paths":[],"cfgs":[],"env":[],"out_dir":"/home/epage/src/personal/winnow/target/debug/build/serde-03f4af86861cbc3e/out"}
{"reason":"compiler-artifact","package_id":"thiserror 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/thiserror-1.0.38/Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/thiserror-1.0.38/build.rs","edition":"2018","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/build/thiserror-66462325c558a4d5/build-script-build"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"ryu 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/ryu-1.0.11/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"ryu","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/ryu-1.0.11/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libryu-0e7b0d46c4589f15.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"crossbeam-utils 0.8.12 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/crossbeam-utils-0.8.12/Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/crossbeam-utils-0.8.12/build.rs","edition":"2018","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","std"],"filenames":["/home/epage/src/personal/winnow/target/debug/build/crossbeam-utils-c9170234d86239e2/build-script-build"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"memchr 2.5.0 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/memchr-2.5.0/Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/memchr-2.5.0/build.rs","edition":"2018","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","std"],"filenames":["/home/epage/src/personal/winnow/target/debug/build/memchr-99ffc1dfd2c517c1/build-script-build"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"serde_json 1.0.86 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/serde_json-1.0.86/Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/serde_json-1.0.86/build.rs","edition":"2018","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","std"],"filenames":["/home/epage/src/personal/winnow/target/debug/build/serde_json-3051866d1babe853/build-script-build"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"memoffset 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/memoffset-0.6.5/Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/memoffset-0.6.5/build.rs","edition":"2015","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default"],"filenames":["/home/epage/src/personal/winnow/target/debug/build/memoffset-37767cb27e2441e6/build-script-build"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"bitflags 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/bitflags-1.3.2/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"bitflags","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/bitflags-1.3.2/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libbitflags-ea9a4e086e887550.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"itoa 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/itoa-1.0.4/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"itoa","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/itoa-1.0.4/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libitoa-69c375d2fd4189a8.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"crossbeam-epoch 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/crossbeam-epoch-0.9.11/Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/crossbeam-epoch-0.9.11/build.rs","edition":"2018","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["alloc","std"],"filenames":["/home/epage/src/personal/winnow/target/debug/build/crossbeam-epoch-ec0452f91ac732bb/build-script-build"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"ucd-trie 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/ucd-trie-0.1.5/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"ucd-trie","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/ucd-trie-0.1.5/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["std"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libucd_trie-eb38f7c85a03bb9d.rlib","/home/epage/src/personal/winnow/target/debug/deps/libucd_trie-eb38f7c85a03bb9d.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"io-lifetimes 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/io-lifetimes-1.0.5/Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/io-lifetimes-1.0.5/build.rs","edition":"2018","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["close","default","libc","windows-sys"],"filenames":["/home/epage/src/personal/winnow/target/debug/build/io-lifetimes-18d168bedd0c64e8/build-script-build"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"log 0.4.17 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/log-0.4.17/Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/log-0.4.17/build.rs","edition":"2015","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/build/log-52c513d099058ca0/build-script-build"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"num-traits 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/num-traits-0.2.15/Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/num-traits-0.2.15/build.rs","edition":"2015","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","std"],"filenames":["/home/epage/src/personal/winnow/target/debug/build/num-traits-77d338745cc017c3/build-script-build"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"rayon-core 1.9.3 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/rayon-core-1.9.3/Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/rayon-core-1.9.3/build.rs","edition":"2018","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/build/rayon-core-eecac7c574986103/build-script-build"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"rustix 0.36.8 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/rustix-0.36.8/Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/rustix-0.36.8/build.rs","edition":"2018","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","io-lifetimes","libc","std","termios","use-libc-auxv"],"filenames":["/home/epage/src/personal/winnow/target/debug/build/rustix-32859c5d0061115d/build-script-build"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"syn 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/syn-1.0.102/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"syn","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/syn-1.0.102/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["clone-impls","default","derive","parsing","printing","proc-macro","quote"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libsyn-c9741af862298610.rlib","/home/epage/src/personal/winnow/target/debug/deps/libsyn-c9741af862298610.rmeta"],"executable":null,"fresh":true}
{"reason":"build-script-executed","package_id":"thiserror 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)","linked_libs":[],"linked_paths":[],"cfgs":[],"env":[],"out_dir":"/home/epage/src/personal/winnow/target/debug/build/thiserror-37d8ebe1b01cc51d/out"}
{"reason":"build-script-executed","package_id":"memchr 2.5.0 (registry+https://github.com/rust-lang/crates.io-index)","linked_libs":[],"linked_paths":[],"cfgs":["memchr_runtime_simd","memchr_runtime_sse2","memchr_runtime_sse42","memchr_runtime_avx"],"env":[],"out_dir":"/home/epage/src/personal/winnow/target/debug/build/memchr-743d739a8480f48a/out"}
{"reason":"build-script-executed","package_id":"crossbeam-utils 0.8.12 (registry+https://github.com/rust-lang/crates.io-index)","linked_libs":[],"linked_paths":[],"cfgs":[],"env":[],"out_dir":"/home/epage/src/personal/winnow/target/debug/build/crossbeam-utils-1975bdb7ecfbff2a/out"}
{"reason":"build-script-executed","package_id":"serde_json 1.0.86 (registry+https://github.com/rust-lang/crates.io-index)","linked_libs":[],"linked_paths":[],"cfgs":["limb_width_64"],"env":[],"out_dir":"/home/epage/src/personal/winnow/target/debug/build/serde_json-3f569e9655a7cd7e/out"}
{"reason":"build-script-executed","package_id":"memoffset 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)","linked_libs":[],"linked_paths":[],"cfgs":["tuple_ty","allow_clippy","maybe_uninit","doctests","raw_ref_macros"],"env":[],"out_dir":"/home/epage/src/personal/winnow/target/debug/build/memoffset-27100c1e8e709074/out"}
{"reason":"build-script-executed","package_id":"crossbeam-epoch 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)","linked_libs":[],"linked_paths":[],"cfgs":[],"env":[],"out_dir":"/home/epage/src/personal/winnow/target/debug/build/crossbeam-epoch-7f643445aebfa633/out"}
{"reason":"compiler-artifact","package_id":"getrandom 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/getrandom-0.2.7/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"getrandom","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/getrandom-0.2.7/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["std"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libgetrandom-d2efdd8bbd217458.rmeta"],"executable":null,"fresh":true}
{"reason":"build-script-executed","package_id":"log 0.4.17 (registry+https://github.com/rust-lang/crates.io-index)","linked_libs":[],"linked_paths":[],"cfgs":["atomic_cas","has_atomics"],"env":[],"out_dir":"/home/epage/src/personal/winnow/target/debug/build/log-b56bc898d3207792/out"}
{"reason":"build-script-executed","package_id":"io-lifetimes 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)","linked_libs":[],"linked_paths":[],"cfgs":["io_safety_is_in_std","panic_in_const_fn"],"env":[],"out_dir":"/home/epage/src/personal/winnow/target/debug/build/io-lifetimes-701560b8574ef205/out"}
{"reason":"compiler-artifact","package_id":"once_cell 1.15.0 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/once_cell-1.15.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"once_cell","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/once_cell-1.15.0/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["alloc","default","race","std"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libonce_cell-c9f9ea925d35da52.rlib","/home/epage/src/personal/winnow/target/debug/deps/libonce_cell-c9f9ea925d35da52.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/scopeguard-1.1.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"scopeguard","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/scopeguard-1.1.0/src/lib.rs","edition":"2015","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libscopeguard-13a4bbff1ce24cc7.rmeta"],"executable":null,"fresh":true}
{"reason":"build-script-executed","package_id":"rayon-core 1.9.3 (registry+https://github.com/rust-lang/crates.io-index)","linked_libs":[],"linked_paths":[],"cfgs":[],"env":[],"out_dir":"/home/epage/src/personal/winnow/target/debug/build/rayon-core-b008d521ccec1e7b/out"}
{"reason":"build-script-executed","package_id":"rustix 0.36.8 (registry+https://github.com/rust-lang/crates.io-index)","linked_libs":[],"linked_paths":[],"cfgs":["linux_raw","asm"],"env":[],"out_dir":"/home/epage/src/personal/winnow/target/debug/build/rustix-e16304a596230d21/out"}
{"reason":"build-script-executed","package_id":"num-traits 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)","linked_libs":[],"linked_paths":[],"cfgs":["has_i128","has_to_int_unchecked","has_reverse_bits","has_leading_trailing_ones","has_int_assignop_ref","has_div_euclid","has_copysign"],"env":[],"out_dir":"/home/epage/src/personal/winnow/target/debug/build/num-traits-6888d3d0832572a9/out"}
{"reason":"compiler-artifact","package_id":"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/lazy_static-1.4.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"lazy_static","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/lazy_static-1.4.0/src/lib.rs","edition":"2015","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/liblazy_static-afa8b761bb5d6b57.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"unicode-width 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/unicode-width-0.1.10/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"unicode-width","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/unicode-width-0.1.10/src/lib.rs","edition":"2015","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libunicode_width-8dcd31c030e77d42.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"either 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/either-1.8.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"either","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/either-1.8.0/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["use_std"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libeither-3c87d508139ef632.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"linux-raw-sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/linux-raw-sys-0.1.4/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"linux-raw-sys","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/linux-raw-sys-0.1.4/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["errno","general","ioctl","no_std"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/liblinux_raw_sys-297593f8ceab708f.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"num_cpus 1.13.1 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/num_cpus-1.13.1/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"num_cpus","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/num_cpus-1.13.1/src/lib.rs","edition":"2015","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libnum_cpus-539d2b6f1744794b.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"serde_derive 1.0.145 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/serde_derive-1.0.145/Cargo.toml","target":{"kind":["proc-macro"],"crate_types":["proc-macro"],"name":"serde_derive","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/serde_derive-1.0.145/src/lib.rs","edition":"2015","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libserde_derive-7934298a61c1a1a2.so"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"thiserror-impl 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/thiserror-impl-1.0.38/Cargo.toml","target":{"kind":["proc-macro"],"crate_types":["proc-macro"],"name":"thiserror-impl","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/thiserror-impl-1.0.38/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libthiserror_impl-213d86e6d4b69b5f.so"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"memchr 2.5.0 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/memchr-2.5.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"memchr","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/memchr-2.5.0/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","std"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libmemchr-ae9722f3894e3314.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"crossbeam-utils 0.8.12 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/crossbeam-utils-0.8.12/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"crossbeam-utils","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/crossbeam-utils-0.8.12/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","std"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libcrossbeam_utils-59f0a08b7eefe073.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"memoffset 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/memoffset-0.6.5/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"memoffset","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/memoffset-0.6.5/src/lib.rs","edition":"2015","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libmemoffset-4787845978faa8b8.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"io-lifetimes 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/io-lifetimes-1.0.5/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"io-lifetimes","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/io-lifetimes-1.0.5/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["close","default","libc","windows-sys"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libio_lifetimes-0d158e24024d572c.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"log 0.4.17 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/log-0.4.17/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"log","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/log-0.4.17/src/lib.rs","edition":"2015","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/liblog-3242a8c3b3d72769.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"rand_core 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/rand_core-0.6.4/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"rand_core","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/rand_core-0.6.4/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["alloc","getrandom","std"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/librand_core-aa1df72cb81e420e.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"num-traits 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/num-traits-0.2.15/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"num-traits","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/num-traits-0.2.15/src/lib.rs","edition":"2015","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","std"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libnum_traits-1c8d0251ac4ea61d.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"rayon 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/rayon-1.5.3/Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/rayon-1.5.3/build.rs","edition":"2018","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/build/rayon-4f0513187044c0f2/build-script-build"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"escargot 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/escargot-0.5.7/Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/escargot-0.5.7/build.rs","edition":"2018","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/build/escargot-44a89e78cfd0d26f/build-script-build"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"yansi 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/yansi-0.5.1/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"yansi","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/yansi-0.5.1/src/lib.rs","edition":"2015","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libyansi-62809433fc3ff8e9.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"regex-automata 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/regex-automata-0.1.10/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"regex-automata","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/regex-automata-0.1.10/src/lib.rs","edition":"2015","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libregex_automata-741e79c4b6938d7d.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"remove_dir_all 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/remove_dir_all-0.5.3/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"remove_dir_all","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/remove_dir_all-0.5.3/src/lib.rs","edition":"2015","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libremove_dir_all-bcafaf4f00d8e4a4.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"regex-syntax 0.6.27 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/regex-syntax-0.6.27/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"regex-syntax","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/regex-syntax-0.6.27/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","unicode","unicode-age","unicode-bool","unicode-case","unicode-gencat","unicode-perl","unicode-script","unicode-segment"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libregex_syntax-3fe0a07eb00f644f.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"ucd-trie 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/ucd-trie-0.1.5/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"ucd-trie","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/ucd-trie-0.1.5/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["std"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libucd_trie-b01a07ea40c7e7ed.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"plotters-backend 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/plotters-backend-0.3.4/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"plotters-backend","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/plotters-backend-0.3.4/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libplotters_backend-34c8fa8c2b121eeb.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"ppv-lite86 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/ppv-lite86-0.2.16/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"ppv-lite86","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/ppv-lite86-0.2.16/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["simd","std"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libppv_lite86-3b9c603c4b32aff6.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"fastrand 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/fastrand-1.8.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"fastrand","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/fastrand-1.8.0/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libfastrand-52ad6b37c39c8a90.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"itertools 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/itertools-0.10.5/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"itertools","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/itertools-0.10.5/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":false},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","use_alloc","use_std"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libitertools-ba05d1064477b941.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"serde 1.0.145 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.145/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"serde","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.145/src/lib.rs","edition":"2015","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","derive","serde_derive","std"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libserde-e026f194fb97de03.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"thiserror 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/thiserror-1.0.38/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"thiserror","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/thiserror-1.0.38/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libthiserror-4481f6326f711f93.rlib","/home/epage/src/personal/winnow/target/debug/deps/libthiserror-4481f6326f711f93.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"crossbeam-epoch 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/crossbeam-epoch-0.9.11/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"crossbeam-epoch","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/crossbeam-epoch-0.9.11/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["alloc","std"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libcrossbeam_epoch-caa786e06425cee9.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"thiserror 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/thiserror-1.0.38/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"thiserror","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/thiserror-1.0.38/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libthiserror-9187d79370422188.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"rustix 0.36.8 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/rustix-0.36.8/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"rustix","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/rustix-0.36.8/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","io-lifetimes","libc","std","termios","use-libc-auxv"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/librustix-ec48622574cca747.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"crossbeam-channel 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/crossbeam-channel-0.5.6/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"crossbeam-channel","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/crossbeam-channel-0.5.6/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["crossbeam-utils","default","std"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libcrossbeam_channel-a9b11ccef7773884.rmeta"],"executable":null,"fresh":true}
{"reason":"build-script-executed","package_id":"escargot 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)","linked_libs":[],"linked_paths":[],"cfgs":[],"env":[],"out_dir":"/home/epage/src/personal/winnow/target/debug/build/escargot-5711ac23b4781245/out"}
{"reason":"compiler-artifact","package_id":"tempfile 3.3.0 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/tempfile-3.3.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"tempfile","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/tempfile-3.3.0/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libtempfile-8b83de608c2a0658.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"rand_chacha 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/rand_chacha-0.3.1/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"rand_chacha","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/rand_chacha-0.3.1/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["std"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/librand_chacha-5967d0068d39b018.rmeta"],"executable":null,"fresh":true}
{"reason":"build-script-executed","package_id":"rayon 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)","linked_libs":[],"linked_paths":[],"cfgs":["has_step_by_rev","has_min_const_generics","has_control_flow"],"env":[],"out_dir":"/home/epage/src/personal/winnow/target/debug/build/rayon-d01aa17f59a1e98f/out"}
{"reason":"compiler-artifact","package_id":"plotters-svg 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/plotters-svg-0.3.3/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"plotters-svg","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/plotters-svg-0.3.3/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libplotters_svg-2bb014e4a5d863b1.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"csv-core 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/csv-core-0.1.10/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"csv-core","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/csv-core-0.1.10/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libcsv_core-bf47a6ecf716f212.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/textwrap-0.11.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"textwrap","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/textwrap-0.11.0/src/lib.rs","edition":"2015","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libtextwrap-1f9a5b633ba2872d.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/atty-0.2.14/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"atty","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/atty-0.2.14/src/lib.rs","edition":"2015","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libatty-7590cc9da5872c3b.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"wait-timeout 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/wait-timeout-0.2.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"wait-timeout","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/wait-timeout-0.2.0/src/lib.rs","edition":"2015","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libwait_timeout-d79299ed1c2df76b.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"diff 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/diff-0.1.13/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"diff","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/diff-0.1.13/src/lib.rs","edition":"2015","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libdiff-a3c1327ada950886.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"concolor-query 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/concolor-query-0.1.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"concolor-query","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/concolor-query-0.1.0/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["windows"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libconcolor_query-1b1f33bda6da9636.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/same-file-1.0.6/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"same-file","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/same-file-1.0.6/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libsame_file-8dba8c6e6bbcef0a.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"bit-vec 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/bit-vec-0.6.3/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"bit-vec","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/bit-vec-0.6.3/src/lib.rs","edition":"2015","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["std"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libbit_vec-5cc1be6dbbb7d172.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"fnv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/fnv-1.0.7/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"fnv","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/fnv-1.0.7/lib.rs","edition":"2015","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","std"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libfnv-e811c8615aede027.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"serde_json 1.0.86 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/serde_json-1.0.86/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"serde_json","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/serde_json-1.0.86/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","std"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libserde_json-10d232e85191c379.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"pest 2.5.5 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/pest-2.5.5/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"pest","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/pest-2.5.5/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","std","thiserror"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libpest-c28124a2ace8ce41.rlib","/home/epage/src/personal/winnow/target/debug/deps/libpest-c28124a2ace8ce41.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"crossbeam-deque 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/crossbeam-deque-0.8.2/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"crossbeam-deque","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/crossbeam-deque-0.8.2/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["crossbeam-epoch","crossbeam-utils","default","std"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libcrossbeam_deque-a531937c1e514cf3.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"bstr 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/bstr-0.2.17/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"bstr","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/bstr-0.2.17/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","lazy_static","regex-automata","serde","serde1","serde1-nostd","std","unicode"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libbstr-3245cbc7e1b4eee0.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"is-terminal 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/is-terminal-0.4.3/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"is-terminal","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/is-terminal-0.4.3/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libis_terminal-ddb4d6f1cea88415.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"pest 2.5.5 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/pest-2.5.5/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"pest","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/pest-2.5.5/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","std","thiserror"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libpest-3fe2406158c36e91.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"doc-comment 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/doc-comment-0.3.3/Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/doc-comment-0.3.3/build.rs","edition":"2015","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/build/doc-comment-476a3be5ae9523a0/build-script-build"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"cast 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/cast-0.3.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"cast","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/cast-0.3.0/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libcast-0f6ca0f808d89c22.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"half 1.8.2 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/half-1.8.2/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"half","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/half-1.8.2/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libhalf-ec0164f76c2e0030.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/quick-error-1.2.3/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"quick-error","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/quick-error-1.2.3/src/lib.rs","edition":"2015","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libquick_error-e7675fcf1fdb276d.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"once_cell 1.15.0 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/once_cell-1.15.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"once_cell","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/once_cell-1.15.0/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["alloc","default","race","std"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libonce_cell-6a83ca81f3790104.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"itoa 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/itoa-0.4.8/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"itoa","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/itoa-0.4.8/src/lib.rs","edition":"2015","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","std"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libitoa-af0357b7d39004b0.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"plotters 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/plotters-0.3.4/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"plotters","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/plotters-0.3.4/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["area_series","line_series","plotters-svg","svg_backend"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libplotters-c6ddec411d8166e5.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"walkdir 2.3.2 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/walkdir-2.3.2/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"walkdir","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/walkdir-2.3.2/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libwalkdir-9e7e41a0b2bf038d.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"bit-set 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/bit-set-0.5.3/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"bit-set","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/bit-set-0.5.3/src/lib.rs","edition":"2015","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","std"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libbit_set-2aef4909cbe7f4dc.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"pretty_assertions 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/pretty_assertions-1.3.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"pretty_assertions","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/pretty_assertions-1.3.0/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","std"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libpretty_assertions-917f731810bb748b.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"clap 2.34.0 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/clap-2.34.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"clap","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/clap-2.34.0/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libclap-af397f0f338fc8b9.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"rand 0.8.5 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/rand-0.8.5/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"rand","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/rand-0.8.5/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["alloc","getrandom","libc","rand_chacha","std"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/librand-79231f927b479fcb.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"quick-xml 0.23.1 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/quick-xml-0.23.1/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"quick-xml","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/quick-xml-0.23.1/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libquick_xml-56f1f63175d80ec8.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"rand_xorshift 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/rand_xorshift-0.3.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"rand_xorshift","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/rand_xorshift-0.3.0/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/librand_xorshift-52cda07a05fef0b5.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"pest_meta 2.5.5 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/pest_meta-2.5.5/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"pest_meta","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/pest_meta-2.5.5/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libpest_meta-e592b8a19ab895e0.rlib","/home/epage/src/personal/winnow/target/debug/deps/libpest_meta-e592b8a19ab895e0.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"rayon-core 1.9.3 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/rayon-core-1.9.3/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"rayon-core","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/rayon-core-1.9.3/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/librayon_core-28c7fd349d6eb8a3.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"tinytemplate 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/tinytemplate-1.2.1/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"tinytemplate","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/tinytemplate-1.2.1/src/lib.rs","edition":"2015","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libtinytemplate-1e34f9b3f54388b1.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"serde_cbor 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/serde_cbor-0.11.2/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"serde_cbor","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/serde_cbor-0.11.2/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","std"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libserde_cbor-a6696a57f0dc1b3e.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"escargot 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/escargot-0.5.7/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"escargot","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/escargot-0.5.7/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libescargot-8a922c953f0ce0d3.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"csv 1.1.6 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/csv-1.1.6/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"csv","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/csv-1.1.6/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libcsv-66898c2026240d6e.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"rusty-fork 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/rusty-fork-0.3.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"rusty-fork","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/rusty-fork-0.3.0/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["timeout","wait-timeout"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/librusty_fork-3ff190906b9b0c88.rmeta"],"executable":null,"fresh":true}
{"reason":"build-script-executed","package_id":"doc-comment 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)","linked_libs":[],"linked_paths":[],"cfgs":[],"env":[],"out_dir":"/home/epage/src/personal/winnow/target/debug/build/doc-comment-9f27b6aeba0913e7/out"}
{"reason":"compiler-artifact","package_id":"criterion-plot 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/criterion-plot-0.4.5/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"criterion-plot","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/criterion-plot-0.4.5/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libcriterion_plot-dc7b9242779f53d6.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"concolor 0.0.11 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/concolor-0.0.11/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"concolor","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/concolor-0.0.11/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["auto","bitflags","clicolor","concolor-query","core","interactive","no_color","std","term","windows"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libconcolor-c2d97e9aa66666d7.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"regex 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/regex-1.6.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"regex","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/regex-1.6.0/src/lib.rs","edition":"2018","doc":true,"doctest":false,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["std"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libregex-14a9fe50552aac49.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"os_pipe 1.1.3 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/os_pipe-1.1.3/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"os_pipe","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/os_pipe-1.1.3/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libos_pipe-be2a22288f932a49.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"quick-error 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/quick-error-2.0.1/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"quick-error","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/quick-error-2.0.1/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libquick_error-280edfc32cc83812.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"normalize-line-endings 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/normalize-line-endings-0.3.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"normalize-line-endings","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/normalize-line-endings-0.3.0/src/lib.rs","edition":"2015","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libnormalize_line_endings-8a66ba357a389996.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"bytecount 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/bytecount-0.6.3/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"bytecount","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/bytecount-0.6.3/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libbytecount-c083e2cdbbb4f003.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"snapbox-macros 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/snapbox-macros-0.3.1/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"snapbox-macros","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/snapbox-macros-0.3.1/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libsnapbox_macros-d9da77d55f4279bc.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"oorandom 11.1.3 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/oorandom-11.1.3/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"oorandom","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/oorandom-11.1.3/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/liboorandom-052fb0be6ac16c94.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"byteorder 1.4.3 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/byteorder-1.4.3/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"byteorder","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/byteorder-1.4.3/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["std"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libbyteorder-9f6a3ecb302657b0.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"similar 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/similar-2.2.1/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"similar","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/similar-2.2.1/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","inline","text"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libsimilar-ca2082771c207a3c.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"termcolor 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/termcolor-1.2.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"termcolor","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/termcolor-1.2.0/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libtermcolor-dad1a04bc5d2f742.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"pest_generator 2.5.5 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/pest_generator-2.5.5/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"pest_generator","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/pest_generator-2.5.5/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["std"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libpest_generator-17d62a3fe28e46f5.rlib","/home/epage/src/personal/winnow/target/debug/deps/libpest_generator-17d62a3fe28e46f5.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"rayon 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/rayon-1.5.3/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"rayon","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/rayon-1.5.3/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/librayon-41dd0a10f9292557.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"proptest 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/proptest-1.0.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"proptest","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/proptest-1.0.0/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["bit-set","break-dead-code","default","fork","lazy_static","quick-error","regex-syntax","rusty-fork","std","tempfile","timeout"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libproptest-7f94966e4936da95.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"snapbox 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/snapbox-0.4.6/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"snapbox","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/snapbox-0.4.6/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["color","color-auto","concolor","default","diff","examples"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libsnapbox-d99468a5201f9b87.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"doc-comment 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/doc-comment-0.3.3/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"doc_comment","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/doc-comment-0.3.3/src/lib.rs","edition":"2015","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libdoc_comment-5644793091a6953d.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"lexopt 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/lexopt-0.3.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"lexopt","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/lexopt-0.3.0/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/liblexopt-c945e030a97b8e1f.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"circular 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/circular-0.3.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"circular","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/circular-0.3.0/src/lib.rs","edition":"2015","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libcircular-6190d46717d189a0.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"winnow 0.3.1 (path+file:///home/epage/src/personal/winnow)","manifest_path":"/home/epage/src/personal/winnow/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"winnow","src_path":"/home/epage/src/personal/winnow/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["alloc","default","std"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libwinnow-a64b99fd45b2e97c.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"pest_derive 2.5.5 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/pest_derive-2.5.5/Cargo.toml","target":{"kind":["proc-macro"],"crate_types":["proc-macro"],"name":"pest_derive","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/pest_derive-2.5.5/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","std"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libpest_derive-23acec6b80f586aa.so"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"criterion 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/criterion-0.3.6/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"criterion","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/criterion-0.3.6/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["cargo_bench_support","default"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libcriterion-864d2d30e85a25cf.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"handlebars 4.3.6 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/handlebars-4.3.6/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"handlebars","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/handlebars-4.3.6/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libhandlebars-8f7ca769e2915c7a.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"term-transcript 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)","manifest_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/term-transcript-0.2.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"term-transcript","src_path":"/home/epage/.cargo/registry/src/github.com-1ecc6299db9ec823/term-transcript-0.2.0/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["atty","default","handlebars","pretty_assertions","quick-xml","serde","svg","test"],"filenames":["/home/epage/src/personal/winnow/target/debug/deps/libterm_transcript-67537cf74f0568cc.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"winnow 0.3.1 (path+file:///home/epage/src/personal/winnow)","manifest_path":"/home/epage/src/personal/winnow/Cargo.toml","target":{"kind":["example"],"crate_types":["bin"],"name":"ini","src_path":"/home/epage/src/personal/winnow/examples/ini/main.rs","edition":"2021","required-features":["std"],"doc":false,"doctest":false,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["alloc","default","std"],"filenames":["/home/epage/src/personal/winnow/target/debug/examples/libini-029ff669d11d054a.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"winnow 0.3.1 (path+file:///home/epage/src/personal/winnow)","manifest_path":"/home/epage/src/personal/winnow/Cargo.toml","target":{"kind":["example"],"crate_types":["bin"],"name":"custom_error","src_path":"/home/epage/src/personal/winnow/examples/custom_error.rs","edition":"2021","required-features":["alloc"],"doc":false,"doctest":false,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["alloc","default","std"],"filenames":["/home/epage/src/personal/winnow/target/debug/examples/libcustom_error-8382fb7d49937909.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"winnow 0.3.1 (path+file:///home/epage/src/personal/winnow)","manifest_path":"/home/epage/src/personal/winnow/Cargo.toml","target":{"kind":["example"],"crate_types":["bin"],"name":"s_expression","src_path":"/home/epage/src/personal/winnow/examples/s_expression/main.rs","edition":"2021","required-features":["alloc"],"doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["alloc","default","std"],"filenames":["/home/epage/src/personal/winnow/target/debug/examples/libs_expression-0161e16eb3795e0c.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"winnow 0.3.1 (path+file:///home/epage/src/personal/winnow)","manifest_path":"/home/epage/src/personal/winnow/Cargo.toml","target":{"kind":["example"],"crate_types":["bin"],"name":"css","src_path":"/home/epage/src/personal/winnow/examples/css/main.rs","edition":"2021","doc":false,"doctest":false,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["alloc","default","std"],"filenames":["/home/epage/src/personal/winnow/target/debug/examples/libcss-d003331b0eaf24a5.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"winnow 0.3.1 (path+file:///home/epage/src/personal/winnow)","manifest_path":"/home/epage/src/personal/winnow/Cargo.toml","target":{"kind":["example"],"crate_types":["bin"],"name":"json","src_path":"/home/epage/src/personal/winnow/examples/json/main.rs","edition":"2021","required-features":["std"],"doc":false,"doctest":false,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["alloc","default","std"],"filenames":["/home/epage/src/personal/winnow/target/debug/examples/libjson-20da0c01e9db35aa.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"winnow 0.3.1 (path+file:///home/epage/src/personal/winnow)","manifest_path":"/home/epage/src/personal/winnow/Cargo.toml","target":{"kind":["example"],"crate_types":["bin"],"name":"ndjson","src_path":"/home/epage/src/personal/winnow/examples/ndjson/main.rs","edition":"2021","required-features":["std"],"doc":false,"doctest":false,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["alloc","default","std"],"filenames":["/home/epage/src/personal/winnow/target/debug/examples/libndjson-17afbe6b158251ab.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"winnow 0.3.1 (path+file:///home/epage/src/personal/winnow)","manifest_path":"/home/epage/src/personal/winnow/Cargo.toml","target":{"kind":["example"],"crate_types":["bin"],"name":"http","src_path":"/home/epage/src/personal/winnow/examples/http/main.rs","edition":"2021","required-features":["alloc"],"doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["alloc","default","std"],"filenames":["/home/epage/src/personal/winnow/target/debug/examples/libhttp-08613cd431f59551.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"winnow 0.3.1 (path+file:///home/epage/src/personal/winnow)","manifest_path":"/home/epage/src/personal/winnow/Cargo.toml","target":{"kind":["example"],"crate_types":["bin"],"name":"iterator","src_path":"/home/epage/src/personal/winnow/examples/iterator.rs","edition":"2021","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["alloc","default","std"],"filenames":["/home/epage/src/personal/winnow/target/debug/examples/libiterator-46a33f71a5378497.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"winnow 0.3.1 (path+file:///home/epage/src/personal/winnow)","manifest_path":"/home/epage/src/personal/winnow/Cargo.toml","target":{"kind":["example"],"crate_types":["bin"],"name":"string","src_path":"/home/epage/src/personal/winnow/examples/string/main.rs","edition":"2021","required-features":["alloc"],"doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["alloc","default","std"],"filenames":["/home/epage/src/personal/winnow/target/debug/examples/libstring-73fadc9999eff689.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"winnow 0.3.1 (path+file:///home/epage/src/personal/winnow)","manifest_path":"/home/epage/src/personal/winnow/Cargo.toml","target":{"kind":["example"],"crate_types":["bin"],"name":"arithmetic","src_path":"/home/epage/src/personal/winnow/examples/arithmetic/main.rs","edition":"2021","required-features":["alloc"],"doc":false,"doctest":false,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["alloc","default","std"],"filenames":["/home/epage/src/personal/winnow/target/debug/examples/libarithmetic-04ca5fb45c28ebc2.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"winnow 0.3.1 (path+file:///home/epage/src/personal/winnow)","manifest_path":"/home/epage/src/personal/winnow/Cargo.toml","target":{"kind":["example"],"crate_types":["bin"],"name":"json_iterator","src_path":"/home/epage/src/personal/winnow/examples/json_iterator.rs","edition":"2021","required-features":["std"],"doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["alloc","default","std"],"filenames":["/home/epage/src/personal/winnow/target/debug/examples/libjson_iterator-9bb330a957b0d53d.rmeta"],"executable":null,"fresh":true}
{"reason":"build-finished","success":true}

View file

@ -0,0 +1,114 @@
mod parser;
use std::io::Read;
use winnow::error::ErrMode;
use winnow::error::InputError;
use winnow::error::Needed;
use winnow::prelude::*;
use winnow::stream::Offset;
fn main() -> Result<(), lexopt::Error> {
let args = Args::parse()?;
let input = args.input.ok_or_else(|| lexopt::Error::MissingValue {
option: Some("<PATH>".to_owned()),
})?;
let mut file = std::fs::File::open(&input).map_err(to_lexopt)?;
// Intentionally starting with a small buffer to make it easier to show `Incomplete` handling
let buffer_size = 10;
let min_buffer_growth = 100;
let buffer_growth_factor = 2;
let mut buffer = circular::Buffer::with_capacity(buffer_size);
loop {
let read = file.read(buffer.space()).map_err(to_lexopt)?;
eprintln!("read {}", read);
if read == 0 {
// Should be EOF since we always make sure there is `available_space`
assert_ne!(buffer.available_space(), 0);
assert_eq!(
buffer.available_data(),
0,
"leftover data: {}",
String::from_utf8_lossy(buffer.data())
);
break;
}
buffer.fill(read);
loop {
let input = parser::Stream::new(std::str::from_utf8(buffer.data()).map_err(to_lexopt)?);
match parser::ndjson::<InputError<parser::Stream>>.parse_peek(input) {
Ok((remainder, value)) => {
println!("{:?}", value);
println!();
// Tell the buffer how much we read
let consumed = remainder.offset_from(&input);
buffer.consume(consumed);
}
Err(ErrMode::Backtrack(e)) | Err(ErrMode::Cut(e)) => {
return Err(fmt_lexopt(e.to_string()));
}
Err(ErrMode::Incomplete(Needed::Size(size))) => {
// Without the format telling us how much space is required, we really should
// treat this the same as `Unknown` but are doing this to demonstrate how to
// handle `Size`.
//
// Even when the format has a header to tell us `Size`, we could hit incidental
// `Size(1)`s, so make sure we buffer more space than that to avoid reading
// one byte at a time
let head_room = size.get().max(min_buffer_growth);
let new_capacity = buffer.available_data() + head_room;
eprintln!("growing buffer to {}", new_capacity);
buffer.grow(new_capacity);
if buffer.available_space() < head_room {
eprintln!("buffer shift");
buffer.shift();
}
break;
}
Err(ErrMode::Incomplete(Needed::Unknown)) => {
let new_capacity = buffer_growth_factor * buffer.capacity();
eprintln!("growing buffer to {}", new_capacity);
buffer.grow(new_capacity);
break;
}
}
}
}
Ok(())
}
#[derive(Default)]
struct Args {
input: Option<std::path::PathBuf>,
}
impl Args {
fn parse() -> Result<Self, lexopt::Error> {
use lexopt::prelude::*;
let mut res = Args::default();
let mut args = lexopt::Parser::from_env();
while let Some(arg) = args.next()? {
match arg {
Value(input) => {
res.input = Some(input.into());
}
_ => return Err(arg.unexpected()),
}
}
Ok(res)
}
}
fn to_lexopt(e: impl std::error::Error + Send + Sync + 'static) -> lexopt::Error {
lexopt::Error::Custom(Box::new(e))
}
fn fmt_lexopt(e: String) -> lexopt::Error {
lexopt::Error::Custom(e.into())
}

View file

@ -0,0 +1,344 @@
use std::collections::HashMap;
use std::str;
use winnow::prelude::*;
use winnow::{
ascii::float,
ascii::line_ending,
combinator::alt,
combinator::cut_err,
combinator::{delimited, preceded, separated_pair, terminated},
combinator::{repeat, separated},
error::{AddContext, ParserError},
stream::Partial,
token::{any, none_of, take, take_while},
};
#[derive(Debug, PartialEq, Clone)]
pub enum JsonValue {
Null,
Boolean(bool),
Str(String),
Num(f64),
Array(Vec<JsonValue>),
Object(HashMap<String, JsonValue>),
}
/// Use `Partial` to cause `ErrMode::Incomplete` while parsing
pub type Stream<'i> = Partial<&'i str>;
pub fn ndjson<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, &'static str>>(
input: &mut Stream<'i>,
) -> PResult<Option<JsonValue>, E> {
alt((
terminated(delimited(ws, json_value, ws), line_ending).map(Some),
line_ending.value(None),
))
.parse_next(input)
}
// --Besides `WS`, same as a regular json parser ----------------------------
/// `alt` is a combinator that tries multiple parsers one by one, until
/// one of them succeeds
fn json_value<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, &'static str>>(
input: &mut Stream<'i>,
) -> PResult<JsonValue, E> {
// `alt` combines the each value parser. It returns the result of the first
// successful parser, or an error
alt((
null.value(JsonValue::Null),
boolean.map(JsonValue::Boolean),
string.map(JsonValue::Str),
float.map(JsonValue::Num),
array.map(JsonValue::Array),
object.map(JsonValue::Object),
))
.parse_next(input)
}
/// `tag(string)` generates a parser that recognizes the argument string.
///
/// This also shows returning a sub-slice of the original input
fn null<'i, E: ParserError<Stream<'i>>>(input: &mut Stream<'i>) -> PResult<&'i str, E> {
// This is a parser that returns `"null"` if it sees the string "null", and
// an error otherwise
"null".parse_next(input)
}
/// We can combine `tag` with other functions, like `value` which returns a given constant value on
/// success.
fn boolean<'i, E: ParserError<Stream<'i>>>(input: &mut Stream<'i>) -> PResult<bool, E> {
// This is a parser that returns `true` if it sees the string "true", and
// an error otherwise
let parse_true = "true".value(true);
// This is a parser that returns `false` if it sees the string "false", and
// an error otherwise
let parse_false = "false".value(false);
alt((parse_true, parse_false)).parse_next(input)
}
/// This parser gathers all `char`s up into a `String`with a parse to recognize the double quote
/// character, before the string (using `preceded`) and after the string (using `terminated`).
fn string<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, &'static str>>(
input: &mut Stream<'i>,
) -> PResult<String, E> {
preceded(
'\"',
// `cut_err` transforms an `ErrMode::Backtrack(e)` to `ErrMode::Cut(e)`, signaling to
// combinators like `alt` that they should not try other parsers. We were in the
// right branch (since we found the `"` character) but encountered an error when
// parsing the string
cut_err(terminated(
repeat(0.., character).fold(String::new, |mut string, c| {
string.push(c);
string
}),
'\"',
)),
)
// `context` lets you add a static string to errors to provide more information in the
// error chain (to indicate which parser had an error)
.context("string")
.parse_next(input)
}
/// You can mix the above declarative parsing with an imperative style to handle more unique cases,
/// like escaping
fn character<'i, E: ParserError<Stream<'i>>>(input: &mut Stream<'i>) -> PResult<char, E> {
let c = none_of('"').parse_next(input)?;
if c == '\\' {
alt((
any.verify_map(|c| {
Some(match c {
'"' | '\\' | '/' => c,
'b' => '\x08',
'f' => '\x0C',
'n' => '\n',
'r' => '\r',
't' => '\t',
_ => return None,
})
}),
preceded('u', unicode_escape),
))
.parse_next(input)
} else {
Ok(c)
}
}
fn unicode_escape<'i, E: ParserError<Stream<'i>>>(input: &mut Stream<'i>) -> PResult<char, E> {
alt((
// Not a surrogate
u16_hex
.verify(|cp| !(0xD800..0xE000).contains(cp))
.map(|cp| cp as u32),
// See https://en.wikipedia.org/wiki/UTF-16#Code_points_from_U+010000_to_U+10FFFF for details
separated_pair(u16_hex, "\\u", u16_hex)
.verify(|(high, low)| (0xD800..0xDC00).contains(high) && (0xDC00..0xE000).contains(low))
.map(|(high, low)| {
let high_ten = (high as u32) - 0xD800;
let low_ten = (low as u32) - 0xDC00;
(high_ten << 10) + low_ten + 0x10000
}),
))
.verify_map(
// Could be probably replaced with .unwrap() or _unchecked due to the verify checks
std::char::from_u32,
)
.parse_next(input)
}
fn u16_hex<'i, E: ParserError<Stream<'i>>>(input: &mut Stream<'i>) -> PResult<u16, E> {
take(4usize)
.verify_map(|s| u16::from_str_radix(s, 16).ok())
.parse_next(input)
}
/// Some combinators, like `separated` or `repeat`, will call a parser repeatedly,
/// accumulating results in a `Vec`, until it encounters an error.
/// If you want more control on the parser application, check out the `iterator`
/// combinator (cf `examples/iterator.rs`)
fn array<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, &'static str>>(
input: &mut Stream<'i>,
) -> PResult<Vec<JsonValue>, E> {
preceded(
('[', ws),
cut_err(terminated(
separated(0.., json_value, (ws, ',', ws)),
(ws, ']'),
)),
)
.context("array")
.parse_next(input)
}
fn object<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, &'static str>>(
input: &mut Stream<'i>,
) -> PResult<HashMap<String, JsonValue>, E> {
preceded(
('{', ws),
cut_err(terminated(
separated(0.., key_value, (ws, ',', ws)),
(ws, '}'),
)),
)
.context("object")
.parse_next(input)
}
fn key_value<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, &'static str>>(
input: &mut Stream<'i>,
) -> PResult<(String, JsonValue), E> {
separated_pair(string, cut_err((ws, ':', ws)), json_value).parse_next(input)
}
/// Parser combinators are constructed from the bottom up:
/// first we write parsers for the smallest elements (here a space character),
/// then we'll combine them in larger parsers
fn ws<'i, E: ParserError<Stream<'i>>>(input: &mut Stream<'i>) -> PResult<&'i str, E> {
// Combinators like `take_while` return a function. That function is the
// parser,to which we can pass the input
take_while(0.., WS).parse_next(input)
}
const WS: &[char] = &[' ', '\t'];
#[cfg(test)]
mod test {
#[allow(clippy::useless_attribute)]
#[allow(dead_code)] // its dead for benches
use super::*;
#[allow(clippy::useless_attribute)]
#[allow(dead_code)] // its dead for benches
type Error<'i> = winnow::error::InputError<Partial<&'i str>>;
#[test]
fn json_string() {
assert_eq!(
string::<Error<'_>>.parse_peek(Partial::new("\"\"")),
Ok((Partial::new(""), "".to_string()))
);
assert_eq!(
string::<Error<'_>>.parse_peek(Partial::new("\"abc\"")),
Ok((Partial::new(""), "abc".to_string()))
);
assert_eq!(
string::<Error<'_>>.parse_peek(Partial::new(
"\"abc\\\"\\\\\\/\\b\\f\\n\\r\\t\\u0001\\u2014\u{2014}def\""
)),
Ok((
Partial::new(""),
"abc\"\\/\x08\x0C\n\r\t\x01——def".to_string()
)),
);
assert_eq!(
string::<Error<'_>>.parse_peek(Partial::new("\"\\uD83D\\uDE10\"")),
Ok((Partial::new(""), "😐".to_string()))
);
assert!(string::<Error<'_>>.parse_peek(Partial::new("\"")).is_err());
assert!(string::<Error<'_>>
.parse_peek(Partial::new("\"abc"))
.is_err());
assert!(string::<Error<'_>>
.parse_peek(Partial::new("\"\\\""))
.is_err());
assert!(string::<Error<'_>>
.parse_peek(Partial::new("\"\\u123\""))
.is_err());
assert!(string::<Error<'_>>
.parse_peek(Partial::new("\"\\uD800\""))
.is_err());
assert!(string::<Error<'_>>
.parse_peek(Partial::new("\"\\uD800\\uD800\""))
.is_err());
assert!(string::<Error<'_>>
.parse_peek(Partial::new("\"\\uDC00\""))
.is_err());
}
#[test]
fn json_object() {
use JsonValue::{Num, Object, Str};
let input = r#"{"a":42,"b":"x"}
"#;
let expected = Object(
vec![
("a".to_string(), Num(42.0)),
("b".to_string(), Str("x".to_string())),
]
.into_iter()
.collect(),
);
assert_eq!(
ndjson::<Error<'_>>.parse_peek(Partial::new(input)),
Ok((Partial::new(""), Some(expected)))
);
}
#[test]
fn json_array() {
use JsonValue::{Array, Num, Str};
let input = r#"[42,"x"]
"#;
let expected = Array(vec![Num(42.0), Str("x".to_string())]);
assert_eq!(
ndjson::<Error<'_>>.parse_peek(Partial::new(input)),
Ok((Partial::new(""), Some(expected)))
);
}
#[test]
fn json_whitespace() {
use JsonValue::{Array, Boolean, Null, Num, Object, Str};
let input = r#" { "null" : null, "true" :true , "false": false , "number" : 123e4 , "string" : " abc 123 " , "array" : [ false , 1 , "two" ] , "object" : { "a" : 1.0 , "b" : "c" } , "empty_array" : [ ] , "empty_object" : { } }
"#;
assert_eq!(
ndjson::<Error<'_>>.parse_peek(Partial::new(input)),
Ok((
Partial::new(""),
Some(Object(
vec![
("null".to_string(), Null),
("true".to_string(), Boolean(true)),
("false".to_string(), Boolean(false)),
("number".to_string(), Num(123e4)),
("string".to_string(), Str(" abc 123 ".to_string())),
(
"array".to_string(),
Array(vec![Boolean(false), Num(1.0), Str("two".to_string())])
),
(
"object".to_string(),
Object(
vec![
("a".to_string(), Num(1.0)),
("b".to_string(), Str("c".to_string())),
]
.into_iter()
.collect()
)
),
("empty_array".to_string(), Array(vec![]),),
("empty_object".to_string(), Object(HashMap::new()),),
]
.into_iter()
.collect()
))
))
);
}
}

View file

@ -0,0 +1,20 @@
//! In this example we build an [S-expression](https://en.wikipedia.org/wiki/S-expression)
//! parser and tiny [lisp](https://en.wikipedia.org/wiki/Lisp_(programming_language)) interpreter.
//! Lisp is a simple type of language made up of Atoms and Lists, forming easily parsable trees.
#![cfg(feature = "alloc")]
mod parser;
fn main() {
let expression_1 = "((if (= (+ 3 (/ 9 3))
(* 2 3))
*
/)
456 123)";
println!(
"\"{}\"\nevaled gives us: {:?}",
expression_1,
parser::eval_from_str(expression_1)
);
}

View file

@ -0,0 +1,361 @@
//! In this example we build an [S-expression](https://en.wikipedia.org/wiki/S-expression)
//! parser and tiny [lisp](https://en.wikipedia.org/wiki/Lisp_(programming_language)) interpreter.
//! Lisp is a simple type of language made up of Atoms and Lists, forming easily parsable trees.
use winnow::{
ascii::{alpha1, digit1, multispace0, multispace1},
combinator::alt,
combinator::repeat,
combinator::{cut_err, opt},
combinator::{delimited, preceded, terminated},
error::ContextError,
error::StrContext,
prelude::*,
token::one_of,
};
/// We start with a top-level function to tie everything together, letting
/// us call eval on a string directly
pub fn eval_from_str(src: &str) -> Result<Expr, String> {
parse_expr
.parse(src)
.map_err(|e| e.to_string())
.and_then(|exp| eval_expression(exp).ok_or_else(|| "Eval failed".to_string()))
}
/// For parsing, we start by defining the types that define the shape of data that we want.
/// In this case, we want something tree-like
/// The remaining half is Lists. We implement these as recursive Expressions.
/// For a list of numbers, we have `'(1 2 3)`, which we'll parse to:
/// ```
/// Expr::Quote(vec![Expr::Constant(Atom::Num(1)),
/// Expr::Constant(Atom::Num(2)),
/// Expr::Constant(Atom::Num(3))])
/// Quote takes an S-expression and prevents evaluation of it, making it a data
/// structure that we can deal with programmatically. Thus any valid expression
/// is also a valid data structure in Lisp itself.
#[derive(Debug, Eq, PartialEq, Clone)]
pub enum Expr {
Constant(Atom),
/// (func-name arg1 arg2)
Application(Box<Expr>, Vec<Expr>),
/// (if predicate do-this)
If(Box<Expr>, Box<Expr>),
/// (if predicate do-this otherwise-do-this)
IfElse(Box<Expr>, Box<Expr>, Box<Expr>),
/// '(3 (if (+ 3 3) 4 5) 7)
Quote(Vec<Expr>),
}
/// We now wrap this type and a few other primitives into our Atom type.
/// Remember from before that Atoms form one half of our language.
#[derive(Debug, Eq, PartialEq, Clone)]
pub enum Atom {
Num(i32),
Keyword(String),
Boolean(bool),
BuiltIn(BuiltIn),
}
/// Now, the most basic type. We define some built-in functions that our lisp has
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum BuiltIn {
Plus,
Minus,
Times,
Divide,
Equal,
Not,
}
/// With types defined, we move onto the top-level expression parser!
fn parse_expr(i: &mut &'_ str) -> PResult<Expr> {
preceded(
multispace0,
alt((parse_constant, parse_application, parse_if, parse_quote)),
)
.parse_next(i)
}
/// We then add the Expr layer on top
fn parse_constant(i: &mut &'_ str) -> PResult<Expr> {
parse_atom.map(Expr::Constant).parse_next(i)
}
/// Now we take all these simple parsers and connect them.
/// We can now parse half of our language!
fn parse_atom(i: &mut &'_ str) -> PResult<Atom> {
alt((
parse_num,
parse_bool,
parse_builtin.map(Atom::BuiltIn),
parse_keyword,
))
.parse_next(i)
}
/// Next up is number parsing. We're keeping it simple here by accepting any number (> 1)
/// of digits but ending the program if it doesn't fit into an i32.
fn parse_num(i: &mut &'_ str) -> PResult<Atom> {
alt((
digit1.try_map(|digit_str: &str| digit_str.parse::<i32>().map(Atom::Num)),
preceded("-", digit1).map(|digit_str: &str| Atom::Num(-digit_str.parse::<i32>().unwrap())),
))
.parse_next(i)
}
/// Our boolean values are also constant, so we can do it the same way
fn parse_bool(i: &mut &'_ str) -> PResult<Atom> {
alt((
"#t".map(|_| Atom::Boolean(true)),
"#f".map(|_| Atom::Boolean(false)),
))
.parse_next(i)
}
fn parse_builtin(i: &mut &'_ str) -> PResult<BuiltIn> {
// alt gives us the result of first parser that succeeds, of the series of
// parsers we give it
alt((
parse_builtin_op,
// map lets us process the parsed output, in this case we know what we parsed,
// so we ignore the input and return the BuiltIn directly
"not".map(|_| BuiltIn::Not),
))
.parse_next(i)
}
/// Continuing the trend of starting from the simplest piece and building up,
/// we start by creating a parser for the built-in operator functions.
fn parse_builtin_op(i: &mut &'_ str) -> PResult<BuiltIn> {
// one_of matches one of the characters we give it
let t = one_of(['+', '-', '*', '/', '=']).parse_next(i)?;
// because we are matching single character tokens, we can do the matching logic
// on the returned value
Ok(match t {
'+' => BuiltIn::Plus,
'-' => BuiltIn::Minus,
'*' => BuiltIn::Times,
'/' => BuiltIn::Divide,
'=' => BuiltIn::Equal,
_ => unreachable!(),
})
}
/// The next easiest thing to parse are keywords.
/// We introduce some error handling combinators: `context` for human readable errors
/// and `cut_err` to prevent back-tracking.
///
/// Put plainly: `preceded(":", cut_err(alpha1))` means that once we see the `:`
/// character, we have to see one or more alphabetic characters or the input is invalid.
fn parse_keyword(i: &mut &'_ str) -> PResult<Atom> {
preceded(":", cut_err(alpha1))
.context(StrContext::Label("keyword"))
.map(|sym_str: &str| Atom::Keyword(sym_str.to_string()))
.parse_next(i)
}
/// We can now use our new combinator to define the rest of the `Expr`s.
///
/// Starting with function application, we can see how the parser mirrors our data
/// definitions: our definition is `Application(Box<Expr>, Vec<Expr>)`, so we know
/// that we need to parse an expression and then parse 0 or more expressions, all
/// wrapped in an S-expression.
///
/// tuples are themselves a parser, used to sequence parsers together, so we can translate this
/// directly and then map over it to transform the output into an `Expr::Application`
fn parse_application(i: &mut &'_ str) -> PResult<Expr> {
let application_inner = (parse_expr, repeat(0.., parse_expr))
.map(|(head, tail)| Expr::Application(Box::new(head), tail));
// finally, we wrap it in an s-expression
s_exp(application_inner).parse_next(i)
}
/// Because `Expr::If` and `Expr::IfElse` are so similar (we easily could have
/// defined `Expr::If` to have an `Option` for the else block), we parse both
/// in a single function.
///
/// In fact, we define our parser as if `Expr::If` was defined with an Option in it,
/// we have the `opt` combinator which fits very nicely here.
fn parse_if(i: &mut &'_ str) -> PResult<Expr> {
let if_inner = preceded(
// here to avoid ambiguity with other names starting with `if`, if we added
// variables to our language, we say that if must be terminated by at least
// one whitespace character
terminated("if", multispace1),
cut_err((parse_expr, parse_expr, opt(parse_expr))),
)
.map(|(pred, true_branch, maybe_false_branch)| {
if let Some(false_branch) = maybe_false_branch {
Expr::IfElse(
Box::new(pred),
Box::new(true_branch),
Box::new(false_branch),
)
} else {
Expr::If(Box::new(pred), Box::new(true_branch))
}
})
.context(StrContext::Label("if expression"));
s_exp(if_inner).parse_next(i)
}
/// A quoted S-expression is list data structure.
///
/// This example doesn't have the symbol atom, but by adding variables and changing
/// the definition of quote to not always be around an S-expression, we'd get them
/// naturally.
fn parse_quote(i: &mut &'_ str) -> PResult<Expr> {
// this should look very straight-forward after all we've done:
// we find the `'` (quote) character, use cut_err to say that we're unambiguously
// looking for an s-expression of 0 or more expressions, and then parse them
preceded("'", cut_err(s_exp(repeat(0.., parse_expr))))
.context(StrContext::Label("quote"))
.map(Expr::Quote)
.parse_next(i)
}
/// Before continuing, we need a helper function to parse lists.
/// A list starts with `(` and ends with a matching `)`.
/// By putting whitespace and newline parsing here, we can avoid having to worry about it
/// in much of the rest of the parser.
//.parse_next/
/// Unlike the previous functions, this function doesn't take or consume input, instead it
/// takes a parsing function and returns a new parsing function.
fn s_exp<'a, O1, F>(inner: F) -> impl Parser<&'a str, O1, ContextError>
where
F: Parser<&'a str, O1, ContextError>,
{
delimited(
'(',
preceded(multispace0, inner),
cut_err(preceded(multispace0, ')')).context(StrContext::Label("closing paren")),
)
}
/// And that's it!
/// We can now parse our entire lisp language.
///
/// But in order to make it a little more interesting, we can hack together
/// a little interpreter to take an Expr, which is really an
/// [Abstract Syntax Tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree) (AST),
/// and give us something back
/// This function tries to reduce the AST.
/// This has to return an Expression rather than an Atom because quoted `s_expressions`
/// can't be reduced
fn eval_expression(e: Expr) -> Option<Expr> {
match e {
// Constants and quoted s-expressions are our base-case
Expr::Constant(_) | Expr::Quote(_) => Some(e),
// we then recursively `eval_expression` in the context of our special forms
// and built-in operators
Expr::If(pred, true_branch) => {
let reduce_pred = eval_expression(*pred)?;
if get_bool_from_expr(reduce_pred)? {
eval_expression(*true_branch)
} else {
None
}
}
Expr::IfElse(pred, true_branch, false_branch) => {
let reduce_pred = eval_expression(*pred)?;
if get_bool_from_expr(reduce_pred)? {
eval_expression(*true_branch)
} else {
eval_expression(*false_branch)
}
}
Expr::Application(head, tail) => {
let reduced_head = eval_expression(*head)?;
let reduced_tail = tail
.into_iter()
.map(eval_expression)
.collect::<Option<Vec<Expr>>>()?;
if let Expr::Constant(Atom::BuiltIn(bi)) = reduced_head {
Some(Expr::Constant(match bi {
BuiltIn::Plus => Atom::Num(
reduced_tail
.into_iter()
.map(get_num_from_expr)
.collect::<Option<Vec<i32>>>()?
.into_iter()
.sum(),
),
BuiltIn::Times => Atom::Num(
reduced_tail
.into_iter()
.map(get_num_from_expr)
.collect::<Option<Vec<i32>>>()?
.into_iter()
.product(),
),
BuiltIn::Equal => Atom::Boolean(
reduced_tail
.iter()
.zip(reduced_tail.iter().skip(1))
.all(|(a, b)| a == b),
),
BuiltIn::Not => {
if reduced_tail.len() != 1 {
return None;
} else {
Atom::Boolean(!get_bool_from_expr(
reduced_tail.first().cloned().unwrap(),
)?)
}
}
BuiltIn::Minus => {
Atom::Num(if let Some(first_elem) = reduced_tail.first().cloned() {
let fe = get_num_from_expr(first_elem)?;
reduced_tail
.into_iter()
.map(get_num_from_expr)
.collect::<Option<Vec<i32>>>()?
.into_iter()
.skip(1)
.fold(fe, |a, b| a - b)
} else {
Default::default()
})
}
BuiltIn::Divide => {
Atom::Num(if let Some(first_elem) = reduced_tail.first().cloned() {
let fe = get_num_from_expr(first_elem)?;
reduced_tail
.into_iter()
.map(get_num_from_expr)
.collect::<Option<Vec<i32>>>()?
.into_iter()
.skip(1)
.fold(fe, |a, b| a / b)
} else {
Default::default()
})
}
}))
} else {
None
}
}
}
}
/// To start we define a couple of helper functions
fn get_num_from_expr(e: Expr) -> Option<i32> {
if let Expr::Constant(Atom::Num(n)) = e {
Some(n)
} else {
None
}
}
fn get_bool_from_expr(e: Expr) -> Option<bool> {
if let Expr::Constant(Atom::Boolean(b)) = e {
Some(b)
} else {
None
}
}

View file

@ -0,0 +1,70 @@
//! This example shows an example of how to parse an escaped string. The
//! rules for the string are similar to JSON and rust. A string is:
//!
//! - Enclosed by double quotes
//! - Can contain any raw unescaped code point besides \ and "
//! - Matches the following escape sequences: \b, \f, \n, \r, \t, \", \\, \/
//! - Matches code points like Rust: \u{XXXX}, where XXXX can be up to 6
//! hex characters
//! - an escape followed by whitespace consumes all whitespace between the
//! escape and the next non-whitespace character
#![cfg(feature = "alloc")]
mod parser;
use winnow::prelude::*;
fn main() -> Result<(), lexopt::Error> {
let args = Args::parse()?;
let data = args.input.as_deref().unwrap_or("\"abc\"");
let result = parser::parse_string::<()>.parse(data);
match result {
Ok(data) => println!("{}", data),
Err(err) => println!("{:?}", err),
}
Ok(())
}
#[derive(Default)]
struct Args {
input: Option<String>,
}
impl Args {
fn parse() -> Result<Self, lexopt::Error> {
use lexopt::prelude::*;
let mut res = Args::default();
let mut args = lexopt::Parser::from_env();
while let Some(arg) = args.next()? {
match arg {
Value(input) => {
res.input = Some(input.string()?);
}
_ => return Err(arg.unexpected()),
}
}
Ok(res)
}
}
#[test]
fn simple() {
let data = "\"abc\"";
let result = parser::parse_string::<()>.parse(data);
assert_eq!(result, Ok(String::from("abc")));
}
#[test]
fn escaped() {
let data = "\"tab:\\tafter tab, newline:\\nnew line, quote: \\\", emoji: \\u{1F602}, newline:\\nescaped whitespace: \\ abc\"";
let result = parser::parse_string::<()>.parse(data);
assert_eq!(
result,
Ok(String::from("tab:\tafter tab, newline:\nnew line, quote: \", emoji: 😂, newline:\nescaped whitespace: abc"))
);
}

View file

@ -0,0 +1,167 @@
//! This example shows an example of how to parse an escaped string. The
//! rules for the string are similar to JSON and rust. A string is:
//!
//! - Enclosed by double quotes
//! - Can contain any raw unescaped code point besides \ and "
//! - Matches the following escape sequences: \b, \f, \n, \r, \t, \", \\, \/
//! - Matches code points like Rust: \u{XXXX}, where XXXX can be up to 6
//! hex characters
//! - an escape followed by whitespace consumes all whitespace between the
//! escape and the next non-whitespace character
use winnow::ascii::multispace1;
use winnow::combinator::alt;
use winnow::combinator::repeat;
use winnow::combinator::{delimited, preceded};
use winnow::error::{FromExternalError, ParserError};
use winnow::prelude::*;
use winnow::token::{take_till, take_while};
/// Parse a string. Use a loop of `parse_fragment` and push all of the fragments
/// into an output string.
pub fn parse_string<'a, E>(input: &mut &'a str) -> PResult<String, E>
where
E: ParserError<&'a str> + FromExternalError<&'a str, std::num::ParseIntError>,
{
// Repeat::fold is the equivalent of iterator::fold. It runs a parser in a loop,
// and for each output value, calls a folding function on each output value.
let build_string = repeat(
0..,
// Our parser function parses a single string fragment
parse_fragment,
)
.fold(
// Our init value, an empty string
String::new,
// Our folding function. For each fragment, append the fragment to the
// string.
|mut string, fragment| {
match fragment {
StringFragment::Literal(s) => string.push_str(s),
StringFragment::EscapedChar(c) => string.push(c),
StringFragment::EscapedWS => {}
}
string
},
);
// Finally, parse the string. Note that, if `build_string` could accept a raw
// " character, the closing delimiter " would never match. When using
// `delimited` with a looping parser (like Repeat::fold), be sure that the
// loop won't accidentally match your closing delimiter!
delimited('"', build_string, '"').parse_next(input)
}
/// A string fragment contains a fragment of a string being parsed: either
/// a non-empty Literal (a series of non-escaped characters), a single
/// parsed escaped character, or a block of escaped whitespace.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum StringFragment<'a> {
Literal(&'a str),
EscapedChar(char),
EscapedWS,
}
/// Combine `parse_literal`, `parse_escaped_whitespace`, and `parse_escaped_char`
/// into a `StringFragment`.
fn parse_fragment<'a, E>(input: &mut &'a str) -> PResult<StringFragment<'a>, E>
where
E: ParserError<&'a str> + FromExternalError<&'a str, std::num::ParseIntError>,
{
alt((
// The `map` combinator runs a parser, then applies a function to the output
// of that parser.
parse_literal.map(StringFragment::Literal),
parse_escaped_char.map(StringFragment::EscapedChar),
parse_escaped_whitespace.value(StringFragment::EscapedWS),
))
.parse_next(input)
}
/// Parse a non-empty block of text that doesn't include \ or "
fn parse_literal<'a, E: ParserError<&'a str>>(input: &mut &'a str) -> PResult<&'a str, E> {
// `take_till` parses a string of 0 or more characters that aren't one of the
// given characters.
let not_quote_slash = take_till(1.., ['"', '\\']);
// `verify` runs a parser, then runs a verification function on the output of
// the parser. The verification function accepts the output only if it
// returns true. In this case, we want to ensure that the output of take_till
// is non-empty.
not_quote_slash
.verify(|s: &str| !s.is_empty())
.parse_next(input)
}
// parser combinators are constructed from the bottom up:
// first we write parsers for the smallest elements (escaped characters),
// then combine them into larger parsers.
/// Parse an escaped character: \n, \t, \r, \u{00AC}, etc.
fn parse_escaped_char<'a, E>(input: &mut &'a str) -> PResult<char, E>
where
E: ParserError<&'a str> + FromExternalError<&'a str, std::num::ParseIntError>,
{
preceded(
'\\',
// `alt` tries each parser in sequence, returning the result of
// the first successful match
alt((
parse_unicode,
// The `value` parser returns a fixed value (the first argument) if its
// parser (the second argument) succeeds. In these cases, it looks for
// the marker characters (n, r, t, etc) and returns the matching
// character (\n, \r, \t, etc).
'n'.value('\n'),
'r'.value('\r'),
't'.value('\t'),
'b'.value('\u{08}'),
'f'.value('\u{0C}'),
'\\'.value('\\'),
'/'.value('/'),
'"'.value('"'),
)),
)
.parse_next(input)
}
/// Parse a unicode sequence, of the form u{XXXX}, where XXXX is 1 to 6
/// hexadecimal numerals. We will combine this later with `parse_escaped_char`
/// to parse sequences like \u{00AC}.
fn parse_unicode<'a, E>(input: &mut &'a str) -> PResult<char, E>
where
E: ParserError<&'a str> + FromExternalError<&'a str, std::num::ParseIntError>,
{
// `take_while` parses between `m` and `n` bytes (inclusive) that match
// a predicate. `parse_hex` here parses between 1 and 6 hexadecimal numerals.
let parse_hex = take_while(1..=6, |c: char| c.is_ascii_hexdigit());
// `preceded` takes a prefix parser, and if it succeeds, returns the result
// of the body parser. In this case, it parses u{XXXX}.
let parse_delimited_hex = preceded(
'u',
// `delimited` is like `preceded`, but it parses both a prefix and a suffix.
// It returns the result of the middle parser. In this case, it parses
// {XXXX}, where XXXX is 1 to 6 hex numerals, and returns XXXX
delimited('{', parse_hex, '}'),
);
// `try_map` takes the result of a parser and applies a function that returns
// a Result. In this case we take the hex bytes from parse_hex and attempt to
// convert them to a u32.
let parse_u32 = parse_delimited_hex.try_map(move |hex| u32::from_str_radix(hex, 16));
// verify_map is like try_map, but it takes an Option instead of a Result. If
// the function returns None, verify_map returns an error. In this case, because
// not all u32 values are valid unicode code points, we have to fallibly
// convert to char with from_u32.
parse_u32.verify_map(std::char::from_u32).parse_next(input)
}
/// Parse a backslash, followed by any amount of whitespace. This is used later
/// to discard any escaped whitespace.
fn parse_escaped_whitespace<'a, E: ParserError<&'a str>>(
input: &mut &'a str,
) -> PResult<&'a str, E> {
preceded('\\', multispace1).parse_next(input)
}

View file

@ -0,0 +1,19 @@
//! # Arithmetic
//!
//! ## Direct evaluation
//!
//! ```rust
#![doc = include_str!("../../examples/arithmetic/parser.rs")]
//! ```
//!
//! ## Parse to AST
//!
//! ```rust
#![doc = include_str!("../../examples/arithmetic/parser_ast.rs")]
//! ```
//!
//! ## Parse to Tokens then AST
//!
//! ```rust
#![doc = include_str!("../../examples/arithmetic/parser_lexer.rs")]
//! ```

View file

@ -0,0 +1,27 @@
//! # Custom Errors
//!
//! Between [`ContextError`], [`Parser::context`], and [`cut_err`],
//! most error needs will likely be met
//! (see [tutorial][chapter_6]).
//! When that isn't the case, you can implement your own error type.
//!
//! The most basic error trait is [`ParserError`].
//!
//! Optional traits include:
//! - [`AddContext`]
//! - [`FromExternalError`]
//!
//! # Example
//!
//!```rust
#![doc = include_str!("../../examples/custom_error.rs")]
//!```
#![allow(unused_imports)]
use crate::combinator::cut_err;
use crate::error::ContextError;
use crate::Parser;
use crate::_tutorial::chapter_6;
use crate::error::AddContext;
use crate::error::FromExternalError;
use crate::error::ParserError;

View file

@ -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")]
//! ```

View file

@ -0,0 +1,5 @@
//! # HTTP
//!
//! ```rust
#![doc = include_str!("../../examples/http/parser.rs")]
//! ```

View file

@ -0,0 +1,5 @@
//! # INI
//!
//! ```rust
#![doc = include_str!("../../examples/ini/parser.rs")]
//! ```

View file

@ -0,0 +1,5 @@
//! # json
//!
//! ```rust,ignore
#![doc = include_str!("../../examples/json/parser.rs")]
//! ```

View file

@ -0,0 +1,330 @@
//! # 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)
//!
//! ## 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_till1,
//! };
//!
//! pub fn peol_comment<'a, E: ParserError<&'a str>>(i: &mut &'a str) -> PResult<(), E>
//! {
//! ('%', take_till1(['\n', '\r']))
//! .void() // Output is thrown away.
//! .parse_next(i)
//! }
//! ```
//!
//! ### `/* C-style comments */`
//!
//! Inline comments surrounded with sentinel tags `(*` and `*)`. This version returns an output of `()`
//! and does not handle nested comments.
//!
//! ```rust
//! use winnow::prelude::*;
//! use winnow::{
//! error::ParserError,
//! token::{tag, take_until},
//! };
//!
//! pub fn pinline_comment<'a, E: ParserError<&'a str>>(i: &mut &'a str) -> PResult<(), 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) -> PResult<&'s str> {
//! (
//! one_of(|c: char| c.is_alpha() || c == '_'),
//! take_while(0.., |c: char| c.is_alphanum() || c == '_')
//! )
//! .recognize()
//! .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 recognize `h`. The tuple ensures that
//! `ello_world123abc` will be piped to the next [`take_while`][crate::token::take_while] parser,
//! which recognizes every remaining character. However, the tuple returns a tuple of the results
//! of its sub-parsers. The [`recognize`][crate::Parser::recognize] 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 [`escaped`] and [`escaped_transform`].
//!
//! ### 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,
//! token::tag,
//! };
//!
//! fn hexadecimal<'s>(input: &mut &'s str) -> PResult<&'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(|()| ()).recognize()
//! ).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,
//! token::tag,
//! };
//!
//! fn hexadecimal_value(input: &mut &str) -> PResult<i64> {
//! preceded(
//! alt(("0x", "0X")),
//! repeat(1..,
//! terminated(one_of(('0'..='9', 'a'..='f', 'A'..='F')), repeat(0.., '_').map(|()| ()))
//! ).map(|()| ()).recognize()
//! ).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,
//! token::tag,
//! };
//!
//! fn octal<'s>(input: &mut &'s str) -> PResult<&'s str> {
//! preceded(
//! alt(("0o", "0O")),
//! repeat(1..,
//! terminated(one_of('0'..='7'), repeat(0.., '_').map(|()| ()))
//! ).map(|()| ()).recognize()
//! ).parse_next(input)
//! }
//! ```
//!
//! #### Binary
//!
//! ```rust
//! use winnow::prelude::*;
//! use winnow::{
//! combinator::alt,
//! combinator::{repeat},
//! combinator::{preceded, terminated},
//! token::one_of,
//! token::tag,
//! };
//!
//! fn binary<'s>(input: &mut &'s str) -> PResult<&'s str> {
//! preceded(
//! alt(("0b", "0B")),
//! repeat(1..,
//! terminated(one_of('0'..='1'), repeat(0.., '_').map(|()| ()))
//! ).map(|()| ()).recognize()
//! ).parse_next(input)
//! }
//! ```
//!
//! #### Decimal
//!
//! ```rust
//! use winnow::prelude::*;
//! use winnow::{
//! combinator::{repeat},
//! combinator::terminated,
//! token::one_of,
//! };
//!
//! fn decimal<'s>(input: &mut &'s str) -> PResult<&'s str> {
//! repeat(1..,
//! terminated(one_of('0'..='9'), repeat(0.., '_').map(|()| ()))
//! ).map(|()| ())
//! .recognize()
//! .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) -> PResult<&'s str> {
//! alt((
//! // Case one: .42
//! (
//! '.',
//! decimal,
//! opt((
//! one_of(['e', 'E']),
//! opt(one_of(['+', '-'])),
//! decimal
//! ))
//! ).recognize()
//! , // Case two: 42e42 and 42.42e42
//! (
//! decimal,
//! opt(preceded(
//! '.',
//! decimal,
//! )),
//! one_of(['e', 'E']),
//! opt(one_of(['+', '-'])),
//! decimal
//! ).recognize()
//! , // Case three: 42. and 42.42
//! (
//! decimal,
//! '.',
//! opt(decimal)
//! ).recognize()
//! )).parse_next(input)
//! }
//!
//! fn decimal<'s>(input: &mut &'s str) -> PResult<&'s str> {
//! repeat(1..,
//! terminated(one_of('0'..='9'), repeat(0.., '_').map(|()| ()))
//! ).
//! map(|()| ())
//! .recognize()
//! .parse_next(input)
//! }
//! ```
//!
//! See also [`float`]
#![allow(unused_imports)]
use crate::ascii::dec_int;
use crate::ascii::dec_uint;
use crate::ascii::escaped;
use crate::ascii::escaped_transform;
use crate::ascii::float;
use crate::ascii::hex_uint;

View file

@ -0,0 +1,40 @@
//! # Special Topics
//!
//! These are short recipes for accomplishing common tasks.
//!
//! - [Why `winnow`?][why]
//! - [Migrating from `nom`][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]
//! - [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)]
pub mod arithmetic;
pub mod error;
pub mod fromstr;
pub mod http;
pub mod ini;
pub mod json;
pub mod language;
pub mod nom;
pub mod partial;
pub mod performance;
pub mod s_expression;
pub mod stream;
pub mod why;

View file

@ -0,0 +1,49 @@
//! # 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].
//!
//! 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/v0.3-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/v0.4-main/CHANGELOG.md#compatibility-2) 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/v0.5.0/CHANGELOG.md))
//! 1. Commit
//! 1. Resolve deprecation messagess
//! 1. Commit
//!
//! 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)

View file

@ -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](http://ndjson.org/)
//! - 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;

View file

@ -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;

View file

@ -0,0 +1,5 @@
//! # s-expression
//!
//! ```rust
#![doc = include_str!("../../examples/s_expression/parser.rs")]
//! ```

View file

@ -0,0 +1,66 @@
//! # 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
//! - [`Located`] for looking up the absolute position of a token
//!
//! But that won't always cut it for your parser. For example, you might lex `&str` into
//! a series of tokens and then want to parse a `TokenStream`.
//!
//! ## Implementing a custom stream
//!
//! Let's assume we have an input type we'll call `MyStream`.
//! `MyStream` is a sequence of `MyItem` type.
//!
//! The goal is to define parsers with this signature: `&mut MyStream -> PResult<Output>`.
//! ```rust
//! # use winnow::prelude::*;
//! # use winnow::token::tag;
//! # type MyStream<'i> = &'i str;
//! # type Output<'i> = &'i str;
//! fn parser<'s>(i: &mut MyStream<'s>) -> PResult<Output<'s>> {
//! "test".parse_next(i)
//! }
//! ```
//!
//! Here are the traits you may have to implement for `MyStream`:
//!
//! | 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`:
//!
//! | trait | usage |
//! |---|---|
//! | [`AsChar`] |Transforms common types to a char for basic token parsing|
//! | [`ContainsToken`] |Look for the token in the given set|
//!
//! And traits for `&[MyItem]`:
//!
//! | trait | usage |
//! |---|---|
//! | [`SliceLen`] |Calculate the input length|
//! | [`ParseSlice`] |Used to integrate `&str`'s `parse()` method|
//!
//! ## Implementing a custom token
//!
//! If you are parsing `&[Myitem]`, leaving just the `MyItem` traits.
//!
//! For example:
//! ```rust
#![doc = include_str!("../../examples/arithmetic/parser_lexer.rs")]
//! ```
#[allow(unused_imports)] // Here for intra-dock links
use crate::stream::*;

View file

@ -0,0 +1,101 @@
//! # Why `winnow`?
//!
//! To answer this question, it will be useful to contrast this with other approaches to parsing.
//!
//! **Note:** This will focus on principles and priorities. For a deeper and wider wider
//! comparison with other Rust parser libraries, see
//! [parse-rosetta-rs](https://github.com/rosetta-rs/parse-rosetta-rs).
//!
//! ## 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
//! ["Ive 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][crate::trace] for easier debugging
//! - [Streaming parsing][crate::Partial] for network communication or large file
//! - [Stateful][crate::Stateful] parsers
//!
//! For binary formats, `winnow` includes:
//! - [A hexadecimal view][crate::Bytes] in [traces][crate::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::Located]
//! - [A textual view when parsing as bytes][crate::BStr] in [traces][crate::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.
//!
//! See also our [nom migration guide][super::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::stream::Accumulate;

View file

@ -0,0 +1,39 @@
//! # 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:
//!
//! - The parsers are small and easy to write
//! - The parsers components are easy to reuse (if they're general enough, please add them to winnow!)
//! - The parsers components are easy to test separately (unit tests and property-based tests)
//! - The parser combination code looks close to the grammar you would have written
//! - You can build partial parsers, specific to the data you need at the moment, and ignore the rest
#![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;

View file

@ -0,0 +1,86 @@
//! # 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 build simple parsers, and
//! then combine them (using "combinators").
//!
//! Let's discuss what a "parser" actually does. A parser takes an input and 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 left pointing to
//! data that still needs processing
//!
//! If the parser failed, then there are multiple errors that could be returned.
//! For simplicity, however, in the next chapters we will leave these unexplored.
//!
//! ```text
//! ┌─► Ok(what matched the parser)
//! ┌─────────┐ │
//! my input───►│my parser├──►either──┤
//! └─────────┘ └─► Err(...)
//! ```
//!
//!
//! To represent this model of the world, winnow uses the [`PResult<O>`] type.
//! The `Ok` variant has `output: O`;
//! whereas the `Err` variant stores an error.
//!
//! You can import that from:
//!
//! ```rust
//! use winnow::PResult;
//! ```
//!
//! 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.
//!
//! 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; 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) -> PResult<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 remaining input is the same as the input.
//! - Since it doesn't parse anything, it also should just return an empty string.
//!
//! ```rust
//! use winnow::PResult;
//! use winnow::Parser;
//!
//! pub fn do_nothing_parser<'s>(input: &mut &'s str) -> PResult<&'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 crate::PResult;
use crate::Parser;
pub use super::chapter_0 as previous;
pub use super::chapter_2 as next;
pub use crate::_tutorial as table_of_contents;

View file

@ -0,0 +1,248 @@
//! # Chapter 2: Tokens and Tags
//!
//! The simplest *useful* parser you can write is one which matches tokens.
//!
//! ## 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::PResult;
//! use winnow::stream::Stream;
//! use winnow::error::ParserError;
//! use winnow::error::ErrorKind;
//! use winnow::error::ErrMode;
//!
//! fn parse_prefix(input: &mut &str) -> PResult<char> {
//! let c = input.next_token().ok_or_else(|| {
//! ErrMode::from_error_kind(input, ErrorKind::Token)
//! })?;
//! if c != '0' {
//! return Err(ErrMode::from_error_kind(input, ErrorKind::Verify));
//! }
//! 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());
//! }
//! ```
//!
//! [`any`] and [`Parser::verify`] are [`Parser`] building blocks on top of [`Stream`]:
//! ```rust
//! # use winnow::PResult;
//! use winnow::Parser;
//! use winnow::token::any;
//!
//! fn parse_prefix(input: &mut &str) -> PResult<char> {
//! any.verify(|c| *c == '0').parse_next(input)
//! }
//! #
//! # 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
//! `char`.
//!
//! ```rust
//! # use winnow::PResult;
//! use winnow::Parser;
//!
//! fn parse_prefix(input: &mut &str) -> PResult<char> {
//! '0'.parse_next(input)
//! }
//! #
//! # 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::PResult;
//! use winnow::stream::Stream;
//! use winnow::error::ParserError;
//! use winnow::error::ErrorKind;
//! use winnow::error::ErrMode;
//!
//! fn parse_prefix<'s>(input: &mut &'s str) -> PResult<&'s str> {
//! let expected = "0x";
//! if input.len() < expected.len() {
//! return Err(ErrMode::from_error_kind(input, ErrorKind::Slice));
//! }
//! let actual = input.next_slice(expected.len());
//! if actual != expected {
//! return Err(ErrMode::from_error_kind(input, ErrorKind::Verify));
//! }
//! 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());
//! }
//! ```
//!
//! Again, matching a literal is common enough that [`Parser`] is implemented for `&str`:
//! ```rust
//! # use winnow::PResult;
//! use winnow::Parser;
//!
//! fn parse_prefix<'s>(input: &mut &'s str) -> PResult<&'s str> {
//! "0x".parse_next(input)
//! }
//! #
//! # 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());
//! # }
//! ```
//!
//! In `winnow`, we call this type of parser a [`tag`]. See [`token`] for additional individual
//! and token-slice parsers.
//!
//! ## Character Classes
//!
//! Selecting a single `char` or a [`tag`] 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::PResult;
//! use winnow::token::one_of;
//!
//! fn parse_digits(input: &mut &str) -> PResult<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::InputError;
//! > pub fn one_of<'i>(
//! > list: &'static [char]
//! > ) -> impl Parser<&'i str, char, InputError<&'i str>> {
//! > // ...
//! > # 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 `PResult`. 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::PResult;
//! use winnow::token::take_while;
//!
//! fn parse_digits<'s>(input: &mut &'s str) -> PResult<&'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::PResult;
//! use winnow::ascii::hex_digit1;
//!
//! fn parse_digits<'s>(input: &mut &'s str) -> PResult<&'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::one_of;
use crate::token::tag;
use crate::token::take_while;
use crate::Parser;
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;

View file

@ -0,0 +1,376 @@
//! # Chapter 3: Sequencing and Alternatives
//!
//! In the last chapter, we saw how to create simple parsers using prebuilt parsers.
//!
//! In this chapter, we explore two other widely used features:
//! alternatives and composition.
//!
//! ## Sequencing
//!
//! Now that we can create more interesting parsers, we can sequence them together, like:
//!
//! ```rust
//! # use winnow::prelude::*;
//! # use winnow::token::take_while;
//! #
//! fn parse_prefix<'s>(input: &mut &'s str) -> PResult<&'s str> {
//! "0x".parse_next(input)
//! }
//!
//! fn parse_digits<'s>(input: &mut &'s str) -> PResult<&'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::token::take_while;
//! #
//! # fn parse_prefix<'s>(input: &mut &'s str) -> PResult<&'s str> {
//! # "0x".parse_next(input)
//! # }
//! #
//! # fn parse_digits<'s>(input: &mut &'s str) -> PResult<&'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 tag and you can instead use one of the provided combinators,
//! like [`preceded`]:
//! ```rust
//! # use winnow::prelude::*;
//! # use winnow::token::take_while;
//! use winnow::combinator::preceded;
//!
//! # fn parse_prefix<'s>(input: &mut &'s str) -> PResult<&'s str> {
//! # "0x".parse_next(input)
//! # }
//! #
//! # fn parse_digits<'s>(input: &mut &'s str) -> PResult<&'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.
//!
//! [`Stream::checkpoint`] helps us to retry parsing:
//! ```rust
//! # use winnow::prelude::*;
//! # use winnow::token::take_while;
//! use winnow::stream::Stream;
//!
//! fn parse_digits<'s>(input: &mut &'s str) -> PResult<(&'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) -> PResult<&'s str> {
//! # take_while(1.., (
//! # ('0'..='7'),
//! # )).parse_next(input)
//! # }
//! #
//! # fn parse_oct_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
//! # take_while(1.., (
//! # ('0'..='7'),
//! # )).parse_next(input)
//! # }
//! #
//! # fn parse_dec_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
//! # take_while(1.., (
//! # ('0'..='9'),
//! # )).parse_next(input)
//! # }
//! #
//! # fn parse_hex_digits<'s>(input: &mut &'s str) -> PResult<&'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());
//! }
//! ```
//!
//! > **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_6`#errmode]
//!
//! [`opt`] is a basic building block for correctly handling retrying parsing:
//! ```rust
//! # use winnow::prelude::*;
//! # use winnow::token::take_while;
//! use winnow::combinator::opt;
//!
//! fn parse_digits<'s>(input: &mut &'s str) -> PResult<(&'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) -> PResult<&'s str> {
//! # take_while(1.., (
//! # ('0'..='7'),
//! # )).parse_next(input)
//! # }
//! #
//! # fn parse_oct_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
//! # take_while(1.., (
//! # ('0'..='7'),
//! # )).parse_next(input)
//! # }
//! #
//! # fn parse_dec_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
//! # take_while(1.., (
//! # ('0'..='9'),
//! # )).parse_next(input)
//! # }
//! #
//! # fn parse_hex_digits<'s>(input: &mut &'s str) -> PResult<&'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::token::take_while;
//! use winnow::combinator::alt;
//!
//! fn parse_digits<'s>(input: &mut &'s str) -> PResult<(&'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) -> PResult<&'s str> {
//! # take_while(1.., (
//! # ('0'..='7'),
//! # )).parse_next(input)
//! # }
//! #
//! # fn parse_oct_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
//! # take_while(1.., (
//! # ('0'..='7'),
//! # )).parse_next(input)
//! # }
//! #
//! # fn parse_dec_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
//! # take_while(1.., (
//! # ('0'..='9'),
//! # )).parse_next(input)
//! # }
//! #
//! # fn parse_hex_digits<'s>(input: &mut &'s str) -> PResult<&'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());
//! # }
//! ```
//!
//! > **Note:** [`empty`] and [`fail`] are parsers that might be useful in the `else` case.
//!
//! 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::token::take_while;
//! use winnow::combinator::dispatch;
//! use winnow::token::take;
//! use winnow::combinator::fail;
//!
//! fn parse_digits<'s>(input: &mut &'s str) -> PResult<&'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) -> PResult<&'s str> {
//! # take_while(1.., (
//! # ('0'..='7'),
//! # )).parse_next(input)
//! # }
//! #
//! # fn parse_oct_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
//! # take_while(1.., (
//! # ('0'..='7'),
//! # )).parse_next(input)
//! # }
//! #
//! # fn parse_dec_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
//! # take_while(1.., (
//! # ('0'..='9'),
//! # )).parse_next(input)
//! # }
//! #
//! # fn parse_hex_digits<'s>(input: &mut &'s str) -> PResult<&'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());
//! }
//! ```
//!
//! > **Note:** [`peek`] may be useful when [`dispatch`]ing from hints from each case's parser.
//!
//! See [`combinator`] for more alternative parsers.
#![allow(unused_imports)]
use super::chapter_6;
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;

View file

@ -0,0 +1,107 @@
//! # Chapter 4: Parsers With Custom Return Types
//!
//! So far, we have seen mostly functions that take an `&str`, and return a
//! `PResult<&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 [`PResult`] to the desired return type.
//! For example, to return a `usize`, return a `PResult<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::ascii::digit1;
//! #
//! fn parse_digits(input: &mut &str) -> PResult<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::token::take_while;
//! use winnow::combinator::dispatch;
//! use winnow::token::take;
//! use winnow::combinator::fail;
//!
//! fn parse_digits(input: &mut &str) -> PResult<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) -> PResult<&'s str> {
//! # take_while(1.., (
//! # ('0'..='7'),
//! # )).parse_next(input)
//! # }
//! #
//! # fn parse_oct_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
//! # take_while(1.., (
//! # ('0'..='7'),
//! # )).parse_next(input)
//! # }
//! #
//! # fn parse_dec_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
//! # take_while(1.., (
//! # ('0'..='9'),
//! # )).parse_next(input)
//! # }
//! #
//! # fn parse_hex_digits<'s>(input: &mut &'s str) -> PResult<&'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::PResult;
use crate::Parser;
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;

View file

@ -0,0 +1,282 @@
//! # 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::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) -> PResult<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) -> PResult<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) -> PResult<&'s str> {
//! # take_while(1.., (
//! # ('0'..='7'),
//! # )).parse_next(input)
//! # }
//! #
//! # fn parse_oct_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
//! # take_while(1.., (
//! # ('0'..='7'),
//! # )).parse_next(input)
//! # }
//! #
//! # fn parse_dec_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
//! # take_while(1.., (
//! # ('0'..='9'),
//! # )).parse_next(input)
//! # }
//! #
//! # fn parse_hex_digits<'s>(input: &mut &'s str) -> PResult<&'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::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) -> PResult<Vec<usize>> {
//! repeat(0..,
//! terminated(parse_digits, opt(','))
//! ).parse_next(input)
//! }
//! #
//! # fn parse_digits(input: &mut &str) -> PResult<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) -> PResult<&'s str> {
//! # take_while(1.., (
//! # ('0'..='7'),
//! # )).parse_next(input)
//! # }
//! #
//! # fn parse_oct_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
//! # take_while(1.., (
//! # ('0'..='7'),
//! # )).parse_next(input)
//! # }
//! #
//! # fn parse_dec_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
//! # take_while(1.., (
//! # ('0'..='9'),
//! # )).parse_next(input)
//! # }
//! #
//! # fn parse_hex_digits<'s>(input: &mut &'s str) -> PResult<&'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 `,` when we intended to not support that. We can
//! easily fix this by using [`separated`]:
//! ```rust
//! # use winnow::prelude::*;
//! # 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) -> PResult<Vec<usize>> {
//! separated(0.., parse_digits, ",").parse_next(input)
//! }
//!
//! // ...
//! # fn parse_digits(input: &mut &str) -> PResult<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) -> PResult<&'s str> {
//! # take_while(1.., (
//! # ('0'..='7'),
//! # )).parse_next(input)
//! # }
//! #
//! # fn parse_oct_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
//! # take_while(1.., (
//! # ('0'..='7'),
//! # )).parse_next(input)
//! # }
//! #
//! # fn parse_dec_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
//! # take_while(1.., (
//! # ('0'..='9'),
//! # )).parse_next(input)
//! # }
//! #
//! # fn parse_hex_digits<'s>(input: &mut &'s str) -> PResult<&'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 [`repeat`], it isn't collecting directly into a [`Vec`] but
//! [`Accumulate`] to gather the results. This lets us make more complex parsers than we did in
//! [`chapter_2`] by accumulating the results into a `()` and [`recognize`][Parser::recognize]-ing the captured input:
//! ```rust
//! # use winnow::prelude::*;
//! # use winnow::token::take_while;
//! # use winnow::combinator::dispatch;
//! # use winnow::token::take;
//! # use winnow::combinator::fail;
//! # use winnow::combinator::separated;
//! #
//! fn recognize_list<'s>(input: &mut &'s str) -> PResult<&'s str> {
//! parse_list.recognize().parse_next(input)
//! }
//!
//! fn parse_list(input: &mut &str) -> PResult<()> {
//! separated(0.., parse_digits, ",").parse_next(input)
//! }
//!
//! # fn parse_digits(input: &mut &str) -> PResult<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) -> PResult<&'s str> {
//! # take_while(1.., (
//! # ('0'..='7'),
//! # )).parse_next(input)
//! # }
//! #
//! # fn parse_oct_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
//! # take_while(1.., (
//! # ('0'..='7'),
//! # )).parse_next(input)
//! # }
//! #
//! # fn parse_dec_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
//! # take_while(1.., (
//! # ('0'..='9'),
//! # )).parse_next(input)
//! # }
//! #
//! # fn parse_hex_digits<'s>(input: &mut &'s str) -> PResult<&'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 = recognize_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::Parser;
use std::vec::Vec;
pub use super::chapter_4 as previous;
pub use super::chapter_6 as next;
pub use crate::_tutorial as table_of_contents;

View file

@ -0,0 +1,156 @@
//! # Chapter 6: Error Reporting
//!
//! ## `Error`
//!
//! Back in [`chapter_1`], we glossed over the `Err` side of [`PResult`]. `PResult<O>` is
//! actually short for `PResult<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 improve the error quality.
//!
//! ```rust
//! # use winnow::prelude::*;
//! # use winnow::token::take_while;
//! # use winnow::combinator::alt;
//! use winnow::error::StrContext;
//!
//! fn parse_digits<'s>(input: &mut &'s str) -> PResult<(&'s str, &'s str)> {
//! alt((
//! ("0b", parse_bin_digits).context(StrContext::Label("binary")),
//! ("0o", parse_oct_digits).context(StrContext::Label("octal")),
//! ("0d", parse_dec_digits).context(StrContext::Label("decimal")),
//! ("0x", parse_hex_digits).context(StrContext::Label("hexadecimal")),
//! )).parse_next(input)
//! }
//!
//! // ...
//! # fn parse_bin_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
//! # take_while(1.., (
//! # ('0'..='7'),
//! # )).parse_next(input)
//! # }
//! #
//! # fn parse_oct_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
//! # take_while(1.., (
//! # ('0'..='7'),
//! # )).parse_next(input)
//! # }
//! #
//! # fn parse_dec_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
//! # take_while(1.., (
//! # ('0'..='9'),
//! # )).parse_next(input)
//! # }
//! #
//! # fn parse_hex_digits<'s>(input: &mut &'s str) -> PResult<&'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");
//! }
//! ```
//!
//! At first glance, this looks correct but what `context` will be reported when parsing `"0b5"`?
//! If you remember back to [`chapter_3`], [`alt`] will only report the last error by default which
//! means when parsing `"0b5"`, the `context` will be `"hexadecimal"`.
//!
//! ## `ErrMode`
//!
//! Let's break down `PResult<O, E>` one step further:
//! ```rust
//! # use winnow::error::ErrorKind;
//! # use winnow::error::ErrMode;
//! pub type PResult<O, E = ErrorKind> = Result<O, ErrMode<E>>;
//! ```
//! [`PResult`] is just a fancy wrapper around `Result` that wraps our error in an [`ErrMode`]
//! type.
//!
//! [`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.
//!
//! So we can get the correct `context` by modifying the above example with [`cut_err`]:
//! ```rust
//! # use winnow::prelude::*;
//! # use winnow::token::take_while;
//! # use winnow::combinator::alt;
//! # use winnow::error::StrContext;
//! use winnow::combinator::cut_err;
//!
//! fn parse_digits<'s>(input: &mut &'s str) -> PResult<(&'s str, &'s str)> {
//! alt((
//! ("0b", cut_err(parse_bin_digits)).context(StrContext::Label("binary")),
//! ("0o", cut_err(parse_oct_digits)).context(StrContext::Label("octal")),
//! ("0d", cut_err(parse_dec_digits)).context(StrContext::Label("decimal")),
//! ("0x", cut_err(parse_hex_digits)).context(StrContext::Label("hexadecimal")),
//! )).parse_next(input)
//! }
//!
//! // ...
//! # fn parse_bin_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
//! # take_while(1.., (
//! # ('0'..='7'),
//! # )).parse_next(input)
//! # }
//! #
//! # fn parse_oct_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
//! # take_while(1.., (
//! # ('0'..='7'),
//! # )).parse_next(input)
//! # }
//! #
//! # fn parse_dec_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
//! # take_while(1.., (
//! # ('0'..='9'),
//! # )).parse_next(input)
//! # }
//! #
//! # fn parse_hex_digits<'s>(input: &mut &'s str) -> PResult<&'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");
//! }
//! ```
//! Now, when parsing `"0b5"`, the `context` will be `"binary"`.
#![allow(unused_imports)]
use super::chapter_1;
use super::chapter_3;
use crate::combinator::alt;
use crate::combinator::cut_err;
use crate::error::ContextError;
use crate::error::ErrMode;
use crate::error::ErrMode::*;
use crate::error::ErrorKind;
use crate::PResult;
use crate::Parser;
use crate::_topic;
pub use super::chapter_5 as previous;
pub use super::chapter_7 as next;
pub use crate::_tutorial as table_of_contents;

View file

@ -0,0 +1,119 @@
//! # Chapter 7: 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::error::ErrMode;
//! # use winnow::Parser;
//! #
//! pub fn parser<'s>(input: &mut &'s str) -> PResult<&'s str> {
//! // ...
//! # Ok("")
//! }
//!
//! type PResult<O> = Result<
//! O,
//! ErrMode<ContextError>
//! >;
//! ```
//! 1. We have to decide what to do about the "remainder" of the `input`.
//! 2. The [`ErrMode<ContextError>`] is not 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`]
//! - Removes the [`ErrMode`] wrapper
//! - Wraps the error in [`ParseError`]
//! - Provides access to the original [`input`][ParseError::input] with the
//! [`offset`][ParseError::offset] of where it failed
//! - Provides a default renderer (via [`std::fmt::Display`])
//! ```rust
//! # use winnow::prelude::*;
//! # 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 = String;
//!
//! fn from_str(input: &str) -> Result<Self, Self::Err> {
//! parse_digits
//! .map(Hex)
//! .parse(input)
//! .map_err(|e| e.to_string())
//! }
//! }
//!
//! // ...
//! # fn parse_digits<'s>(input: &mut &'s str) -> PResult<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) -> PResult<&'s str> {
//! # take_while(1.., (
//! # ('0'..='7'),
//! # )).parse_next(input)
//! # }
//! #
//! # fn parse_oct_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
//! # take_while(1.., (
//! # ('0'..='7'),
//! # )).parse_next(input)
//! # }
//! #
//! # fn parse_dec_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
//! # take_while(1.., (
//! # ('0'..='9'),
//! # )).parse_next(input)
//! # }
//! #
//! # fn parse_hex_digits<'s>(input: &mut &'s str) -> PResult<&'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 crate::combinator::eof;
use crate::error::ErrMode;
use crate::error::InputError;
use crate::error::ParseError;
use crate::PResult;
use crate::Parser;
pub use super::chapter_6 as previous;
pub use super::chapter_8 as next;
pub use crate::_tutorial as table_of_contents;

View file

@ -0,0 +1,34 @@
//! # Chapter 8: Debugging
//!
//! When things inevitably go wrong, you can introspect the parsing state by running your test case
//! with `--features debug`:
//! ![Trace output from string example](https://raw.githubusercontent.com/winnow-rs/winnow/main/assets/trace.svg "Example output")
//!
//! 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::PResult;
//! # use winnow::Parser;
//! use winnow::combinator::trace;
//!
//! pub fn do_nothing_parser<'s>(input: &mut &'s str) -> PResult<&'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::_tutorial as table_of_contents;

View file

@ -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;

1727
third-party/vendor/winnow/src/ascii/mod.rs vendored Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,377 @@
//! Bit level parsers
//!
#[cfg(test)]
mod tests;
use crate::combinator::trace;
use crate::error::{ErrMode, ErrorConvert, ErrorKind, Needed, ParserError};
use crate::lib::std::ops::{AddAssign, Div, Shl, Shr};
use crate::stream::{AsBytes, Stream, StreamIsPartial, ToUsize};
use crate::{unpeek, IResult, PResult, Parser};
/// 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
/// ```
/// use winnow::prelude::*;
/// use winnow::Bytes;
/// use winnow::binary::bits::{bits, take};
/// use winnow::error::InputError;
///
/// type Stream<'i> = &'i Bytes;
///
/// fn stream(b: &[u8]) -> Stream<'_> {
/// Bytes::new(b)
/// }
///
/// fn parse(input: Stream<'_>) -> IResult<Stream<'_>, (u8, u8)> {
/// bits::<_, _, InputError<(_, usize)>, _, _>((take(4usize), take(8usize))).parse_peek(input)
/// }
///
/// let input = stream(&[0x12, 0x34, 0xff, 0xff]);
///
/// let output = parse(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<I, O, E1, E2, P>(mut parser: P) -> impl Parser<I, O, E2>
where
E1: ParserError<(I, usize)> + ErrorConvert<E2>,
E2: ParserError<I>,
I: Stream + Clone,
P: Parser<(I, usize), O, E1>,
{
trace(
"bits",
unpeek(move |input: I| {
match parser.parse_peek((input, 0)) {
Ok(((rest, offset), result)) => {
// 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 (input, _) = rest.peek_slice(remaining_bytes_index);
Ok((input, result))
}
Err(ErrMode::Incomplete(n)) => {
Err(ErrMode::Incomplete(n.map(|u| u.get() / BYTE + 1)))
}
Err(e) => Err(e.convert()),
}
}),
)
}
/// Convert a [`bits`] stream back into a byte stream
///
/// **Warning:** A partial byte remaining in the input will be ignored and the given parser will
/// start parsing at the next full byte.
///
/// ```
/// use winnow::prelude::*;
/// use winnow::Bytes;
/// use winnow::binary::bits::{bits, bytes, take};
/// use winnow::combinator::rest;
/// use winnow::error::InputError;
///
/// type Stream<'i> = &'i Bytes;
///
/// fn stream(b: &[u8]) -> Stream<'_> {
/// Bytes::new(b)
/// }
///
/// fn parse(input: Stream<'_>) -> IResult<Stream<'_>, (u8, u8, &[u8])> {
/// bits::<_, _, InputError<(_, usize)>, _, _>((
/// take(4usize),
/// take(8usize),
/// bytes::<_, _, InputError<_>, _, _>(rest)
/// )).parse_peek(input)
/// }
///
/// let input = stream(&[0x12, 0x34, 0xff, 0xff]);
///
/// assert_eq!(parse(input), Ok(( stream(&[]), (0x01, 0x23, &[0xff, 0xff][..]) )));
/// ```
pub fn bytes<I, O, E1, E2, P>(mut parser: P) -> impl Parser<(I, usize), O, E2>
where
E1: ParserError<I> + ErrorConvert<E2>,
E2: ParserError<(I, usize)>,
I: Stream<Token = u8> + Clone,
P: Parser<I, O, E1>,
{
trace(
"bytes",
unpeek(move |(input, offset): (I, usize)| {
let (inner, _) = if offset % BYTE != 0 {
input.peek_slice(1 + offset / BYTE)
} else {
input.peek_slice(offset / BYTE)
};
let i = (input, offset);
match parser.parse_peek(inner) {
Ok((rest, res)) => Ok(((rest, 0), res)),
Err(ErrMode::Incomplete(Needed::Unknown)) => {
Err(ErrMode::Incomplete(Needed::Unknown))
}
Err(ErrMode::Incomplete(Needed::Size(sz))) => {
Err(match sz.get().checked_mul(BYTE) {
Some(v) => ErrMode::Incomplete(Needed::new(v)),
None => ErrMode::Cut(E2::assert(
&i,
"overflow in turning needed bytes into needed bits",
)),
})
}
Err(e) => Err(e.convert()),
}
}),
)
}
/// Parse taking `count` bits
///
/// # Example
/// ```rust
/// # use winnow::prelude::*;
/// # use winnow::Bytes;
/// # use winnow::error::{InputError, ErrorKind};
/// use winnow::binary::bits::take;
///
/// type Stream<'i> = &'i Bytes;
///
/// fn stream(b: &[u8]) -> Stream<'_> {
/// Bytes::new(b)
/// }
///
/// fn parser(input: (Stream<'_>, usize), count: usize)-> IResult<(Stream<'_>, usize), u8> {
/// take(count).parse_peek(input)
/// }
///
/// // Consumes 0 bits, returns 0
/// assert_eq!(parser((stream(&[0b00010010]), 0), 0), Ok(((stream(&[0b00010010]), 0), 0)));
///
/// // Consumes 4 bits, returns their values and increase offset to 4
/// assert_eq!(parser((stream(&[0b00010010]), 0), 4), Ok(((stream(&[0b00010010]), 4), 0b00000001)));
///
/// // Consumes 4 bits, offset is 4, returns their values and increase offset to 0 of next byte
/// assert_eq!(parser((stream(&[0b00010010]), 4), 4), Ok(((stream(&[]), 0), 0b00000010)));
///
/// // Tries to consume 12 bits but only 8 are available
/// assert_eq!(parser((stream(&[0b00010010]), 0), 12), Err(winnow::error::ErrMode::Backtrack(InputError::new((stream(&[0b00010010]), 0), ErrorKind::Eof))));
/// ```
#[inline(always)]
pub fn take<I, O, C, E: ParserError<(I, usize)>>(count: C) -> impl Parser<(I, usize), O, E>
where
I: Stream<Token = u8> + AsBytes + StreamIsPartial + Clone,
C: ToUsize,
O: From<u8> + AddAssign + Shl<usize, Output = O> + Shr<usize, Output = O>,
{
let count = count.to_usize();
trace(
"take",
unpeek(move |input: (I, usize)| {
if <I as StreamIsPartial>::is_partial_supported() {
take_::<_, _, _, true>(input, count)
} else {
take_::<_, _, _, false>(input, count)
}
}),
)
}
fn take_<I, O, E: ParserError<(I, usize)>, const PARTIAL: bool>(
(input, bit_offset): (I, usize),
count: usize,
) -> IResult<(I, usize), O, E>
where
I: StreamIsPartial,
I: Stream<Token = u8> + AsBytes + Clone,
O: From<u8> + AddAssign + Shl<usize, Output = O> + Shr<usize, Output = O>,
{
if count == 0 {
Ok(((input, bit_offset), 0u8.into()))
} else {
if input.eof_offset() * BYTE < count + bit_offset {
if PARTIAL && input.is_partial() {
Err(ErrMode::Incomplete(Needed::new(count)))
} else {
Err(ErrMode::from_error_kind(
&(input, bit_offset),
ErrorKind::Eof,
))
}
} 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.as_bytes().iter().copied().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, _) = input.peek_slice(cnt);
Ok(((input, end_offset), acc))
}
}
}
/// Parse taking `count` bits and comparing them to `pattern`
///
/// # Example
///
/// ```rust
/// # use winnow::prelude::*;
/// # use winnow::Bytes;
/// # use winnow::error::{InputError, ErrorKind};
/// use winnow::binary::bits::tag;
///
/// 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.
/// fn parser(pattern: u8, count: u8, input: (Stream<'_>, usize)) -> IResult<(Stream<'_>, usize), u8> {
/// tag(pattern, count).parse_peek(input)
/// }
///
/// // The lowest 4 bits of 0b00001111 match the lowest 4 bits of 0b11111111.
/// assert_eq!(
/// parser(0b0000_1111, 4, (stream(&[0b1111_1111]), 0)),
/// Ok(((stream(&[0b1111_1111]), 4), 0b0000_1111))
/// );
///
/// // The lowest bit of 0b00001111 matches the lowest bit of 0b11111111 (both are 1).
/// assert_eq!(
/// parser(0b00000001, 1, (stream(&[0b11111111]), 0)),
/// Ok(((stream(&[0b11111111]), 1), 0b00000001))
/// );
///
/// // The lowest 2 bits of 0b11111111 and 0b00000001 are different.
/// assert_eq!(
/// parser(0b000000_01, 2, (stream(&[0b111111_11]), 0)),
/// Err(winnow::error::ErrMode::Backtrack(InputError::new(
/// (stream(&[0b11111111]), 0),
/// ErrorKind::Tag
/// )))
/// );
///
/// // The lowest 8 bits of 0b11111111 and 0b11111110 are different.
/// assert_eq!(
/// parser(0b11111110, 8, (stream(&[0b11111111]), 0)),
/// Err(winnow::error::ErrMode::Backtrack(InputError::new(
/// (stream(&[0b11111111]), 0),
/// ErrorKind::Tag
/// )))
/// );
/// ```
#[inline(always)]
#[doc(alias = "literal")]
#[doc(alias = "just")]
#[doc(alias = "tag")]
pub fn pattern<I, O, C, E: ParserError<(I, usize)>>(
pattern: O,
count: C,
) -> impl Parser<(I, usize), O, E>
where
I: Stream<Token = u8> + AsBytes + StreamIsPartial + Clone,
C: ToUsize,
O: From<u8> + AddAssign + Shl<usize, Output = O> + Shr<usize, Output = O> + PartialEq,
{
let count = count.to_usize();
trace("pattern", move |input: &mut (I, usize)| {
let start = input.checkpoint();
take(count).parse_next(input).and_then(|o| {
if pattern == o {
Ok(o)
} else {
input.reset(start);
Err(ErrMode::Backtrack(E::from_error_kind(
input,
ErrorKind::Tag,
)))
}
})
})
}
/// Deprecated, replaced with [`pattern`]
#[deprecated(since = "0.5.38", note = "Replaced with `pattern`")]
pub fn tag<I, O, C, E: ParserError<(I, usize)>>(p: O, count: C) -> impl Parser<(I, usize), O, E>
where
I: Stream<Token = u8> + AsBytes + StreamIsPartial + Clone,
C: ToUsize,
O: From<u8> + AddAssign + Shl<usize, Output = O> + Shr<usize, Output = O> + PartialEq,
{
pattern(p, count)
}
/// Parses one specific bit as a bool.
///
/// # Example
///
/// ```rust
/// # use winnow::prelude::*;
/// # use winnow::Bytes;
/// # use winnow::error::{InputError, ErrorKind};
/// use winnow::binary::bits::bool;
///
/// type Stream<'i> = &'i Bytes;
///
/// fn stream(b: &[u8]) -> Stream<'_> {
/// Bytes::new(b)
/// }
///
/// fn parse(input: (Stream<'_>, usize)) -> IResult<(Stream<'_>, usize), bool> {
/// bool.parse_peek(input)
/// }
///
/// assert_eq!(parse((stream(&[0b10000000]), 0)), Ok(((stream(&[0b10000000]), 1), true)));
/// assert_eq!(parse((stream(&[0b10000000]), 1)), Ok(((stream(&[0b10000000]), 2), false)));
/// ```
#[doc(alias = "any")]
pub fn bool<I, E: ParserError<(I, usize)>>(input: &mut (I, usize)) -> PResult<bool, E>
where
I: Stream<Token = u8> + AsBytes + StreamIsPartial + Clone,
{
trace("bool", |input: &mut (I, usize)| {
let bit: u32 = take(1usize).parse_next(input)?;
Ok(bit != 0)
})
.parse_next(input)
}

View file

@ -0,0 +1,191 @@
use super::*;
use crate::error::InputError;
use crate::Partial;
#[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].
let result: IResult<&[u8], (u8, u8, u8)> =
bits::<_, _, InputError<(&[u8], usize)>, _, _>((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: IResult<&[u8], (u8, u8)> =
bits::<_, _, InputError<(&[u8], usize)>, _, _>((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: IResult<_, (u8, u8)> =
bits::<_, _, InputError<(_, usize)>, _, _>((take(4usize), take(8usize))).parse_peek(input);
assert!(result.is_err());
let error = result.err().unwrap();
assert_eq!("Parsing requires 2 bytes/chars", error.to_string());
}
#[test]
fn test_take_complete_0() {
let input = &[0b00010010][..];
let count = 0usize;
assert_eq!(count, 0usize);
let offset = 0usize;
let result: crate::IResult<(&[u8], usize), usize> = take(count).parse_peek((input, offset));
assert_eq!(result, Ok(((input, offset), 0)));
}
#[test]
fn test_take_complete_eof() {
let input = &[0b00010010][..];
let result: crate::IResult<(&[u8], usize), usize> = take(1usize).parse_peek((input, 8));
assert_eq!(
result,
Err(crate::error::ErrMode::Backtrack(InputError::new(
(input, 8),
ErrorKind::Eof
)))
);
}
#[test]
fn test_take_complete_span_over_multiple_bytes() {
let input = &[0b00010010, 0b00110100, 0b11111111, 0b11111111][..];
let result: crate::IResult<(&[u8], usize), usize> = take(24usize).parse_peek((input, 4));
assert_eq!(
result,
Ok((([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: crate::IResult<(_, usize), usize> = take(count).parse_peek((input, offset));
assert_eq!(result, Ok(((input, offset), 0)));
}
#[test]
fn test_tag_partial_ok() {
let input = Partial::new(&[0b00011111][..]);
let offset = 0usize;
let bits_to_take = 4usize;
let value_to_tag = 0b0001;
let result: crate::IResult<(_, usize), usize> =
tag(value_to_tag, bits_to_take).parse_peek((input, offset));
assert_eq!(result, Ok(((input, bits_to_take), value_to_tag)));
}
#[test]
fn test_tag_partial_err() {
let input = Partial::new(&[0b00011111][..]);
let offset = 0usize;
let bits_to_take = 4usize;
let value_to_tag = 0b1111;
let result: crate::IResult<(_, usize), usize> =
tag(value_to_tag, bits_to_take).parse_peek((input, offset));
assert_eq!(
result,
Err(crate::error::ErrMode::Backtrack(InputError::new(
(input, offset),
ErrorKind::Tag
)))
);
}
#[test]
fn test_bool_0_complete() {
let input = [0b10000000].as_ref();
let result: crate::IResult<(&[u8], usize), bool> = bool.parse_peek((input, 0));
assert_eq!(result, Ok(((input, 1), true)));
}
#[test]
fn test_bool_eof_complete() {
let input = [0b10000000].as_ref();
let result: crate::IResult<(&[u8], usize), bool> = bool.parse_peek((input, 8));
assert_eq!(
result,
Err(crate::error::ErrMode::Backtrack(InputError::new(
(input, 8),
ErrorKind::Eof
)))
);
}
#[test]
fn test_bool_0_partial() {
let input = Partial::new([0b10000000].as_ref());
let result: crate::IResult<(Partial<&[u8]>, usize), bool> = bool.parse_peek((input, 0));
assert_eq!(result, Ok(((input, 1), true)));
}
#[test]
fn test_bool_eof_partial() {
let input = Partial::new([0b10000000].as_ref());
let result: crate::IResult<(Partial<&[u8]>, usize), bool> = bool.parse_peek((input, 8));
assert_eq!(
result,
Err(crate::error::ErrMode::Incomplete(Needed::new(1)))
);
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,329 @@
use crate::combinator::trace;
use crate::error::{ErrMode, ErrorKind, ParserError};
use crate::stream::Stream;
use crate::*;
#[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) -> PResult<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_6]).
///
/// 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
/// # use winnow::{error::ErrMode, error::InputError,error::ErrorKind, error::Needed};
/// # use winnow::prelude::*;
/// use winnow::ascii::{alpha1, digit1};
/// use winnow::combinator::alt;
/// # fn main() {
/// fn parser(input: &str) -> IResult<&str, &str> {
/// alt((alpha1, digit1)).parse_peek(input)
/// };
///
/// // the first parser, alpha1, recognizes the input
/// assert_eq!(parser("abc"), Ok(("", "abc")));
///
/// // the first parser returns an error, so alt tries the second one
/// assert_eq!(parser("123456"), Ok(("", "123456")));
///
/// // both parsers failed, and with the default error type, alt will return the last error
/// assert_eq!(parser(" "), Err(ErrMode::Backtrack(InputError::new(" ", ErrorKind::Slice))));
/// # }
/// ```
#[doc(alias = "choice")]
pub fn alt<I: Stream, O, E: ParserError<I>, List: Alt<I, O, E>>(
mut l: List,
) -> impl Parser<I, O, E> {
trace("alt", move |i: &mut I| l.choice(i))
}
/// Helper trait for the [permutation()] combinator.
///
/// This trait is implemented for tuples of up to 21 elements
pub trait Permutation<I, O, E> {
/// Tries to apply all parsers in the tuple in various orders until all of them succeed
fn permutation(&mut self, input: &mut I) -> PResult<O, E>;
}
/// Applies a list of parsers in any order.
///
/// Permutation will succeed if all of the child parsers succeeded.
/// It takes as argument a tuple of parsers, and returns a
/// tuple of the parser results.
///
/// To stop on an error, rather than trying further permutations, see
/// [`cut_err`][crate::combinator::cut_err] ([example][crate::_tutorial::chapter_6]).
///
/// # Example
///
/// ```rust
/// # use winnow::{error::ErrMode,error::{InputError, ErrorKind}, error::Needed};
/// # use winnow::prelude::*;
/// use winnow::ascii::{alpha1, digit1};
/// use winnow::combinator::permutation;
/// # fn main() {
/// fn parser(input: &str) -> IResult<&str, (&str, &str)> {
/// permutation((alpha1, digit1)).parse_peek(input)
/// }
///
/// // permutation recognizes alphabetic characters then digit
/// assert_eq!(parser("abc123"), Ok(("", ("abc", "123"))));
///
/// // but also in inverse order
/// assert_eq!(parser("123abc"), Ok(("", ("abc", "123"))));
///
/// // it will fail if one of the parsers failed
/// assert_eq!(parser("abc;"), Err(ErrMode::Backtrack(InputError::new(";", ErrorKind::Slice))));
/// # }
/// ```
///
/// 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
/// # use winnow::{error::ErrMode, error::{InputError, ErrorKind}};
/// # use winnow::prelude::*;
/// use winnow::combinator::permutation;
/// use winnow::token::any;
///
/// fn parser(input: &str) -> IResult<&str, (char, char)> {
/// permutation((any, 'a')).parse_peek(input)
/// }
///
/// // any parses 'b', then char('a') parses 'a'
/// assert_eq!(parser("ba"), Ok(("", ('b', 'a'))));
///
/// // any parses 'a', then char('a') fails on 'b',
/// // even though char('a') followed by any would succeed
/// assert_eq!(parser("ab"), Err(ErrMode::Backtrack(InputError::new("b", ErrorKind::Verify))));
/// ```
///
pub fn permutation<I: Stream, O, E: ParserError<I>, List: Permutation<I, O, E>>(
mut l: List,
) -> impl Parser<I, O, E> {
trace("permutation", move |i: &mut I| l.permutation(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) -> PResult<O, E> {
let mut error: Option<E> = None;
let start = input.checkpoint();
for branch in self {
input.reset(start.clone());
match branch.parse_next(input) {
Err(ErrMode::Backtrack(e)) => {
error = match error {
Some(error) => Some(error.or(e)),
None => Some(e),
};
}
res => return res,
}
}
match error {
Some(e) => Err(ErrMode::Backtrack(e.append(input, ErrorKind::Alt))),
None => Err(ErrMode::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) -> PResult<Output, Error> {
let start = input.checkpoint();
match self.0.parse_next(input) {
Err(ErrMode::Backtrack(e)) => alt_trait_inner!(1, self, input, start, e, $($id)+),
res => res,
}
}
}
);
);
macro_rules! succ (
(0, $submac:ident ! ($($rest:tt)*)) => ($submac!(1, $($rest)*));
(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)*));
(9, $submac:ident ! ($($rest:tt)*)) => ($submac!(10, $($rest)*));
(10, $submac:ident ! ($($rest:tt)*)) => ($submac!(11, $($rest)*));
(11, $submac:ident ! ($($rest:tt)*)) => ($submac!(12, $($rest)*));
(12, $submac:ident ! ($($rest:tt)*)) => ($submac!(13, $($rest)*));
(13, $submac:ident ! ($($rest:tt)*)) => ($submac!(14, $($rest)*));
(14, $submac:ident ! ($($rest:tt)*)) => ($submac!(15, $($rest)*));
(15, $submac:ident ! ($($rest:tt)*)) => ($submac!(16, $($rest)*));
(16, $submac:ident ! ($($rest:tt)*)) => ($submac!(17, $($rest)*));
(17, $submac:ident ! ($($rest:tt)*)) => ($submac!(18, $($rest)*));
(18, $submac:ident ! ($($rest:tt)*)) => ($submac!(19, $($rest)*));
(19, $submac:ident ! ($($rest:tt)*)) => ($submac!(20, $($rest)*));
(20, $submac:ident ! ($($rest:tt)*)) => ($submac!(21, $($rest)*));
);
macro_rules! alt_trait_inner(
($it:tt, $self:expr, $input:expr, $start:ident, $err:expr, $head:ident $($id:ident)+) => ({
$input.reset($start.clone());
match $self.$it.parse_next($input) {
Err(ErrMode::Backtrack(e)) => {
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(ErrMode::Backtrack($err.append($input, ErrorKind::Alt)))
});
);
alt_trait!(Alt2 Alt3 Alt4 Alt5 Alt6 Alt7 Alt8 Alt9 Alt10 Alt11 Alt12 Alt13 Alt14 Alt15 Alt16 Alt17 Alt18 Alt19 Alt20 Alt21 Alt22);
// Manually implement Alt for (A,), the 1-tuple type
impl<I, O, E: ParserError<I>, A: Parser<I, O, E>> Alt<I, O, E> for (A,) {
fn choice(&mut self, input: &mut I) -> PResult<O, E> {
self.0.parse_next(input)
}
}
macro_rules! permutation_trait(
(
$name1:ident $ty1:ident $item1:ident
$name2:ident $ty2:ident $item2:ident
$($name3:ident $ty3:ident $item3:ident)*
) => (
permutation_trait!(__impl $name1 $ty1 $item1, $name2 $ty2 $item2; $($name3 $ty3 $item3)*);
);
(
__impl $($name:ident $ty:ident $item:ident),+;
$name1:ident $ty1:ident $item1:ident $($name2:ident $ty2:ident $item2:ident)*
) => (
permutation_trait_impl!($($name $ty $item),+);
permutation_trait!(__impl $($name $ty $item),+ , $name1 $ty1 $item1; $($name2 $ty2 $item2)*);
);
(__impl $($name:ident $ty:ident $item:ident),+;) => (
permutation_trait_impl!($($name $ty $item),+);
);
);
macro_rules! permutation_trait_impl(
($($name:ident $ty:ident $item:ident),+) => (
impl<
I: Stream, $($ty),+ , Error: ParserError<I>,
$($name: Parser<I, $ty, Error>),+
> Permutation<I, ( $($ty),+ ), Error> for ( $($name),+ ) {
fn permutation(&mut self, input: &mut I) -> PResult<( $($ty),+ ), Error> {
let mut res = ($(Option::<$ty>::None),+);
loop {
let mut err: Option<Error> = None;
let start = input.checkpoint();
permutation_trait_inner!(0, self, input, start, res, err, $($name)+);
// If we reach here, every iterator has either been applied before,
// or errored on the remaining input
if let Some(err) = err {
// There are remaining parsers, and all errored on the remaining input
input.reset(start.clone());
return Err(ErrMode::Backtrack(err.append(input, ErrorKind::Alt)));
}
// All parsers were applied
match res {
($(Some($item)),+) => return Ok(($($item),+)),
_ => unreachable!(),
}
}
}
}
);
);
macro_rules! permutation_trait_inner(
($it:tt, $self:expr, $input:ident, $start:ident, $res:expr, $err:expr, $head:ident $($id:ident)*) => (
if $res.$it.is_none() {
$input.reset($start.clone());
match $self.$it.parse_next($input) {
Ok(o) => {
$res.$it = Some(o);
continue;
}
Err(ErrMode::Backtrack(e)) => {
$err = Some(match $err {
Some(err) => err.or(e),
None => e,
});
}
Err(e) => return Err(e),
};
}
succ!($it, permutation_trait_inner!($self, $input, $start, $res, $err, $($id)*));
);
($it:tt, $self:expr, $input:ident, $start:ident, $res:expr, $err:expr,) => ();
);
permutation_trait!(
P1 O1 o1
P2 O2 o2
P3 O3 o3
P4 O4 o4
P5 O5 o5
P6 O6 o6
P7 O7 o7
P8 O8 o8
P9 O9 o9
P10 O10 o10
P11 O11 o11
P12 O12 o12
P13 O13 o13
P14 O14 o14
P15 O15 o15
P16 O16 o16
P17 O17 o17
P18 O18 o18
P19 O19 o19
P20 O20 o20
P21 O21 o21
);

View file

@ -0,0 +1,501 @@
use crate::combinator::trace;
use crate::error::{ErrMode, ErrorKind, Needed, ParserError};
use crate::stream::Stream;
use crate::*;
/// Return the remaining input.
///
/// # Example
///
/// ```rust
/// # use winnow::prelude::*;
/// # use winnow::error::ErrorKind;
/// # use winnow::error::InputError;
/// use winnow::combinator::rest;
/// assert_eq!(rest::<_,InputError<_>>.parse_peek("abc"), Ok(("", "abc")));
/// assert_eq!(rest::<_,InputError<_>>.parse_peek(""), Ok(("", "")));
/// ```
#[inline]
pub fn rest<I, E: ParserError<I>>(input: &mut I) -> PResult<<I as Stream>::Slice, E>
where
I: Stream,
{
trace("rest", move |input: &mut I| Ok(input.finish())).parse_next(input)
}
/// Return the length of the remaining input.
///
/// Note: this does not advance the [`Stream`]
///
/// # Example
///
/// ```rust
/// # use winnow::prelude::*;
/// # use winnow::error::ErrorKind;
/// # use winnow::error::InputError;
/// use winnow::combinator::rest_len;
/// assert_eq!(rest_len::<_,InputError<_>>.parse_peek("abc"), Ok(("abc", 3)));
/// assert_eq!(rest_len::<_,InputError<_>>.parse_peek(""), Ok(("", 0)));
/// ```
#[inline]
pub fn rest_len<I, E: ParserError<I>>(input: &mut I) -> PResult<usize, E>
where
I: Stream,
{
trace("rest_len", move |input: &mut I| {
let len = input.eof_offset();
Ok(len)
})
.parse_next(input)
}
/// Apply a [`Parser`], producing `None` on [`ErrMode::Backtrack`].
///
/// To chain an error up, see [`cut_err`].
///
/// # Example
///
/// ```rust
/// # use winnow::{error::ErrMode, error::ErrorKind, error::InputError};
/// # use winnow::prelude::*;
/// use winnow::combinator::opt;
/// use winnow::ascii::alpha1;
/// # fn main() {
///
/// fn parser(i: &str) -> IResult<&str, Option<&str>> {
/// opt(alpha1).parse_peek(i)
/// }
///
/// assert_eq!(parser("abcd;"), Ok((";", Some("abcd"))));
/// assert_eq!(parser("123;"), Ok(("123;", None)));
/// # }
/// ```
pub fn opt<I: Stream, O, E: ParserError<I>, F>(mut f: F) -> impl Parser<I, Option<O>, E>
where
F: Parser<I, O, E>,
{
trace("opt", move |input: &mut I| {
let start = input.checkpoint();
match f.parse_next(input) {
Ok(o) => Ok(Some(o)),
Err(ErrMode::Backtrack(_)) => {
input.reset(start);
Ok(None)
}
Err(e) => Err(e),
}
})
}
/// Calls the parser if the condition is met.
///
/// # Example
///
/// ```rust
/// # use winnow::{error::ErrMode, error::{InputError, ErrorKind}, IResult};
/// # use winnow::prelude::*;
/// use winnow::combinator::cond;
/// use winnow::ascii::alpha1;
/// # fn main() {
///
/// fn parser(b: bool, i: &str) -> IResult<&str, Option<&str>> {
/// cond(b, alpha1).parse_peek(i)
/// }
///
/// assert_eq!(parser(true, "abcd;"), Ok((";", Some("abcd"))));
/// assert_eq!(parser(false, "abcd;"), Ok(("abcd;", None)));
/// assert_eq!(parser(true, "123;"), Err(ErrMode::Backtrack(InputError::new("123;", ErrorKind::Slice))));
/// assert_eq!(parser(false, "123;"), Ok(("123;", None)));
/// # }
/// ```
pub fn cond<I, O, E: ParserError<I>, F>(b: bool, mut f: F) -> impl Parser<I, Option<O>, E>
where
I: Stream,
F: Parser<I, O, E>,
{
trace("cond", move |input: &mut I| {
if b {
f.parse_next(input).map(Some)
} else {
Ok(None)
}
})
}
/// Tries to apply its parser without consuming the input.
///
/// # Example
///
/// ```rust
/// # use winnow::{error::ErrMode, error::ErrorKind, error::InputError, IResult};
/// # use winnow::prelude::*;
/// use winnow::combinator::peek;
/// use winnow::ascii::alpha1;
/// # fn main() {
///
/// let mut parser = peek(alpha1);
///
/// assert_eq!(parser.parse_peek("abcd;"), Ok(("abcd;", "abcd")));
/// assert_eq!(parser.parse_peek("123;"), Err(ErrMode::Backtrack(InputError::new("123;", ErrorKind::Slice))));
/// # }
/// ```
#[doc(alias = "look_ahead")]
#[doc(alias = "rewind")]
pub fn peek<I: Stream, O, E: ParserError<I>, F>(mut f: F) -> impl Parser<I, O, E>
where
F: Parser<I, O, E>,
{
trace("peek", move |input: &mut I| {
let start = input.checkpoint();
let res = f.parse_next(input);
input.reset(start);
res
})
}
/// Match the end of the [`Stream`]
///
/// Otherwise, it will error.
///
/// # Example
///
/// ```rust
/// # use std::str;
/// # use winnow::{error::ErrMode, error::ErrorKind, error::InputError};
/// # use winnow::combinator::eof;
/// # use winnow::prelude::*;
///
/// let mut parser = eof;
/// assert_eq!(parser.parse_peek("abc"), Err(ErrMode::Backtrack(InputError::new("abc", ErrorKind::Eof))));
/// assert_eq!(parser.parse_peek(""), Ok(("", "")));
/// ```
#[doc(alias = "end")]
#[doc(alias = "eoi")]
pub fn eof<I, E: ParserError<I>>(input: &mut I) -> PResult<<I as Stream>::Slice, E>
where
I: Stream,
{
trace("eof", move |input: &mut I| {
if input.eof_offset() == 0 {
Ok(input.next_slice(0))
} else {
Err(ErrMode::from_error_kind(input, ErrorKind::Eof))
}
})
.parse_next(input)
}
/// Succeeds if the child parser returns an error.
///
/// **Note:** This does not advance the [`Stream`]
///
/// # Example
///
/// ```rust
/// # use winnow::{error::ErrMode, error::ErrorKind, error::InputError, IResult};
/// # use winnow::prelude::*;
/// use winnow::combinator::not;
/// use winnow::ascii::alpha1;
/// # fn main() {
///
/// let mut parser = not(alpha1);
///
/// assert_eq!(parser.parse_peek("123"), Ok(("123", ())));
/// assert_eq!(parser.parse_peek("abcd"), Err(ErrMode::Backtrack(InputError::new("abcd", ErrorKind::Not))));
/// # }
/// ```
pub fn not<I: Stream, O, E: ParserError<I>, F>(mut parser: F) -> impl Parser<I, (), E>
where
F: Parser<I, O, E>,
{
trace("not", move |input: &mut I| {
let start = input.checkpoint();
let res = parser.parse_next(input);
input.reset(start);
match res {
Ok(_) => Err(ErrMode::from_error_kind(input, ErrorKind::Not)),
Err(ErrMode::Backtrack(_)) => Ok(()),
Err(e) => Err(e),
}
})
}
/// Transforms an [`ErrMode::Backtrack`] (recoverable) to [`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_6] for more details.
///
/// # Example
///
/// Without `cut_err`:
/// ```rust
/// # use winnow::{error::ErrMode, error::ErrorKind, error::InputError};
/// # use winnow::token::one_of;
/// # use winnow::ascii::digit1;
/// # use winnow::combinator::rest;
/// # use winnow::combinator::alt;
/// # use winnow::combinator::preceded;
/// # use winnow::prelude::*;
/// # fn main() {
///
/// fn parser(input: &str) -> IResult<&str, &str> {
/// alt((
/// preceded(one_of(['+', '-']), digit1),
/// rest
/// )).parse_peek(input)
/// }
///
/// assert_eq!(parser("+10 ab"), Ok((" ab", "10")));
/// assert_eq!(parser("ab"), Ok(("", "ab")));
/// assert_eq!(parser("+"), Ok(("", "+")));
/// # }
/// ```
///
/// With `cut_err`:
/// ```rust
/// # use winnow::{error::ErrMode, error::ErrorKind, error::InputError};
/// # use winnow::prelude::*;
/// # use winnow::token::one_of;
/// # use winnow::ascii::digit1;
/// # use winnow::combinator::rest;
/// # use winnow::combinator::alt;
/// # use winnow::combinator::preceded;
/// use winnow::combinator::cut_err;
/// # fn main() {
///
/// fn parser(input: &str) -> IResult<&str, &str> {
/// alt((
/// preceded(one_of(['+', '-']), cut_err(digit1)),
/// rest
/// )).parse_peek(input)
/// }
///
/// assert_eq!(parser("+10 ab"), Ok((" ab", "10")));
/// assert_eq!(parser("ab"), Ok(("", "ab")));
/// assert_eq!(parser("+"), Err(ErrMode::Cut(InputError::new("", ErrorKind::Slice ))));
/// # }
/// ```
pub fn cut_err<I, O, E: ParserError<I>, F>(mut parser: F) -> impl Parser<I, O, E>
where
I: Stream,
F: Parser<I, O, E>,
{
trace("cut_err", move |input: &mut I| {
parser.parse_next(input).map_err(|e| e.cut())
})
}
/// Transforms an [`ErrMode::Cut`] (unrecoverable) to [`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<I, O, E: ParserError<I>, F>(mut parser: F) -> impl Parser<I, O, E>
where
I: Stream,
F: Parser<I, O, E>,
{
trace("backtrack_err", move |input: &mut I| {
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) -> PResult<u64> {
/// todo(input)
/// }
/// ```
#[track_caller]
pub fn todo<I, O, E>(input: &mut I) -> PResult<O, E>
where
I: Stream,
{
#![allow(clippy::todo)]
trace("todo", move |_input: &mut I| todo!("unimplemented parse")).parse_next(input)
}
/// Repeats the embedded parser, lazily returning the results
///
/// Call the iterator's [`ParserIterator::finish`] method to get the remaining input if successful,
/// or the error value if we encountered an error.
///
/// On [`ErrMode::Backtrack`], iteration will stop. To instead chain an error up, see [`cut_err`].
///
/// # Example
///
/// ```rust
/// use winnow::{combinator::iterator, IResult, token::tag, ascii::alpha1, combinator::terminated};
/// use std::collections::HashMap;
///
/// let data = "abc|defg|hijkl|mnopqr|123";
/// let mut it = iterator(data, terminated(alpha1, "|"));
///
/// let parsed = it.map(|v| (v, v.len())).collect::<HashMap<_,_>>();
/// let res: IResult<_,_> = it.finish();
///
/// assert_eq!(parsed, [("abc", 3usize), ("defg", 4), ("hijkl", 5), ("mnopqr", 6)].iter().cloned().collect());
/// assert_eq!(res, Ok(("123", ())));
/// ```
pub fn iterator<I, O, E, F>(input: I, parser: F) -> ParserIterator<F, I, O, E>
where
F: Parser<I, O, E>,
I: Stream,
E: ParserError<I>,
{
ParserIterator {
parser,
input,
state: Some(State::Running),
o: Default::default(),
}
}
/// Main structure associated to [`iterator`].
pub struct ParserIterator<F, I, O, E>
where
F: Parser<I, O, E>,
I: Stream,
{
parser: F,
input: I,
state: Option<State<E>>,
o: core::marker::PhantomData<O>,
}
impl<F, I, O, E> ParserIterator<F, I, O, E>
where
F: Parser<I, O, E>,
I: Stream,
{
/// Returns the remaining input if parsing was successful, or the error if we encountered an error.
pub fn finish(mut self) -> PResult<(I, ()), E> {
match self.state.take().unwrap() {
State::Running | State::Done => Ok((self.input, ())),
State::Failure(e) => Err(ErrMode::Cut(e)),
State::Incomplete(i) => Err(ErrMode::Incomplete(i)),
}
}
}
impl<'a, F, I, O, E> core::iter::Iterator for &'a mut ParserIterator<F, I, O, E>
where
F: Parser<I, O, E>,
I: Stream,
{
type Item = O;
fn next(&mut self) -> Option<Self::Item> {
if let State::Running = self.state.take().unwrap() {
let start = self.input.checkpoint();
match self.parser.parse_next(&mut self.input) {
Ok(o) => {
self.state = Some(State::Running);
Some(o)
}
Err(ErrMode::Backtrack(_)) => {
self.input.reset(start);
self.state = Some(State::Done);
None
}
Err(ErrMode::Cut(e)) => {
self.state = Some(State::Failure(e));
None
}
Err(ErrMode::Incomplete(i)) => {
self.state = Some(State::Incomplete(i));
None
}
}
} else {
None
}
}
}
enum State<E> {
Running,
Done,
Failure(E),
Incomplete(Needed),
}
/// 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`]
///
/// **Note:** This never advances the [`Stream`]
///
/// # Example
///
/// ```rust
/// # use winnow::{error::ErrMode, error::ErrorKind, error::InputError};
/// # use winnow::prelude::*;
/// use winnow::combinator::alt;
/// use winnow::combinator::empty;
///
/// fn sign(input: &str) -> IResult<&str, isize> {
/// alt((
/// '-'.value(-1),
/// '+'.value(1),
/// empty.value(1)
/// )).parse_peek(input)
/// }
/// assert_eq!(sign("+10"), Ok(("10", 1)));
/// assert_eq!(sign("-10"), Ok(("10", -1)));
/// assert_eq!(sign("10"), Ok(("10", 1)));
/// ```
#[doc(alias = "value")]
#[doc(alias = "success")]
pub fn empty<I: Stream, E: ParserError<I>>(_input: &mut I) -> PResult<(), E> {
Ok(())
}
/// Deprecated, replaced with [`empty`] + [`Parser::value`]
#[deprecated(since = "0.5.35", note = "Replaced with empty.value(...)`")]
pub fn success<I: Stream, O: Clone, E: ParserError<I>>(val: O) -> impl Parser<I, O, E> {
trace("success", move |_input: &mut I| Ok(val.clone()))
}
/// 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::ErrorKind, error::InputError, IResult};
/// # use winnow::prelude::*;
/// use winnow::combinator::fail;
///
/// let s = "string";
/// assert_eq!(fail::<_, &str, _>.parse_peek(s), Err(ErrMode::Backtrack(InputError::new(s, ErrorKind::Fail))));
/// ```
#[doc(alias = "unexpected")]
pub fn fail<I: Stream, O, E: ParserError<I>>(i: &mut I) -> PResult<O, E> {
trace("fail", |i: &mut I| {
Err(ErrMode::from_error_kind(i, ErrorKind::Fail))
})
.parse_next(i)
}

View file

@ -0,0 +1,301 @@
#![cfg(feature = "std")]
use std::io::Write;
use crate::error::ErrMode;
use crate::stream::Stream;
use crate::*;
pub struct Trace<P, D, I, O, E>
where
P: Parser<I, O, E>,
I: Stream,
D: std::fmt::Display,
{
parser: P,
name: D,
call_count: usize,
i: core::marker::PhantomData<I>,
o: core::marker::PhantomData<O>,
e: core::marker::PhantomData<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,
{
#[inline(always)]
pub fn new(parser: P, name: D) -> Self {
Self {
parser,
name,
call_count: 0,
i: Default::default(),
o: Default::default(),
e: 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,
{
#[inline]
fn parse_next(&mut self, i: &mut I) -> PResult<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 struct Depth {
depth: usize,
inc: bool,
}
impl Depth {
pub fn new() -> Self {
let depth = DEPTH.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
let inc = true;
Self { depth, inc }
}
pub 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 crate::lib::std::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 enum Severity {
Success,
Backtrack,
Cut,
Incomplete,
}
impl Severity {
pub fn with_result<T, E>(result: &Result<T, ErrMode<E>>) -> Self {
match result {
Ok(_) => Self::Success,
Err(ErrMode::Backtrack(_)) => Self::Backtrack,
Err(ErrMode::Cut(_)) => Self::Cut,
Err(ErrMode::Incomplete(_)) => Self::Incomplete,
}
}
}
pub fn start<I: Stream>(
depth: usize,
name: &dyn crate::lib::std::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!("{:#?}", input.raw());
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 fn end(
depth: usize,
name: &dyn crate::lib::std::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 fn result(depth: usize, name: &dyn crate::lib::std::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
.checked_sub(min_call_width + min_input_width + decor_width)
.unwrap_or_default();
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::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())
}

View file

@ -0,0 +1,91 @@
#![cfg_attr(feature = "debug", allow(clippy::std_instead_of_core))]
#[cfg(feature = "debug")]
mod internals;
use crate::error::ErrMode;
use crate::stream::Stream;
use crate::Parser;
#[cfg(all(feature = "debug", not(feature = "std")))]
compile_error!("`debug` requires `std`");
/// 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::{InputError, ErrorKind}, 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]) -> PResult<&'s [u8], InputError<&'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_eq!(short_alpha.parse_peek(b"ed"), Err(ErrMode::Backtrack(InputError::new(&b"ed"[..], ErrorKind::Slice))));
/// assert_eq!(short_alpha.parse_peek(b"12345"), Err(ErrMode::Backtrack(InputError::new(&b"12345"[..], ErrorKind::Slice))));
/// ```
#[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>(
name: impl crate::lib::std::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, E>(
name: impl crate::lib::std::fmt::Display,
res: &Result<T, ErrMode<E>>,
) {
#[cfg(feature = "debug")]
{
let depth = internals::Depth::existing();
let severity = internals::Severity::with_result(res);
internals::result(*depth, &name, severity);
}
}
#[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", [cmd.as_str()]);
}

View file

@ -0,0 +1,176 @@
//! # List of parsers and combinators
//!
//! **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.
//!
//! ## Basic elements
//!
//! Those are used to recognize 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 characters (works with non ASCII characters too)|
//! | [`none_of`][crate::token::none_of] | `none_of(['a', 'b', 'c'])` | `"xyab"` | `"yab"` | `Ok('x')` |Matches anything but the provided characters|
//! | [`tag`][crate::token::tag] | `"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 list of bytes for which the provided pattern matches.|
//! | [`take_till0`][crate::token::take_till0] | `take_till0(is_alphabetic)` | `"123abc"` | `"abc"` | `Ok("123")` |Returns the longest list of bytes or characters until the provided pattern matches. `take_till1` does the same, but must return at least one character. 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 the longest list of bytes or characters until the provided tag 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 |
//! | [`permutation`] | `permutation(("ab", "cd", "12"))` | `"cd12abc"` | `"c"` | `Ok(("ab", "cd", "12"))` |Succeeds when all its child parser have succeeded, whatever the order|
//!
//! ## Sequence combinators
//!
//! | combinator | usage | input | new input | output | comment |
//! |---|---|---|---|---|---|
//! | [`(...)` (tuples)][crate::Parser] | `("ab", "XY", take(1))` | `"abXYZ!"` | `"!"` | `Ok(("ab", "XY", "Z"))` |Chains parsers and assemble the sub results in a tuple. You can use as many child parsers as you can put elements in a tuple|
//! | [`seq!`] | `seq!(_: char('('), take(2), _: char(')'))` | `"(ab)cd"` | `"cd"` | `Ok("ab")` ||
//! | [`delimited`] | `delimited(char('('), take(2), char(')'))` | `"(ab)cd"` | `"cd"` | `Ok("ab")` ||
//! | [`preceded`] | `preceded("ab", "XY")` | `"abXYZ"` | `"Z"` | `Ok("XY")` ||
//! | [`terminated`] | `terminated("ab", "XY")` | `"abXYZ"` | `"Z"` | `Ok("ab")` ||
//! | [`separated_pair`] | `separated_pair("hello", char(','), "world")` | `"hello,world!"` | `"!"` | `Ok(("hello", "world"))` ||
//!
//! ## 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.., tag( "ab" ), tag( "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|
//! | [`fold_repeat`] | `fold_repeat(1..=2, be_u8, \|\| 0, \|acc, item\| acc + item)` | `[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::recognize`]: If the child parser was successful, return the consumed input as the produced value
//! - [`Parser::with_recognized`]: 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
//!
//! - [`success`]: Returns a value without consuming any input, always succeeds
//! - [`fail`]: Inversion of `success`. 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`]: 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
//!
//! - [`escaped`][crate::ascii::escaped]: Matches a byte string with escaped characters
//! - [`escaped_transform`][crate::ascii::escaped_transform]: Matches a byte string with escaped characters, and returns a new string with the escaped characters replaced
//!
//! ### 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_count`][crate::binary::length_count] Gets a number from the first parser, then applies the second parser that many times
//! - [`length_data`][crate::binary::length_data]: Gets a number from the first parser, then takes a subslice of the input of that size, and returns that subslice
//! - [`length_value`][crate::binary::length_value]: 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
//! - [`tag`][crate::binary::bits::tag]: 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 multi;
mod parser;
mod sequence;
#[cfg(test)]
mod tests;
pub use self::branch::*;
pub use self::core::*;
pub use self::debug::*;
pub use self::multi::*;
pub use self::parser::*;
pub use self::sequence::*;
#[allow(unused_imports)]
use crate::Parser;

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,175 @@
use crate::combinator::trace;
use crate::error::ParserError;
use crate::stream::Stream;
use crate::*;
#[doc(inline)]
pub use crate::seq;
/// Sequence two parsers, only returning the output from the second.
///
/// # Arguments
/// * `first` The opening parser.
/// * `second` The second parser to get object.
///
/// See also [`seq`] to generalize this across any number of fields.
///
/// # Example
///
/// ```rust
/// # use winnow::{error::ErrMode, error::ErrorKind, error::InputError, error::Needed};
/// # use winnow::prelude::*;
/// # use winnow::error::Needed::Size;
/// use winnow::combinator::preceded;
/// use winnow::token::tag;
///
/// let mut parser = preceded("abc", "efg");
///
/// assert_eq!(parser.parse_peek("abcefg"), Ok(("", "efg")));
/// assert_eq!(parser.parse_peek("abcefghij"), Ok(("hij", "efg")));
/// assert_eq!(parser.parse_peek(""), Err(ErrMode::Backtrack(InputError::new("", ErrorKind::Tag))));
/// assert_eq!(parser.parse_peek("123"), Err(ErrMode::Backtrack(InputError::new("123", ErrorKind::Tag))));
/// ```
#[doc(alias = "ignore_then")]
pub fn preceded<I, O1, O2, E: ParserError<I>, F, G>(
mut first: F,
mut second: G,
) -> impl Parser<I, O2, E>
where
I: Stream,
F: Parser<I, O1, E>,
G: Parser<I, O2, E>,
{
trace("preceded", move |input: &mut I| {
let _ = first.parse_next(input)?;
second.parse_next(input)
})
}
/// Sequence two parsers, only returning the output of the first.
///
/// # Arguments
/// * `first` The first parser to apply.
/// * `second` The second parser to match an object.
///
/// See also [`seq`] to generalize this across any number of fields.
///
/// # Example
///
/// ```rust
/// # use winnow::{error::ErrMode, error::ErrorKind, error::InputError, error::Needed};
/// # use winnow::prelude::*;
/// # use winnow::error::Needed::Size;
/// use winnow::combinator::terminated;
/// use winnow::token::tag;
///
/// let mut parser = terminated("abc", "efg");
///
/// assert_eq!(parser.parse_peek("abcefg"), Ok(("", "abc")));
/// assert_eq!(parser.parse_peek("abcefghij"), Ok(("hij", "abc")));
/// assert_eq!(parser.parse_peek(""), Err(ErrMode::Backtrack(InputError::new("", ErrorKind::Tag))));
/// assert_eq!(parser.parse_peek("123"), Err(ErrMode::Backtrack(InputError::new("123", ErrorKind::Tag))));
/// ```
#[doc(alias = "then_ignore")]
pub fn terminated<I, O1, O2, E: ParserError<I>, F, G>(
mut first: F,
mut second: G,
) -> impl Parser<I, O1, E>
where
I: Stream,
F: Parser<I, O1, E>,
G: Parser<I, O2, E>,
{
trace("terminated", move |input: &mut I| {
let o1 = first.parse_next(input)?;
second.parse_next(input).map(|_| o1)
})
}
/// Sequence three parsers, only returning the values of the first and third.
///
/// # Arguments
/// * `first` The first parser to apply.
/// * `sep` The separator parser to apply.
/// * `second` The second parser to apply.
///
/// See also [`seq`] to generalize this across any number of fields.
///
/// # Example
///
/// ```rust
/// # use winnow::{error::ErrMode, error::ErrorKind, error::InputError, error::Needed};
/// # use winnow::error::Needed::Size;
/// # use winnow::prelude::*;
/// use winnow::combinator::separated_pair;
/// use winnow::token::tag;
///
/// let mut parser = separated_pair("abc", "|", "efg");
///
/// assert_eq!(parser.parse_peek("abc|efg"), Ok(("", ("abc", "efg"))));
/// assert_eq!(parser.parse_peek("abc|efghij"), Ok(("hij", ("abc", "efg"))));
/// assert_eq!(parser.parse_peek(""), Err(ErrMode::Backtrack(InputError::new("", ErrorKind::Tag))));
/// assert_eq!(parser.parse_peek("123"), Err(ErrMode::Backtrack(InputError::new("123", ErrorKind::Tag))));
/// ```
pub fn separated_pair<I, O1, O2, O3, E: ParserError<I>, F, G, H>(
mut first: F,
mut sep: G,
mut second: H,
) -> impl Parser<I, (O1, O3), E>
where
I: Stream,
F: Parser<I, O1, E>,
G: Parser<I, O2, E>,
H: Parser<I, O3, E>,
{
trace("separated_pair", move |input: &mut I| {
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.
///
/// # Arguments
/// * `first` The first parser to apply and discard.
/// * `second` The second parser to apply.
/// * `third` The third parser to apply and discard.
///
/// See also [`seq`] to generalize this across any number of fields.
///
/// # Example
///
/// ```rust
/// # use winnow::{error::ErrMode, error::ErrorKind, error::InputError, error::Needed};
/// # use winnow::error::Needed::Size;
/// # use winnow::prelude::*;
/// use winnow::combinator::delimited;
/// use winnow::token::tag;
///
/// let mut parser = delimited("(", "abc", ")");
///
/// assert_eq!(parser.parse_peek("(abc)"), Ok(("", "abc")));
/// assert_eq!(parser.parse_peek("(abc)def"), Ok(("def", "abc")));
/// assert_eq!(parser.parse_peek(""), Err(ErrMode::Backtrack(InputError::new("", ErrorKind::Tag))));
/// assert_eq!(parser.parse_peek("123"), Err(ErrMode::Backtrack(InputError::new("123", ErrorKind::Tag))));
/// ```
#[doc(alias = "between")]
#[doc(alias = "padded")]
pub fn delimited<I, O1, O2, O3, E: ParserError<I>, F, G, H>(
mut first: F,
mut second: G,
mut third: H,
) -> impl Parser<I, O2, E>
where
I: Stream,
F: Parser<I, O1, E>,
G: Parser<I, O2, E>,
H: Parser<I, O3, E>,
{
trace("delimited", move |input: &mut I| {
let _ = first.parse_next(input)?;
let o2 = second.parse_next(input)?;
third.parse_next(input).map(|_| o2)
})
}

File diff suppressed because it is too large Load diff

1467
third-party/vendor/winnow/src/error.rs vendored Normal file

File diff suppressed because it is too large Load diff

256
third-party/vendor/winnow/src/lib.rs vendored Normal file
View file

@ -0,0 +1,256 @@
//! > 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/v0.5.40/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 binary data, strings, or
//! separate lexing and parsing phases
//! 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, currently 1.64.0)
//!
//! See also [Special Topic: Why winnow?][crate::_topic::why]
//!
//! ## Example
//!
//! Run
//! ```console
//! $ cargo add winnow
//! ```
//!
//! Then use it to parse:
//! ```rust
//! # #[cfg(feature = "alloc")] {
#![doc = include_str!("../examples/css/parser.rs")]
//! # }
//! ```
//!
//! See also the [Tutorial][_tutorial::chapter_0] and [Special Topics][_topic]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(docsrs, feature(extended_key_value_attributes))]
#![cfg_attr(not(feature = "std"), no_std)]
#![warn(missing_docs)]
#![warn(clippy::std_instead_of_core)]
// BEGIN - Embark standard lints v6 for Rust 1.55+
// do not change or add/remove here, but one can add exceptions after this section
// for more info see: <https://github.com/EmbarkStudios/rust-ecosystem/issues/59>
// "-Dunsafe_code",
#![warn(clippy::all)]
#![warn(clippy::await_holding_lock)]
#![warn(clippy::char_lit_as_u8)]
#![warn(clippy::checked_conversions)]
#![warn(clippy::dbg_macro)]
#![warn(clippy::debug_assert_with_mut_call)]
#![warn(clippy::doc_markdown)]
#![warn(clippy::empty_enum)]
#![warn(clippy::enum_glob_use)]
#![warn(clippy::exit)]
#![warn(clippy::expl_impl_clone_on_copy)]
#![warn(clippy::explicit_deref_methods)]
#![warn(clippy::explicit_into_iter_loop)]
#![warn(clippy::fallible_impl_from)]
#![warn(clippy::filter_map_next)]
#![warn(clippy::flat_map_option)]
#![warn(clippy::float_cmp_const)]
#![warn(clippy::fn_params_excessive_bools)]
#![warn(clippy::from_iter_instead_of_collect)]
#![warn(clippy::if_let_mutex)]
#![warn(clippy::implicit_clone)]
#![warn(clippy::imprecise_flops)]
#![warn(clippy::inefficient_to_string)]
#![warn(clippy::invalid_upcast_comparisons)]
#![warn(clippy::large_digit_groups)]
#![warn(clippy::large_stack_arrays)]
#![warn(clippy::large_types_passed_by_value)]
#![warn(clippy::let_unit_value)]
#![warn(clippy::linkedlist)]
#![warn(clippy::lossy_float_literal)]
#![warn(clippy::macro_use_imports)]
#![warn(clippy::manual_ok_or)]
#![warn(clippy::map_err_ignore)]
#![warn(clippy::map_flatten)]
#![warn(clippy::map_unwrap_or)]
#![warn(clippy::match_on_vec_items)]
#![warn(clippy::match_same_arms)]
#![warn(clippy::match_wild_err_arm)]
#![warn(clippy::match_wildcard_for_single_variants)]
#![warn(clippy::mem_forget)]
#![warn(clippy::mismatched_target_os)]
#![warn(clippy::missing_enforced_import_renames)]
#![warn(clippy::mut_mut)]
#![warn(clippy::mutex_integer)]
#![warn(clippy::needless_borrow)]
#![warn(clippy::needless_continue)]
#![warn(clippy::needless_for_each)]
#![warn(clippy::option_option)]
#![warn(clippy::path_buf_push_overwrite)]
#![warn(clippy::ptr_as_ptr)]
#![warn(clippy::rc_mutex)]
#![warn(clippy::ref_option_ref)]
#![warn(clippy::rest_pat_in_fully_bound_structs)]
#![warn(clippy::same_functions_in_if_condition)]
#![warn(clippy::semicolon_if_nothing_returned)]
#![warn(clippy::single_match_else)]
#![warn(clippy::string_add_assign)]
#![warn(clippy::string_add)]
#![warn(clippy::string_lit_as_bytes)]
#![warn(clippy::string_to_string)]
#![warn(clippy::todo)]
#![warn(clippy::trait_duplication_in_bounds)]
#![warn(clippy::unimplemented)]
#![warn(clippy::unnested_or_patterns)]
#![warn(clippy::unused_self)]
#![warn(clippy::useless_transmute)]
#![warn(clippy::verbose_file_reads)]
#![warn(clippy::zero_sized_map_values)]
#![warn(future_incompatible)]
#![warn(nonstandard_style)]
#![warn(rust_2018_idioms)]
// END - Embark standard lints v6 for Rust 1.55+
#![allow(clippy::branches_sharing_code)]
#![allow(clippy::collapsible_else_if)]
#![allow(clippy::if_same_then_else)]
#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::let_and_return)]
#![allow(clippy::assertions_on_constants)]
#![allow(clippy::map_unwrap_or)]
#![allow(clippy::single_match_else)]
#![allow(clippy::single_match)]
#![allow(clippy::unnested_or_patterns)]
#![allow(deprecated)]
#[cfg_attr(nightly, warn(rustdoc::missing_doc_code_examples))]
#[cfg(feature = "alloc")]
#[cfg_attr(test, macro_use)]
extern crate alloc;
#[cfg(doctest)]
extern crate doc_comment;
#[cfg(doctest)]
doc_comment::doctest!("../README.md");
/// Lib module to re-export everything needed from `std` or `core`/`alloc`. This is how `serde` does
/// it, albeit there it is not public.
#[doc(hidden)]
pub(crate) mod lib {
/// `std` facade allowing `std`/`core` to be interchangeable. Reexports `alloc` crate optionally,
/// as well as `core` or `std`
#[cfg(not(feature = "std"))]
/// internal std exports for no_std compatibility
pub mod std {
#[doc(hidden)]
#[cfg(not(feature = "alloc"))]
pub use core::borrow;
#[cfg(feature = "alloc")]
#[doc(hidden)]
pub use alloc::{borrow, boxed, collections, string, vec};
#[doc(hidden)]
pub use core::{cmp, convert, fmt, hash, iter, mem, ops, option, result, slice, str};
/// internal reproduction of std prelude
#[doc(hidden)]
pub mod prelude {
pub use core::prelude as v1;
}
}
#[cfg(feature = "std")]
/// internal std exports for `no_std` compatibility
pub mod std {
#![allow(clippy::std_instead_of_core)]
#[doc(hidden)]
pub use std::{
alloc, borrow, boxed, cmp, collections, convert, fmt, hash, iter, mem, ops, option,
result, slice, str, string, vec,
};
/// internal reproduction of std prelude
#[doc(hidden)]
pub mod prelude {
pub use std::prelude as v1;
}
}
}
#[macro_use]
mod macros;
#[macro_use]
pub mod error;
mod parser;
pub mod stream;
pub mod ascii;
pub mod binary;
pub mod combinator;
pub mod token;
pub mod trace;
#[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
/// use winnow::prelude::*;
///
/// fn parse_data(input: &mut &str) -> PResult<u64> {
/// // ...
/// # winnow::ascii::dec_uint(input)
/// }
///
/// fn main() {
/// let result = parse_data.parse("100");
/// assert_eq!(result, Ok(100));
/// }
/// ```
pub mod prelude {
pub use crate::stream::StreamIsPartial as _;
pub use crate::IResult;
pub use crate::PResult;
pub use crate::Parser;
#[cfg(feature = "unstable-recover")]
pub use crate::RecoverableParser as _;
}
pub use error::IResult;
pub use error::PResult;
pub use parser::*;
pub use stream::BStr;
pub use stream::Bytes;
pub use stream::Located;
pub use stream::Partial;
pub use stream::Stateful;
pub use stream::Str;

View file

@ -0,0 +1,55 @@
/// `match` for parsers
///
/// 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`][crate::combinator::peek].
///
/// For tight control over the error in a catch-all case, use [`fail`][crate::combinator::fail].
///
/// # Example
///
/// ```rust
/// use winnow::prelude::*;
/// use winnow::combinator::dispatch;
/// # use winnow::token::any;
/// # use winnow::combinator::peek;
/// # use winnow::combinator::preceded;
/// # use winnow::combinator::empty;
/// # use winnow::combinator::fail;
///
/// fn escaped(input: &mut &str) -> PResult<char> {
/// preceded('\\', escape_seq_char).parse_next(input)
/// }
///
/// fn escape_seq_char(input: &mut &str) -> PResult<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 {
($match_parser: expr; $( $pat:pat $(if $pred:expr)? => $expr: expr ),+ $(,)? ) => {
$crate::combinator::trace("dispatch", move |i: &mut _|
{
use $crate::Parser;
let initial = $match_parser.parse_next(i)?;
match initial {
$(
$pat $(if $pred)? => $expr.parse_next(i),
)*
}
})
}
}

View file

@ -0,0 +1,5 @@
mod dispatch;
mod seq;
#[cfg(test)]
mod test;

View file

@ -0,0 +1,268 @@
/// Initialize a struct or tuple out of a sequences of parsers
///
///# Example
///
/// ```
/// # use winnow::prelude::*;
/// # use winnow::ascii::{alphanumeric1, dec_uint, space0};
/// # use winnow::combinator::delimited;
/// # use winnow::combinator::empty;
/// # use winnow::error::ContextError;
/// 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]) -> PResult<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]) -> PResult<(u32, u32)> {
/// let num = dec_uint::<_, u32, 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")]
macro_rules! seq {
($name: ident { $($fields: tt)* }) => {
$crate::combinator::trace(stringify!($name), move |input: &mut _| {
use $crate::Parser;
$crate::seq_parse_struct_fields!(input; $($fields)*);
#[allow(clippy::redundant_field_names)]
Ok($crate::seq_init_struct_fields!( ($($fields)*); $name;))
})
};
($name: ident ( $($elements: tt)* )) => {
$crate::combinator::trace(stringify!($name), move |input: &mut _| {
use $crate::Parser;
$crate::seq_parse_tuple_fields!( ($($elements)*) ; ).map(|t| {
$crate::seq_init_tuple_fields!(
($($elements)*);
(t.0, t.1, t.2, t.3, t.4, t.5, t.6, t.7, t.8, t.9, t.10, t.11, t.12, t.13, t.14, t.15, t.16, t.17, t.18, t.19, t.20);
$name;
)
}).parse_next(input)
})
};
(( $($elements: tt)* )) => {
$crate::combinator::trace("tuple", move |input: &mut _| {
use $crate::Parser;
$crate::seq_parse_tuple_fields!( ($($elements)*) ; ).map(|t| {
$crate::seq_init_tuple_fields!(
($($elements)*);
(t.0, t.1, t.2, t.3, t.4, t.5, t.6, t.7, t.8, t.9, t.10, t.11, t.12, t.13, t.14, t.15, t.16, t.17, t.18, t.19, t.20);
;
)
}).parse_next(input)
})
};
($($elements: tt)*) => {
$crate::seq!(($($elements)*))
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! seq_parse_struct_fields {
(
$input: ident;
_ : $head_parser: expr, $($fields: tt)*
) => {
let _ = $head_parser.parse_next($input)?;
$crate::seq_parse_struct_fields!($input; $($fields)*)
};
(
$input: ident;
_ : $head_parser: expr
) => {
let _ = $head_parser.parse_next($input)?;
};
(
$input: ident;
$head_field: ident : $head_parser: expr, $($fields: tt)*
) => {
let $head_field = $head_parser.parse_next($input)?;
$crate::seq_parse_struct_fields!($input; $($fields)*)
};
(
$input: ident;
$head_field: ident : $head_parser: expr
) => {
let $head_field = $head_parser.parse_next($input)?;
};
(
$input: expr;
.. $update: expr
) => {};
(
$input: expr;
$(,)?
) => {};
}
#[macro_export]
#[doc(hidden)]
macro_rules! seq_parse_tuple_fields {
(
(_ : $head_parser: expr, $($fields: tt)* );
$($sequenced: tt)*
) => {
$crate::seq_parse_tuple_fields!( ( $($fields)* ) ; $($sequenced)* $head_parser.void(), )
};
(
(_ : $head_parser: expr);
$($sequenced: tt)*
) => {
$crate::seq_parse_tuple_fields!((); $($sequenced)* $head_parser.void(), )
};
(
($head_parser: expr, $($fields: tt)*);
$($sequenced: tt)*
) => {
$crate::seq_parse_tuple_fields!( ( $($fields)* ) ; $($sequenced)* $head_parser, )
};
(
($head_parser: expr);
$($sequenced: tt)*
)=> {
$crate::seq_parse_tuple_fields!((); $($sequenced)* $head_parser, )
};
(
();
$($sequenced: tt)*
) => {
($($sequenced)*)
};
}
#[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, )
};
(
($head_field: ident : $head_parser: expr);
$name: ident;
$($inits: tt)*
) => {
$crate::seq_init_struct_fields!( (); $name ; $($inits)* $head_field: $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)*);
($head_arg: expr, $($args: expr),*);
$($name: ident)?;
$($inits: tt)*
) => {
$crate::seq_init_tuple_fields!( ( $($fields)* ); ( $($args),* ) ; $($name)? ; $($inits)* )
};
(
(_ : $head_parser: expr);
($head_arg: expr, $($args: expr),*);
$($name: ident)?;
$($inits: tt)*
) => {
$crate::seq_init_tuple_fields!((); ( $($args),* ); $($name)? ; $($inits)*)
};
(
($head_parser: expr, $($fields: tt)*);
($head_arg: expr, $($args: expr),*);
$($name: ident)?;
$($inits: tt)*
) => {
$crate::seq_init_tuple_fields!( ( $($fields)* ) ; ( $($args),* ) ; $($name)? ; $($inits)* $head_arg, )
};
(
($head_parser: expr);
($head_arg: expr, $($args: expr),*);
$($name: ident)?;
$($inits: tt)*
) => {
$crate::seq_init_tuple_fields!((); ( $($args),* ); $($name)? ; $($inits)* $head_arg)
};
(
();
($($args: expr),*);
$($name: ident)?;
$($inits: expr),* $(,)?
) => {
$($name)?( $($inits,)* )
};
}

View file

@ -0,0 +1,378 @@
use crate::ascii::dec_uint;
use crate::combinator::dispatch;
use crate::combinator::empty;
use crate::combinator::fail;
use crate::combinator::seq;
use crate::error::ErrMode;
use crate::error::ErrorKind;
use crate::error::ParserError;
use crate::prelude::*;
use crate::token::any;
#[test]
fn dispatch_basics() {
fn escape_seq_char(input: &mut &str) -> PResult<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!(escape_seq_char.parse_peek("b123"), Ok(("123", '\u{8}')));
assert_eq!(
escape_seq_char.parse_peek("error"),
Err(ErrMode::Backtrack(ParserError::from_error_kind(
&"rror",
ErrorKind::Fail
)))
);
assert_eq!(
escape_seq_char.parse_peek(""),
Err(ErrMode::Backtrack(ParserError::from_error_kind(
&"",
ErrorKind::Fail
)))
);
}
#[test]
fn seq_struct_basics() {
#[derive(Debug, PartialEq)]
struct Point {
x: u32,
y: u32,
}
fn parser(input: &mut &str) -> PResult<Point> {
seq! {
Point {
x: dec_uint,
_: ',',
y: dec_uint,
}
}
.parse_next(input)
}
assert_eq!(
parser.parse_peek("123,4 remaining"),
Ok((" remaining", Point { x: 123, y: 4 },)),
);
assert_eq!(
parser.parse_peek("123, remaining"),
Err(ErrMode::Backtrack(ParserError::from_error_kind(
&" remaining",
ErrorKind::Fail
)))
);
assert_eq!(
parser.parse_peek(""),
Err(ErrMode::Backtrack(ParserError::from_error_kind(
&"",
ErrorKind::Fail
)))
);
}
#[test]
fn seq_struct_default_init() {
#[derive(Debug, PartialEq, Default)]
struct Point {
x: u32,
y: u32,
z: u32,
}
fn parser(input: &mut &str) -> PResult<Point> {
seq! {
Point {
x: dec_uint,
_: ',',
y: dec_uint,
..Default::default()
}
}
.parse_next(input)
}
assert_eq!(
parser.parse_peek("123,4 remaining"),
Ok((" remaining", Point { x: 123, y: 4, z: 0 },)),
);
assert_eq!(
parser.parse_peek("123, remaining"),
Err(ErrMode::Backtrack(ParserError::from_error_kind(
&" remaining",
ErrorKind::Fail
)))
);
assert_eq!(
parser.parse_peek(""),
Err(ErrMode::Backtrack(ParserError::from_error_kind(
&"",
ErrorKind::Fail
)))
);
}
#[test]
fn seq_struct_trailing_comma_elided() {
#![allow(dead_code)]
#[derive(Debug, PartialEq)]
struct Point {
x: u32,
y: u32,
}
fn parser(input: &mut &str) -> PResult<Point> {
seq! {
Point {
x: dec_uint,
_: ',',
y: dec_uint,
_: empty,
}
}
.parse_next(input)
}
}
#[test]
fn seq_struct_no_trailing_comma() {
#![allow(dead_code)]
#[derive(Debug, PartialEq)]
struct Point {
x: u32,
y: u32,
}
fn parser(input: &mut &str) -> PResult<Point> {
seq! {
Point {
x: dec_uint,
_: ',',
y: dec_uint
}
}
.parse_next(input)
}
}
#[test]
fn seq_struct_no_trailing_comma_elided() {
#![allow(dead_code)]
#[derive(Debug, PartialEq)]
struct Point {
x: u32,
y: u32,
}
fn parser(input: &mut &str) -> PResult<Point> {
seq! {
Point {
x: dec_uint,
_: ',',
y: dec_uint,
_: empty
}
}
.parse_next(input)
}
}
#[test]
fn seq_tuple_struct_basics() {
#[derive(Debug, PartialEq)]
struct Point(u32, u32);
fn parser(input: &mut &str) -> PResult<Point> {
seq! {
Point(
dec_uint,
_: ',',
dec_uint,
)
}
.parse_next(input)
}
assert_eq!(
parser.parse_peek("123,4 remaining"),
Ok((" remaining", Point(123, 4),)),
);
assert_eq!(
parser.parse_peek("123, remaining"),
Err(ErrMode::Backtrack(ParserError::from_error_kind(
&" remaining",
ErrorKind::Fail
)))
);
assert_eq!(
parser.parse_peek(""),
Err(ErrMode::Backtrack(ParserError::from_error_kind(
&"",
ErrorKind::Fail
)))
);
}
#[test]
fn seq_tuple_struct_trailing_comma_elided() {
#![allow(dead_code)]
#[derive(Debug, PartialEq)]
struct Point(u32, u32);
fn parser(input: &mut &str) -> PResult<Point> {
seq! {
Point(
dec_uint,
_: ',',
dec_uint,
_: empty,
)
}
.parse_next(input)
}
}
#[test]
fn seq_tuple_struct_no_trailing_comma() {
#![allow(dead_code)]
#[derive(Debug, PartialEq)]
struct Point(u32, u32);
fn parser(input: &mut &str) -> PResult<Point> {
seq! {
Point(
dec_uint,
_: ',',
dec_uint
)
}
.parse_next(input)
}
}
#[test]
fn seq_tuple_struct_no_trailing_comma_elided() {
#![allow(dead_code)]
#[derive(Debug, PartialEq)]
struct Point(u32, u32);
fn parser(input: &mut &str) -> PResult<Point> {
seq! {
Point(
dec_uint,
_: ',',
dec_uint,
_: empty
)
}
.parse_next(input)
}
}
#[test]
fn seq_tuple_basics() {
fn parser(input: &mut &str) -> PResult<(u32, u32)> {
seq! {
(
dec_uint,
_: ',',
dec_uint,
)
}
.parse_next(input)
}
assert_eq!(
parser.parse_peek("123,4 remaining"),
Ok((" remaining", (123, 4),)),
);
assert_eq!(
parser.parse_peek("123, remaining"),
Err(ErrMode::Backtrack(ParserError::from_error_kind(
&" remaining",
ErrorKind::Fail
)))
);
assert_eq!(
parser.parse_peek(""),
Err(ErrMode::Backtrack(ParserError::from_error_kind(
&"",
ErrorKind::Fail
)))
);
}
#[test]
fn seq_tuple_trailing_comma_elided() {
#![allow(dead_code)]
fn parser(input: &mut &str) -> PResult<(u32, u32)> {
seq! {
(
dec_uint,
_: ',',
dec_uint,
_: empty,
)
}
.parse_next(input)
}
}
#[test]
fn seq_tuple_no_trailing_comma() {
#![allow(dead_code)]
fn parser(input: &mut &str) -> PResult<(u32, u32)> {
seq! {
(
dec_uint,
_: ',',
dec_uint
)
}
.parse_next(input)
}
}
#[test]
fn seq_tuple_no_trailing_comma_elided() {
#![allow(dead_code)]
fn parser(input: &mut &str) -> PResult<(u32, u32)> {
seq! {
(
dec_uint,
_: ',',
dec_uint,
_: empty
)
}
.parse_next(input)
}
}
#[test]
fn seq_tuple_no_parens() {
#![allow(dead_code)]
fn parser(input: &mut &str) -> PResult<(u32, u32)> {
seq! (
dec_uint,
_: ',',
dec_uint,
)
.parse_next(input)
}
}

1205
third-party/vendor/winnow/src/parser.rs vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,537 @@
macro_rules! impl_partial_eq {
($lhs:ty, $rhs:ty) => {
impl<'a, 'b> PartialEq<$rhs> for $lhs {
#[inline]
fn eq(&self, other: &$rhs) -> bool {
let l = self.as_ref();
let r: &Self = other.as_ref();
PartialEq::eq(l, r)
}
}
impl<'a, 'b> PartialEq<$lhs> for $rhs {
#[inline]
fn eq(&self, other: &$lhs) -> bool {
PartialEq::eq(other, self)
}
}
};
}
macro_rules! impl_partial_ord {
($lhs:ty, $rhs:ty) => {
impl<'a, 'b> PartialOrd<$rhs> for $lhs {
#[inline]
fn partial_cmp(&self, other: &$rhs) -> Option<Ordering> {
let l = self.as_ref();
let r: &Self = other.as_ref();
PartialOrd::partial_cmp(l, r)
}
}
impl<'a, 'b> PartialOrd<$lhs> for $rhs {
#[inline]
fn partial_cmp(&self, other: &$lhs) -> Option<Ordering> {
PartialOrd::partial_cmp(other, self)
}
}
};
}
mod bytes {
use crate::lib::std::{cmp::Ordering, fmt, ops};
use crate::stream::Bytes;
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, "{:0>2x}", byte)?;
}
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, "{:0>2X}", byte)?;
}
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 crate::lib::std::borrow::ToOwned for Bytes {
type Owned = crate::lib::std::vec::Vec<u8>;
#[inline]
fn to_owned(&self) -> Self::Owned {
crate::lib::std::vec::Vec::from(self.as_bytes())
}
}
#[cfg(feature = "alloc")]
impl crate::lib::std::borrow::Borrow<Bytes> for crate::lib::std::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(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;
#[test]
fn test_debug() {
assert_eq!(
"000000206674797069736F6D0000020069736F6D69736F32617663316D70",
format!(
"{:?}",
Bytes::new(b"\0\0\0 ftypisom\0\0\x02\0isomiso2avc1mp")
),
);
}
#[test]
fn test_pretty_debug() {
// Output can change from run-to-run
format!(
"{:#?}",
Bytes::new(b"\0\0\0 ftypisom\0\0\x02\0isomiso2avc1mp")
);
}
#[test]
fn test_sliced() {
// Output can change from run-to-run
let total = Bytes::new(b"12345678901234567890");
format!("{:#?}", total);
format!("{:#?}", &total[1..]);
format!("{:#?}", &total[10..]);
}
}
}
mod bstr {
use crate::lib::std::{cmp::Ordering, fmt, ops};
use crate::stream::BStr;
#[cfg(feature = "alloc")]
impl fmt::Display for BStr {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
crate::lib::std::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 crate::lib::std::borrow::ToOwned for BStr {
type Owned = crate::lib::std::vec::Vec<u8>;
#[inline]
fn to_owned(&self) -> Self::Owned {
crate::lib::std::vec::Vec::from(self.as_bytes())
}
}
#[cfg(feature = "alloc")]
impl crate::lib::std::borrow::Borrow<BStr> for crate::lib::std::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(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")), "<EFBFBD>(<28><>");
}
}
#[cfg(all(test, feature = "std"))]
mod debug {
use crate::stream::BStr;
#[test]
fn test_debug() {
assert_eq!(&format!("{:?}", BStr::new(b"abc")), "\"abc\"");
assert_eq!(
"\"\\0\\0\\0 ftypisom\\0\\0\\u{2}\\0isomiso2avc1mp\"",
format!(
"{:?}",
BStr::new(b"\0\0\0 ftypisom\0\0\x02\0isomiso2avc1mp")
),
);
}
#[test]
fn test_pretty_debug() {
assert_eq!(&format!("{:#?}", BStr::new(b"abc")), "abc");
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,228 @@
#[cfg(feature = "std")]
use proptest::prelude::*;
use crate::error::ErrMode::Backtrack;
use crate::error::{ErrorKind, InputError};
use crate::token::tag;
use crate::{
combinator::{separated, separated_pair},
PResult, Parser,
};
use super::*;
#[cfg(feature = "std")]
#[test]
fn test_fxhashmap_compiles() {
let input = "a=b";
fn pair(i: &mut &str) -> PResult<(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]
#[cfg(feature = "alloc")]
fn test_bit_stream_empty() {
let i = (&b""[..], 0);
let actual = i.iter_offsets().collect::<crate::lib::std::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_input, actual_slice) = i.peek_slice(0);
assert_eq!(actual_input, (&b""[..], 0));
assert_eq!(actual_slice, (&b""[..], 0, 0));
}
#[test]
#[cfg(feature = "alloc")]
fn test_bit_offset_empty() {
let i = (&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 = (&bytes[start_byte..], start_bit);
let mut curr_i = i;
let mut curr_offset = 0;
while let Some((next_i, _token)) = curr_i.peek_token() {
let to_offset = curr_i.offset_from(&i);
assert_eq!(curr_offset, to_offset);
let (slice_i, _) = i.peek_slice(curr_offset);
assert_eq!(curr_i, slice_i);
let at_offset = i.offset_at(curr_offset).unwrap();
assert_eq!(curr_offset, at_offset);
let eof_offset = curr_i.eof_offset();
let (next_eof_i, eof_slice) = curr_i.peek_slice(eof_offset);
assert_eq!(next_eof_i, (&b""[..], 0));
let eof_slice_i = (eof_slice.0, eof_slice.1);
assert_eq!(eof_slice_i, curr_i);
curr_offset += 1;
curr_i = next_i;
}
assert_eq!(i.eof_offset(), curr_offset);
}
#[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_tag_support_char() {
assert_eq!(
tag::<_, _, InputError<_>>('π').parse_peek("π"),
Ok(("", "π"))
);
assert_eq!(
tag::<_, _, InputError<_>>('π').parse_peek("π3.14"),
Ok(("3.14", "π"))
);
assert_eq!(
tag::<_, _, InputError<_>>("π").parse_peek("π3.14"),
Ok(("3.14", "π"))
);
assert_eq!(
tag::<_, _, InputError<_>>('-').parse_peek("π"),
Err(Backtrack(InputError::new("π", ErrorKind::Tag)))
);
assert_eq!(
tag::<_, Partial<&[u8]>, InputError<_>>('π').parse_peek(Partial::new(b"\xCF\x80")),
Ok((Partial::new(Default::default()), "π".as_bytes()))
);
assert_eq!(
tag::<_, &[u8], InputError<_>>('π').parse_peek(b"\xCF\x80"),
Ok((Default::default(), "π".as_bytes()))
);
assert_eq!(
tag::<_, Partial<&[u8]>, InputError<_>>('π').parse_peek(Partial::new(b"\xCF\x803.14")),
Ok((Partial::new(&b"3.14"[..]), "π".as_bytes()))
);
assert_eq!(
tag::<_, &[u8], InputError<_>>('π').parse_peek(b"\xCF\x80"),
Ok((Default::default(), "π".as_bytes()))
);
assert_eq!(
tag::<_, &[u8], InputError<_>>('π').parse_peek(b"\xCF\x803.14"),
Ok((&b"3.14"[..], "π".as_bytes()))
);
assert_eq!(
tag::<_, &[u8], InputError<_>>(AsciiCaseless('a')).parse_peek(b"ABCxyz"),
Ok((&b"BCxyz"[..], &b"A"[..]))
);
assert_eq!(
tag::<_, &[u8], InputError<_>>('a').parse_peek(b"ABCxyz"),
Err(Backtrack(InputError::new(&b"ABCxyz"[..], ErrorKind::Tag)))
);
assert_eq!(
tag::<_, &[u8], InputError<_>>(AsciiCaseless('π')).parse_peek(b"\xCF\x803.14"),
Ok((&b"3.14"[..], "π".as_bytes()))
);
assert_eq!(
tag::<_, _, InputError<_>>(AsciiCaseless('🧑')).parse_peek("🧑你好"),
Ok(("你好", "🧑"))
);
let mut buffer = [0; 4];
let input = '\u{241b}'.encode_utf8(&mut buffer);
assert_eq!(
tag::<_, &[u8], InputError<_>>(AsciiCaseless('␛')).parse_peek(input.as_bytes()),
Ok((&b""[..], [226, 144, 155].as_slice()))
);
assert_eq!(
tag::<_, &[u8], InputError<_>>('-').parse_peek(b"\xCF\x80"),
Err(Backtrack(InputError::new(&b"\xCF\x80"[..], ErrorKind::Tag)))
);
}

1243
third-party/vendor/winnow/src/token/mod.rs vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,807 @@
use super::*;
#[cfg(feature = "std")]
use proptest::prelude::*;
use crate::ascii::Caseless;
use crate::combinator::delimited;
use crate::error::ErrMode;
use crate::error::ErrorKind;
use crate::error::InputError;
use crate::error::Needed;
use crate::stream::AsChar;
use crate::token::tag;
use crate::unpeek;
use crate::IResult;
use crate::Parser;
use crate::Partial;
#[test]
fn complete_take_while_m_n_utf8_all_matching() {
let result: IResult<&str, &str> =
take_while(1..=4, |c: char| c.is_alphabetic()).parse_peek("øn");
assert_eq!(result, Ok(("", "øn")));
}
#[test]
fn complete_take_while_m_n_utf8_all_matching_substring() {
let result: IResult<&str, &str> = take_while(1, |c: char| c.is_alphabetic()).parse_peek("øn");
assert_eq!(result, Ok(("n", "ø")));
}
#[cfg(feature = "std")]
fn model_complete_take_while_m_n(
m: usize,
n: usize,
valid: usize,
input: &str,
) -> IResult<&str, &str> {
if n < m {
Err(crate::error::ErrMode::from_error_kind(
&input,
crate::error::ErrorKind::Slice,
))
} else if m <= valid {
let offset = n.min(valid);
Ok((&input[offset..], &input[0..offset]))
} else {
Err(crate::error::ErrMode::from_error_kind(
&input,
crate::error::ErrorKind::Slice,
))
}
}
#[cfg(feature = "std")]
proptest! {
#[test]
#[cfg_attr(miri, ignore)] // See https://github.com/AltSysrq/proptest/issues/253
fn complete_take_while_m_n_bounds(m in 0..20usize, n in 0..20usize, valid in 0..20usize, invalid in 0..20usize) {
let input = format!("{:a<valid$}{:b<invalid$}", "", "", valid=valid, invalid=invalid);
let expected = model_complete_take_while_m_n(m, n, valid, &input);
if m <= n {
let actual = take_while(m..=n, |c: char| c == 'a').parse_peek(input.as_str());
assert_eq!(expected, actual);
}
}
}
#[test]
fn complete_take_until() {
fn take_until_5_10(i: &str) -> IResult<&str, &str> {
take_until(5..=8, "end").parse_peek(i)
}
assert_eq!(
take_until_5_10("end"),
Err(ErrMode::Backtrack(error_position!(
&"end",
ErrorKind::Slice
)))
);
assert_eq!(
take_until_5_10("1234end"),
Err(ErrMode::Backtrack(error_position!(
&"1234end",
ErrorKind::Slice
)))
);
assert_eq!(take_until_5_10("12345end"), Ok(("end", "12345")));
assert_eq!(take_until_5_10("123456end"), Ok(("end", "123456")));
assert_eq!(take_until_5_10("12345678end"), Ok(("end", "12345678")));
assert_eq!(
take_until_5_10("123456789end"),
Err(ErrMode::Backtrack(error_position!(
&"123456789end",
ErrorKind::Slice
)))
);
}
#[test]
fn complete_tag_case_insensitive() {
fn caseless_bytes(i: &[u8]) -> IResult<&[u8], &[u8]> {
tag(Caseless("ABcd")).parse_peek(i)
}
assert_eq!(
caseless_bytes(&b"aBCdefgh"[..]),
Ok((&b"efgh"[..], &b"aBCd"[..]))
);
assert_eq!(
caseless_bytes(&b"abcdefgh"[..]),
Ok((&b"efgh"[..], &b"abcd"[..]))
);
assert_eq!(
caseless_bytes(&b"ABCDefgh"[..]),
Ok((&b"efgh"[..], &b"ABCD"[..]))
);
assert_eq!(
caseless_bytes(&b"ab"[..]),
Err(ErrMode::Backtrack(error_position!(
&&b"ab"[..],
ErrorKind::Tag
)))
);
assert_eq!(
caseless_bytes(&b"Hello"[..]),
Err(ErrMode::Backtrack(error_position!(
&&b"Hello"[..],
ErrorKind::Tag
)))
);
assert_eq!(
caseless_bytes(&b"Hel"[..]),
Err(ErrMode::Backtrack(error_position!(
&&b"Hel"[..],
ErrorKind::Tag
)))
);
fn caseless_str(i: &str) -> IResult<&str, &str> {
tag(Caseless("ABcd")).parse_peek(i)
}
assert_eq!(caseless_str("aBCdefgh"), Ok(("efgh", "aBCd")));
assert_eq!(caseless_str("abcdefgh"), Ok(("efgh", "abcd")));
assert_eq!(caseless_str("ABCDefgh"), Ok(("efgh", "ABCD")));
assert_eq!(
caseless_str("ab"),
Err(ErrMode::Backtrack(error_position!(&"ab", ErrorKind::Tag)))
);
assert_eq!(
caseless_str("Hello"),
Err(ErrMode::Backtrack(error_position!(
&"Hello",
ErrorKind::Tag
)))
);
assert_eq!(
caseless_str("Hel"),
Err(ErrMode::Backtrack(error_position!(&"Hel", ErrorKind::Tag)))
);
fn matches_kelvin(i: &str) -> IResult<&str, &str> {
tag(Caseless("k")).parse_peek(i)
}
assert_eq!(
matches_kelvin(""),
Err(ErrMode::Backtrack(error_position!(&"", ErrorKind::Tag)))
);
fn is_kelvin(i: &str) -> IResult<&str, &str> {
tag(Caseless("")).parse_peek(i)
}
assert_eq!(
is_kelvin("k"),
Err(ErrMode::Backtrack(error_position!(&"k", ErrorKind::Tag)))
);
}
#[test]
fn complete_tag_fixed_size_array() {
fn test(i: &[u8]) -> IResult<&[u8], &[u8]> {
tag([0x42]).parse_peek(i)
}
fn test2(i: &[u8]) -> IResult<&[u8], &[u8]> {
tag(&[0x42]).parse_peek(i)
}
let input = &[0x42, 0x00][..];
assert_eq!(test(input), Ok((&b"\x00"[..], &b"\x42"[..])));
assert_eq!(test2(input), Ok((&b"\x00"[..], &b"\x42"[..])));
}
#[test]
fn complete_tag_char() {
fn test(i: &[u8]) -> IResult<&[u8], &[u8]> {
tag('B').parse_peek(i)
}
assert_eq!(test(&[0x42, 0x00][..]), Ok((&b"\x00"[..], &b"\x42"[..])));
assert_eq!(
test(&[b'A', b'\0'][..]),
Err(ErrMode::Backtrack(error_position!(
&&b"A\0"[..],
ErrorKind::Tag
)))
);
}
#[test]
fn complete_tag_byte() {
fn test(i: &[u8]) -> IResult<&[u8], &[u8]> {
tag(b'B').parse_peek(i)
}
assert_eq!(test(&[0x42, 0x00][..]), Ok((&b"\x00"[..], &b"\x42"[..])));
assert_eq!(
test(&[b'A', b'\0'][..]),
Err(ErrMode::Backtrack(error_position!(
&&b"A\0"[..],
ErrorKind::Tag
)))
);
}
#[test]
fn partial_any_str() {
use super::any;
assert_eq!(
any::<_, InputError<Partial<&str>>>.parse_peek(Partial::new("Ә")),
Ok((Partial::new(""), 'Ә'))
);
}
#[test]
fn partial_one_of_test() {
fn f(i: Partial<&[u8]>) -> IResult<Partial<&[u8]>, u8> {
one_of(['a', 'b']).parse_peek(i)
}
let a = &b"abcd"[..];
assert_eq!(f(Partial::new(a)), Ok((Partial::new(&b"bcd"[..]), b'a')));
let b = &b"cde"[..];
assert_eq!(
f(Partial::new(b)),
Err(ErrMode::Backtrack(error_position!(
&Partial::new(b),
ErrorKind::Verify
)))
);
fn utf8(i: Partial<&str>) -> IResult<Partial<&str>, char> {
one_of(['+', '\u{FF0B}']).parse_peek(i)
}
assert!(utf8(Partial::new("+")).is_ok());
assert!(utf8(Partial::new("\u{FF0B}")).is_ok());
}
#[test]
fn char_byteslice() {
fn f(i: Partial<&[u8]>) -> IResult<Partial<&[u8]>, u8> {
'c'.parse_peek(i)
}
let a = &b"abcd"[..];
assert_eq!(
f(Partial::new(a)),
Err(ErrMode::Backtrack(error_position!(
&Partial::new(a),
ErrorKind::Verify
)))
);
let b = &b"cde"[..];
assert_eq!(f(Partial::new(b)), Ok((Partial::new(&b"de"[..]), b'c')));
}
#[test]
fn char_str() {
fn f(i: Partial<&str>) -> IResult<Partial<&str>, char> {
'c'.parse_peek(i)
}
let a = "abcd";
assert_eq!(
f(Partial::new(a)),
Err(ErrMode::Backtrack(error_position!(
&Partial::new(a),
ErrorKind::Verify
)))
);
let b = "cde";
assert_eq!(f(Partial::new(b)), Ok((Partial::new("de"), 'c')));
}
#[test]
fn partial_none_of_test() {
fn f(i: Partial<&[u8]>) -> IResult<Partial<&[u8]>, u8> {
none_of(['a', 'b']).parse_peek(i)
}
let a = &b"abcd"[..];
assert_eq!(
f(Partial::new(a)),
Err(ErrMode::Backtrack(error_position!(
&Partial::new(a),
ErrorKind::Verify
)))
);
let b = &b"cde"[..];
assert_eq!(f(Partial::new(b)), Ok((Partial::new(&b"de"[..]), b'c')));
}
#[test]
fn partial_is_a() {
fn a_or_b(i: Partial<&[u8]>) -> IResult<Partial<&[u8]>, &[u8]> {
take_while(1.., ['a', 'b']).parse_peek(i)
}
let a = Partial::new(&b"abcd"[..]);
assert_eq!(a_or_b(a), Ok((Partial::new(&b"cd"[..]), &b"ab"[..])));
let b = Partial::new(&b"bcde"[..]);
assert_eq!(a_or_b(b), Ok((Partial::new(&b"cde"[..]), &b"b"[..])));
let c = Partial::new(&b"cdef"[..]);
assert_eq!(
a_or_b(c),
Err(ErrMode::Backtrack(error_position!(&c, ErrorKind::Slice)))
);
let d = Partial::new(&b"bacdef"[..]);
assert_eq!(a_or_b(d), Ok((Partial::new(&b"cdef"[..]), &b"ba"[..])));
}
#[test]
fn partial_is_not() {
fn a_or_b(i: Partial<&[u8]>) -> IResult<Partial<&[u8]>, &[u8]> {
take_till(1.., ['a', 'b']).parse_peek(i)
}
let a = Partial::new(&b"cdab"[..]);
assert_eq!(a_or_b(a), Ok((Partial::new(&b"ab"[..]), &b"cd"[..])));
let b = Partial::new(&b"cbde"[..]);
assert_eq!(a_or_b(b), Ok((Partial::new(&b"bde"[..]), &b"c"[..])));
let c = Partial::new(&b"abab"[..]);
assert_eq!(
a_or_b(c),
Err(ErrMode::Backtrack(error_position!(&c, ErrorKind::Slice)))
);
let d = Partial::new(&b"cdefba"[..]);
assert_eq!(a_or_b(d), Ok((Partial::new(&b"ba"[..]), &b"cdef"[..])));
let e = Partial::new(&b"e"[..]);
assert_eq!(a_or_b(e), Err(ErrMode::Incomplete(Needed::new(1))));
}
#[test]
fn partial_take_until_incomplete() {
fn y(i: Partial<&[u8]>) -> IResult<Partial<&[u8]>, &[u8]> {
take_until(0.., "end").parse_peek(i)
}
assert_eq!(
y(Partial::new(&b"nd"[..])),
Err(ErrMode::Incomplete(Needed::Unknown))
);
assert_eq!(
y(Partial::new(&b"123"[..])),
Err(ErrMode::Incomplete(Needed::Unknown))
);
assert_eq!(
y(Partial::new(&b"123en"[..])),
Err(ErrMode::Incomplete(Needed::Unknown))
);
}
#[test]
fn partial_take_until_incomplete_s() {
fn ys(i: Partial<&str>) -> IResult<Partial<&str>, &str> {
take_until(0.., "end").parse_peek(i)
}
assert_eq!(
ys(Partial::new("123en")),
Err(ErrMode::Incomplete(Needed::Unknown))
);
}
#[test]
fn partial_recognize() {
use crate::ascii::{
alpha1 as alpha, alphanumeric1 as alphanumeric, digit1 as digit, hex_digit1 as hex_digit,
multispace1 as multispace, oct_digit1 as oct_digit, space1 as space,
};
fn x(i: Partial<&[u8]>) -> IResult<Partial<&[u8]>, &[u8]> {
delimited("<!--", take(5_usize), "-->")
.recognize()
.parse_peek(i)
}
let r = x(Partial::new(&b"<!-- abc --> aaa"[..]));
assert_eq!(r, Ok((Partial::new(&b" aaa"[..]), &b"<!-- abc -->"[..])));
let semicolon = &b";"[..];
fn ya(i: Partial<&[u8]>) -> IResult<Partial<&[u8]>, &[u8]> {
alpha.recognize().parse_peek(i)
}
let ra = ya(Partial::new(&b"abc;"[..]));
assert_eq!(ra, Ok((Partial::new(semicolon), &b"abc"[..])));
fn yd(i: Partial<&[u8]>) -> IResult<Partial<&[u8]>, &[u8]> {
digit.recognize().parse_peek(i)
}
let rd = yd(Partial::new(&b"123;"[..]));
assert_eq!(rd, Ok((Partial::new(semicolon), &b"123"[..])));
fn yhd(i: Partial<&[u8]>) -> IResult<Partial<&[u8]>, &[u8]> {
hex_digit.recognize().parse_peek(i)
}
let rhd = yhd(Partial::new(&b"123abcDEF;"[..]));
assert_eq!(rhd, Ok((Partial::new(semicolon), &b"123abcDEF"[..])));
fn yod(i: Partial<&[u8]>) -> IResult<Partial<&[u8]>, &[u8]> {
oct_digit.recognize().parse_peek(i)
}
let rod = yod(Partial::new(&b"1234567;"[..]));
assert_eq!(rod, Ok((Partial::new(semicolon), &b"1234567"[..])));
fn yan(i: Partial<&[u8]>) -> IResult<Partial<&[u8]>, &[u8]> {
alphanumeric.recognize().parse_peek(i)
}
let ran = yan(Partial::new(&b"123abc;"[..]));
assert_eq!(ran, Ok((Partial::new(semicolon), &b"123abc"[..])));
fn ys(i: Partial<&[u8]>) -> IResult<Partial<&[u8]>, &[u8]> {
space.recognize().parse_peek(i)
}
let rs = ys(Partial::new(&b" \t;"[..]));
assert_eq!(rs, Ok((Partial::new(semicolon), &b" \t"[..])));
fn yms(i: Partial<&[u8]>) -> IResult<Partial<&[u8]>, &[u8]> {
multispace.recognize().parse_peek(i)
}
let rms = yms(Partial::new(&b" \t\r\n;"[..]));
assert_eq!(rms, Ok((Partial::new(semicolon), &b" \t\r\n"[..])));
}
#[test]
fn partial_take_while0() {
fn f(i: Partial<&[u8]>) -> IResult<Partial<&[u8]>, &[u8]> {
take_while(0.., AsChar::is_alpha).parse_peek(i)
}
let a = &b""[..];
let b = &b"abcd"[..];
let c = &b"abcd123"[..];
let d = &b"123"[..];
assert_eq!(f(Partial::new(a)), Err(ErrMode::Incomplete(Needed::new(1))));
assert_eq!(f(Partial::new(b)), Err(ErrMode::Incomplete(Needed::new(1))));
assert_eq!(f(Partial::new(c)), Ok((Partial::new(d), b)));
assert_eq!(f(Partial::new(d)), Ok((Partial::new(d), a)));
}
#[test]
fn partial_take_while1() {
fn f(i: Partial<&[u8]>) -> IResult<Partial<&[u8]>, &[u8]> {
take_while(1.., AsChar::is_alpha).parse_peek(i)
}
let a = &b""[..];
let b = &b"abcd"[..];
let c = &b"abcd123"[..];
let d = &b"123"[..];
assert_eq!(f(Partial::new(a)), Err(ErrMode::Incomplete(Needed::new(1))));
assert_eq!(f(Partial::new(b)), Err(ErrMode::Incomplete(Needed::new(1))));
assert_eq!(f(Partial::new(c)), Ok((Partial::new(&b"123"[..]), b)));
assert_eq!(
f(Partial::new(d)),
Err(ErrMode::Backtrack(error_position!(
&Partial::new(d),
ErrorKind::Slice
)))
);
}
#[test]
fn partial_take_while_m_n() {
fn x(i: Partial<&[u8]>) -> IResult<Partial<&[u8]>, &[u8]> {
take_while(2..=4, AsChar::is_alpha).parse_peek(i)
}
let a = &b""[..];
let b = &b"a"[..];
let c = &b"abc"[..];
let d = &b"abc123"[..];
let e = &b"abcde"[..];
let f = &b"123"[..];
assert_eq!(x(Partial::new(a)), Err(ErrMode::Incomplete(Needed::new(2))));
assert_eq!(x(Partial::new(b)), Err(ErrMode::Incomplete(Needed::new(1))));
assert_eq!(x(Partial::new(c)), Err(ErrMode::Incomplete(Needed::new(1))));
assert_eq!(x(Partial::new(d)), Ok((Partial::new(&b"123"[..]), c)));
assert_eq!(
x(Partial::new(e)),
Ok((Partial::new(&b"e"[..]), &b"abcd"[..]))
);
assert_eq!(
x(Partial::new(f)),
Err(ErrMode::Backtrack(error_position!(
&Partial::new(f),
ErrorKind::Slice
)))
);
}
#[test]
fn partial_take_till0() {
fn f(i: Partial<&[u8]>) -> IResult<Partial<&[u8]>, &[u8]> {
take_till(0.., AsChar::is_alpha).parse_peek(i)
}
let a = &b""[..];
let b = &b"abcd"[..];
let c = &b"123abcd"[..];
let d = &b"123"[..];
assert_eq!(f(Partial::new(a)), Err(ErrMode::Incomplete(Needed::new(1))));
assert_eq!(
f(Partial::new(b)),
Ok((Partial::new(&b"abcd"[..]), &b""[..]))
);
assert_eq!(
f(Partial::new(c)),
Ok((Partial::new(&b"abcd"[..]), &b"123"[..]))
);
assert_eq!(f(Partial::new(d)), Err(ErrMode::Incomplete(Needed::new(1))));
}
#[test]
fn partial_take_till1() {
fn f(i: Partial<&[u8]>) -> IResult<Partial<&[u8]>, &[u8]> {
take_till(1.., AsChar::is_alpha).parse_peek(i)
}
let a = &b""[..];
let b = &b"abcd"[..];
let c = &b"123abcd"[..];
let d = &b"123"[..];
assert_eq!(f(Partial::new(a)), Err(ErrMode::Incomplete(Needed::new(1))));
assert_eq!(
f(Partial::new(b)),
Err(ErrMode::Backtrack(error_position!(
&Partial::new(b),
ErrorKind::Slice
)))
);
assert_eq!(
f(Partial::new(c)),
Ok((Partial::new(&b"abcd"[..]), &b"123"[..]))
);
assert_eq!(f(Partial::new(d)), Err(ErrMode::Incomplete(Needed::new(1))));
}
#[test]
fn partial_take_while_utf8() {
fn f(i: Partial<&str>) -> IResult<Partial<&str>, &str> {
take_while(0.., |c| c != '點').parse_peek(i)
}
assert_eq!(
f(Partial::new("")),
Err(ErrMode::Incomplete(Needed::new(1)))
);
assert_eq!(
f(Partial::new("abcd")),
Err(ErrMode::Incomplete(Needed::new(1)))
);
assert_eq!(f(Partial::new("abcd點")), Ok((Partial::new(""), "abcd")));
assert_eq!(
f(Partial::new("abcd點a")),
Ok((Partial::new("點a"), "abcd"))
);
fn g(i: Partial<&str>) -> IResult<Partial<&str>, &str> {
take_while(0.., |c| c == '點').parse_peek(i)
}
assert_eq!(
g(Partial::new("")),
Err(ErrMode::Incomplete(Needed::new(1)))
);
assert_eq!(g(Partial::new("點abcd")), Ok((Partial::new("abcd"), "")));
assert_eq!(
g(Partial::new("點點點a")),
Ok((Partial::new("a"), "點點點"))
);
}
#[test]
fn partial_take_till0_utf8() {
fn f(i: Partial<&str>) -> IResult<Partial<&str>, &str> {
take_till(0.., |c| c == '點').parse_peek(i)
}
assert_eq!(
f(Partial::new("")),
Err(ErrMode::Incomplete(Needed::new(1)))
);
assert_eq!(
f(Partial::new("abcd")),
Err(ErrMode::Incomplete(Needed::new(1)))
);
assert_eq!(f(Partial::new("abcd點")), Ok((Partial::new(""), "abcd")));
assert_eq!(
f(Partial::new("abcd點a")),
Ok((Partial::new("點a"), "abcd"))
);
fn g(i: Partial<&str>) -> IResult<Partial<&str>, &str> {
take_till(0.., |c| c != '點').parse_peek(i)
}
assert_eq!(
g(Partial::new("")),
Err(ErrMode::Incomplete(Needed::new(1)))
);
assert_eq!(g(Partial::new("點abcd")), Ok((Partial::new("abcd"), "")));
assert_eq!(
g(Partial::new("點點點a")),
Ok((Partial::new("a"), "點點點"))
);
}
#[test]
fn partial_take_utf8() {
fn f(i: Partial<&str>) -> IResult<Partial<&str>, &str> {
take(3_usize).parse_peek(i)
}
assert_eq!(
f(Partial::new("")),
Err(ErrMode::Incomplete(Needed::Unknown))
);
assert_eq!(
f(Partial::new("ab")),
Err(ErrMode::Incomplete(Needed::Unknown))
);
assert_eq!(
f(Partial::new("")),
Err(ErrMode::Incomplete(Needed::Unknown))
);
assert_eq!(f(Partial::new("ab點cd")), Ok((Partial::new("cd"), "ab點")));
assert_eq!(f(Partial::new("a點bcd")), Ok((Partial::new("cd"), "a點b")));
assert_eq!(f(Partial::new("a點b")), Ok((Partial::new(""), "a點b")));
fn g(i: Partial<&str>) -> IResult<Partial<&str>, &str> {
take_while(0.., |c| c == '點').parse_peek(i)
}
assert_eq!(
g(Partial::new("")),
Err(ErrMode::Incomplete(Needed::new(1)))
);
assert_eq!(g(Partial::new("點abcd")), Ok((Partial::new("abcd"), "")));
assert_eq!(
g(Partial::new("點點點a")),
Ok((Partial::new("a"), "點點點"))
);
}
#[test]
fn partial_take_while_m_n_utf8_fixed() {
fn parser(i: Partial<&str>) -> IResult<Partial<&str>, &str> {
take_while(1, |c| c == 'A' || c == '😃').parse_peek(i)
}
assert_eq!(parser(Partial::new("A!")), Ok((Partial::new("!"), "A")));
assert_eq!(parser(Partial::new("😃!")), Ok((Partial::new("!"), "😃")));
}
#[test]
fn partial_take_while_m_n_utf8_range() {
fn parser(i: Partial<&str>) -> IResult<Partial<&str>, &str> {
take_while(1..=2, |c| c == 'A' || c == '😃').parse_peek(i)
}
assert_eq!(parser(Partial::new("A!")), Ok((Partial::new("!"), "A")));
assert_eq!(parser(Partial::new("😃!")), Ok((Partial::new("!"), "😃")));
}
#[test]
fn partial_take_while_m_n_utf8_full_match_fixed() {
fn parser(i: Partial<&str>) -> IResult<Partial<&str>, &str> {
take_while(1, |c: char| c.is_alphabetic()).parse_peek(i)
}
assert_eq!(parser(Partial::new("øn")), Ok((Partial::new("n"), "ø")));
}
#[test]
fn partial_take_while_m_n_utf8_full_match_range() {
fn parser(i: Partial<&str>) -> IResult<Partial<&str>, &str> {
take_while(1..=2, |c: char| c.is_alphabetic()).parse_peek(i)
}
assert_eq!(parser(Partial::new("øn")), Ok((Partial::new(""), "øn")));
}
#[test]
#[cfg(feature = "std")]
fn partial_recognize_take_while0() {
fn x(i: Partial<&[u8]>) -> IResult<Partial<&[u8]>, &[u8]> {
take_while(0.., AsChar::is_alphanum).parse_peek(i)
}
fn y(i: Partial<&[u8]>) -> IResult<Partial<&[u8]>, &[u8]> {
unpeek(x).recognize().parse_peek(i)
}
assert_eq!(
x(Partial::new(&b"ab."[..])),
Ok((Partial::new(&b"."[..]), &b"ab"[..]))
);
assert_eq!(
y(Partial::new(&b"ab."[..])),
Ok((Partial::new(&b"."[..]), &b"ab"[..]))
);
}
#[test]
fn partial_tag_case_insensitive() {
fn caseless_bytes(i: Partial<&[u8]>) -> IResult<Partial<&[u8]>, &[u8]> {
tag(Caseless("ABcd")).parse_peek(i)
}
assert_eq!(
caseless_bytes(Partial::new(&b"aBCdefgh"[..])),
Ok((Partial::new(&b"efgh"[..]), &b"aBCd"[..]))
);
assert_eq!(
caseless_bytes(Partial::new(&b"abcdefgh"[..])),
Ok((Partial::new(&b"efgh"[..]), &b"abcd"[..]))
);
assert_eq!(
caseless_bytes(Partial::new(&b"ABCDefgh"[..])),
Ok((Partial::new(&b"efgh"[..]), &b"ABCD"[..]))
);
assert_eq!(
caseless_bytes(Partial::new(&b"ab"[..])),
Err(ErrMode::Incomplete(Needed::new(2)))
);
assert_eq!(
caseless_bytes(Partial::new(&b"Hello"[..])),
Err(ErrMode::Backtrack(error_position!(
&Partial::new(&b"Hello"[..]),
ErrorKind::Tag
)))
);
assert_eq!(
caseless_bytes(Partial::new(&b"Hel"[..])),
Err(ErrMode::Backtrack(error_position!(
&Partial::new(&b"Hel"[..]),
ErrorKind::Tag
)))
);
fn caseless_str(i: Partial<&str>) -> IResult<Partial<&str>, &str> {
tag(Caseless("ABcd")).parse_peek(i)
}
assert_eq!(
caseless_str(Partial::new("aBCdefgh")),
Ok((Partial::new("efgh"), "aBCd"))
);
assert_eq!(
caseless_str(Partial::new("abcdefgh")),
Ok((Partial::new("efgh"), "abcd"))
);
assert_eq!(
caseless_str(Partial::new("ABCDefgh")),
Ok((Partial::new("efgh"), "ABCD"))
);
assert_eq!(
caseless_str(Partial::new("ab")),
Err(ErrMode::Incomplete(Needed::new(2)))
);
assert_eq!(
caseless_str(Partial::new("Hello")),
Err(ErrMode::Backtrack(error_position!(
&Partial::new("Hello"),
ErrorKind::Tag
)))
);
assert_eq!(
caseless_str(Partial::new("Hel")),
Err(ErrMode::Backtrack(error_position!(
&Partial::new("Hel"),
ErrorKind::Tag
)))
);
}
#[test]
fn partial_tag_fixed_size_array() {
fn test(i: Partial<&[u8]>) -> IResult<Partial<&[u8]>, &[u8]> {
tag([0x42]).parse_peek(i)
}
fn test2(i: Partial<&[u8]>) -> IResult<Partial<&[u8]>, &[u8]> {
tag(&[0x42]).parse_peek(i)
}
let input = Partial::new(&[0x42, 0x00][..]);
assert_eq!(test(input), Ok((Partial::new(&b"\x00"[..]), &b"\x42"[..])));
assert_eq!(test2(input), Ok((Partial::new(&b"\x00"[..]), &b"\x42"[..])));
}

11
third-party/vendor/winnow/src/trace.rs vendored Normal file
View file

@ -0,0 +1,11 @@
//! Deprecated, replaced with [`winnow::combinator`][crate::combinator]
/// Deprecated, replaced with [`winnow::combinator::trace`][crate::combinator::trace]
#[deprecated(since = "0.5.35", note = "Replaced with `winnow::combinator::trace`")]
#[inline(always)]
pub fn trace<I: crate::stream::Stream, O, E>(
name: impl crate::lib::std::fmt::Display,
parser: impl crate::Parser<I, O, E>,
) -> impl crate::Parser<I, O, E> {
crate::combinator::trace(name, parser)
}