feat: add display UI with splash screen, dashboard, and RSSI sparkline
Implement full SSD1306 OLED display support: - Splash screen on boot (1.5s) with project name, version, board - Dashboard showing radio state, config, counters, RSSI/SNR - RSSI history sparkline (64-sample ring buffer, 2px-wide bars) - DisplayOn/DisplayOff host commands routed via dedicated channel - Pure render functions against generic DrawTarget (no hardware coupling) - Display on/off control bypasses radio task entirely Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
17624 files changed
+4564928
-38
No files matched your search
+33
@@ -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);
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
use winnow::prelude::*;
|
||||
|
||||
mod parser;
|
||||
mod parser_ast;
|
||||
mod parser_lexer;
|
||||
#[cfg(test)]
|
||||
mod test_parser;
|
||||
#[cfg(test)]
|
||||
mod test_parser_ast;
|
||||
#[cfg(test)]
|
||||
mod test_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::tokens.parse(input)?;
|
||||
println!(" {tokens:#?}");
|
||||
let tokens = parser_lexer::Tokens::new(&tokens);
|
||||
let result = parser_lexer::expr.parse(tokens).unwrap();
|
||||
println!(" {:#?}={}", result, result.eval());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Args {
|
||||
input: Option<String>,
|
||||
implementation: Impl,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
enum Impl {
|
||||
#[default]
|
||||
Eval,
|
||||
Ast,
|
||||
Lexer,
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use winnow::prelude::*;
|
||||
use winnow::Result;
|
||||
use winnow::{
|
||||
ascii::{digit1 as digits, multispace0 as multispaces},
|
||||
combinator::alt,
|
||||
combinator::delimited,
|
||||
combinator::repeat,
|
||||
token::one_of,
|
||||
};
|
||||
|
||||
// Parser definition
|
||||
|
||||
pub(crate) fn expr(i: &mut &str) -> Result<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
|
||||
pub(crate) fn term(i: &mut &str) -> Result<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
|
||||
pub(crate) fn factor(i: &mut &str) -> Result<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) -> Result<i64> {
|
||||
delimited('(', expr, ')').parse_next(i)
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
use std::fmt;
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use winnow::prelude::*;
|
||||
use winnow::Result;
|
||||
use winnow::{
|
||||
ascii::{digit1 as digits, multispace0 as multispaces},
|
||||
combinator::alt,
|
||||
combinator::delimited,
|
||||
combinator::repeat,
|
||||
token::one_of,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) 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(crate) 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(crate) fn expr(i: &mut &str) -> Result<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)
|
||||
}
|
||||
|
||||
pub(crate) fn term(i: &mut &str) -> Result<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)
|
||||
}
|
||||
|
||||
pub(crate) fn factor(i: &mut &str) -> Result<Expr> {
|
||||
delimited(
|
||||
multispaces,
|
||||
alt((digits.try_map(FromStr::from_str).map(Expr::Value), parens)),
|
||||
multispaces,
|
||||
)
|
||||
.parse_next(i)
|
||||
}
|
||||
|
||||
fn parens(i: &mut &str) -> Result<Expr> {
|
||||
delimited("(", expr, ")")
|
||||
.map(|e| Expr::Paren(Box::new(e)))
|
||||
.parse_next(i)
|
||||
}
|
||||
+246
@@ -0,0 +1,246 @@
|
||||
use std::fmt;
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
|
||||
use winnow::prelude::*;
|
||||
use winnow::Result;
|
||||
use winnow::{
|
||||
ascii::{digit1 as digits, multispace0 as multispaces},
|
||||
combinator::alt,
|
||||
combinator::dispatch,
|
||||
combinator::eof,
|
||||
combinator::fail,
|
||||
combinator::opt,
|
||||
combinator::peek,
|
||||
combinator::repeat,
|
||||
combinator::{delimited, preceded, terminated},
|
||||
error::ContextError,
|
||||
stream::TokenSlice,
|
||||
token::any,
|
||||
token::literal,
|
||||
token::one_of,
|
||||
token::take_till,
|
||||
};
|
||||
|
||||
/// Lex and parse
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn expr2(i: &mut &str) -> Result<Expr> {
|
||||
let tokens = tokens.parse_next(i)?;
|
||||
let mut tokens = Tokens::new(&tokens);
|
||||
expr.parse_next(&mut tokens)
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub(crate) struct Token<'s> {
|
||||
kind: TokenKind,
|
||||
raw: &'s str,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Token<'_> {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// Customized for brevity for a better `winnow/debug` experience
|
||||
match self.kind {
|
||||
TokenKind::Value => Debug::fmt(self.raw, fmt),
|
||||
TokenKind::Oper(oper) => Debug::fmt(&oper, fmt),
|
||||
TokenKind::OpenParen => fmt.write_str("OpenParen"),
|
||||
TokenKind::CloseParen => fmt.write_str("CloseParen"),
|
||||
TokenKind::Unknown => fmt.write_str("Unknown"),
|
||||
TokenKind::Eof => fmt.write_str("Eof"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<TokenKind> for Token<'_> {
|
||||
fn eq(&self, other: &TokenKind) -> bool {
|
||||
self.kind == *other
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub(crate) enum TokenKind {
|
||||
Value,
|
||||
Oper(Oper),
|
||||
OpenParen,
|
||||
CloseParen,
|
||||
Unknown,
|
||||
Eof,
|
||||
}
|
||||
|
||||
impl<'i> Parser<Tokens<'i>, &'i Token<'i>, ContextError> for TokenKind {
|
||||
fn parse_next(&mut self, input: &mut Tokens<'i>) -> Result<&'i Token<'i>> {
|
||||
literal(*self).parse_next(input).map(|t| &t[0])
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub(crate) enum Oper {
|
||||
Add,
|
||||
Sub,
|
||||
Mul,
|
||||
Div,
|
||||
}
|
||||
|
||||
impl winnow::stream::ContainsToken<&'_ Token<'_>> for TokenKind {
|
||||
#[inline(always)]
|
||||
fn contains_token(&self, token: &'_ Token<'_>) -> bool {
|
||||
*self == token.kind
|
||||
}
|
||||
}
|
||||
|
||||
impl winnow::stream::ContainsToken<&'_ Token<'_>> for &'_ [TokenKind] {
|
||||
#[inline]
|
||||
fn contains_token(&self, token: &'_ Token<'_>) -> bool {
|
||||
self.contains(&token.kind)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const LEN: usize> winnow::stream::ContainsToken<&'_ Token<'_>> for &'_ [TokenKind; LEN] {
|
||||
#[inline]
|
||||
fn contains_token(&self, token: &'_ Token<'_>) -> bool {
|
||||
self.contains(&token.kind)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const LEN: usize> winnow::stream::ContainsToken<&'_ Token<'_>> for [TokenKind; LEN] {
|
||||
#[inline]
|
||||
fn contains_token(&self, token: &'_ Token<'_>) -> bool {
|
||||
self.contains(&token.kind)
|
||||
}
|
||||
}
|
||||
|
||||
/// Lex tokens
|
||||
///
|
||||
/// See [`expr`] to parse the tokens
|
||||
pub(crate) fn tokens<'s>(i: &mut &'s str) -> Result<Vec<Token<'s>>> {
|
||||
let mut tokens: Vec<_> =
|
||||
preceded(multispaces, repeat(1.., terminated(token, multispaces))).parse_next(i)?;
|
||||
if let Some(eof) = opt(eof.map(|raw| Token {
|
||||
kind: TokenKind::Eof,
|
||||
raw,
|
||||
}))
|
||||
.parse_next(i)?
|
||||
{
|
||||
tokens.push(eof);
|
||||
}
|
||||
Ok(tokens)
|
||||
}
|
||||
|
||||
fn token<'s>(i: &mut &'s str) -> Result<Token<'s>> {
|
||||
dispatch! {peek(any);
|
||||
'0'..='9' => digits.value(TokenKind::Value),
|
||||
'(' => '('.value(TokenKind::OpenParen),
|
||||
')' => ')'.value(TokenKind::CloseParen),
|
||||
'+' => '+'.value(TokenKind::Oper(Oper::Add)),
|
||||
'-' => '-'.value(TokenKind::Oper(Oper::Sub)),
|
||||
'*' => '*'.value(TokenKind::Oper(Oper::Mul)),
|
||||
'/' => '/'.value(TokenKind::Oper(Oper::Div)),
|
||||
' '| '\t'| '\r'| '\n' => fail,
|
||||
_ => take_till(.., ('0'..='9', '(', ')', '+', '-', '*', '/')).value(TokenKind::Unknown),
|
||||
}
|
||||
.with_taken()
|
||||
.map(|(kind, raw)| Token { kind, raw })
|
||||
.parse_next(i)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) 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(crate) 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(crate) type Tokens<'i> = TokenSlice<'i, Token<'i>>;
|
||||
|
||||
/// Parse the tokens lexed in [`tokens`]
|
||||
pub(crate) fn expr(i: &mut Tokens<'_>) -> Result<Expr> {
|
||||
let init = term.parse_next(i)?;
|
||||
|
||||
let expr = repeat(
|
||||
0..,
|
||||
(
|
||||
one_of([TokenKind::Oper(Oper::Add), TokenKind::Oper(Oper::Sub)]),
|
||||
term,
|
||||
),
|
||||
)
|
||||
.fold(
|
||||
move || init.clone(),
|
||||
|acc, (op, val): (&Token<'_>, Expr)| {
|
||||
if op.kind == TokenKind::Oper(Oper::Add) {
|
||||
Expr::Add(Box::new(acc), Box::new(val))
|
||||
} else {
|
||||
Expr::Sub(Box::new(acc), Box::new(val))
|
||||
}
|
||||
},
|
||||
)
|
||||
.parse_next(i)?;
|
||||
|
||||
opt(TokenKind::Eof).parse_next(i)?;
|
||||
|
||||
Ok(expr)
|
||||
}
|
||||
|
||||
pub(crate) fn term(i: &mut Tokens<'_>) -> Result<Expr> {
|
||||
let init = factor.parse_next(i)?;
|
||||
|
||||
repeat(
|
||||
0..,
|
||||
(
|
||||
one_of([TokenKind::Oper(Oper::Mul), TokenKind::Oper(Oper::Div)]),
|
||||
factor,
|
||||
),
|
||||
)
|
||||
.fold(
|
||||
move || init.clone(),
|
||||
|acc, (op, val): (&Token<'_>, Expr)| {
|
||||
if op.kind == TokenKind::Oper(Oper::Mul) {
|
||||
Expr::Mul(Box::new(acc), Box::new(val))
|
||||
} else {
|
||||
Expr::Div(Box::new(acc), Box::new(val))
|
||||
}
|
||||
},
|
||||
)
|
||||
.parse_next(i)
|
||||
}
|
||||
|
||||
pub(crate) fn factor(i: &mut Tokens<'_>) -> Result<Expr> {
|
||||
alt((
|
||||
TokenKind::Value.try_map(|t: &Token<'_>| t.raw.parse::<i64>().map(Expr::Value)),
|
||||
parens,
|
||||
))
|
||||
.parse_next(i)
|
||||
}
|
||||
|
||||
fn parens(i: &mut Tokens<'_>) -> Result<Expr> {
|
||||
delimited(TokenKind::OpenParen, expr, TokenKind::CloseParen)
|
||||
.map(|e| Expr::Paren(Box::new(e)))
|
||||
.parse_next(i)
|
||||
}
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
use snapbox::assert_data_eq;
|
||||
use snapbox::prelude::*;
|
||||
use snapbox::str;
|
||||
use winnow::prelude::*;
|
||||
|
||||
use crate::parser::*;
|
||||
|
||||
#[test]
|
||||
fn factor_test() {
|
||||
let input = "3";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
3,
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(factor.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = " 12";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
12,
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(factor.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = "537 ";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
537,
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(factor.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = " 24 ";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
24,
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(factor.parse_peek(input).to_debug(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn term_test() {
|
||||
let input = " 12 *2 / 3";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
8,
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(term.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = " 12 *2 / 3";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
8,
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(term.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = " 2* 3 *2 *2 / 3";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
8,
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(term.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = " 48 / 3/2";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
8,
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(term.parse_peek(input).to_debug(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expr_test() {
|
||||
let input = " 1 + 2 ";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
3,
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(expr.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = " 12 + 6 - 4+ 3";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
17,
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(expr.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = " 1 + 2*3 + 4";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
11,
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(expr.parse_peek(input).to_debug(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parens_test() {
|
||||
let input = " ( 2 )";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
2,
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(expr.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = " 2* ( 3 + 4 ) ";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
14,
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(expr.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = " 2*2 / ( 5 - 1) + 3";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
4,
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(expr.parse_peek(input).to_debug(), expected);
|
||||
}
|
||||
@@ -0,0 +1,317 @@
|
||||
use snapbox::assert_data_eq;
|
||||
use snapbox::prelude::*;
|
||||
use snapbox::str;
|
||||
use winnow::prelude::*;
|
||||
|
||||
use crate::parser_ast::*;
|
||||
|
||||
#[test]
|
||||
fn factor_test() {
|
||||
let input = "3";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
Value(
|
||||
3,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(factor.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = " 12";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
Value(
|
||||
12,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(factor.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = "537 ";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
Value(
|
||||
537,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(factor.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = " 24 ";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
Value(
|
||||
24,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(factor.parse_peek(input).to_debug(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn term_test() {
|
||||
let input = " 12 *2 / 3";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
Div(
|
||||
Mul(
|
||||
Value(
|
||||
12,
|
||||
),
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
),
|
||||
Value(
|
||||
3,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(term.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = " 12 *2 / 3";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
Div(
|
||||
Mul(
|
||||
Value(
|
||||
12,
|
||||
),
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
),
|
||||
Value(
|
||||
3,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(term.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = " 2* 3 *2 *2 / 3";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
Div(
|
||||
Mul(
|
||||
Mul(
|
||||
Mul(
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
Value(
|
||||
3,
|
||||
),
|
||||
),
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
),
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
),
|
||||
Value(
|
||||
3,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(term.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = " 48 / 3/2";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
Div(
|
||||
Div(
|
||||
Value(
|
||||
48,
|
||||
),
|
||||
Value(
|
||||
3,
|
||||
),
|
||||
),
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(term.parse_peek(input).to_debug(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expr_test() {
|
||||
let input = " 1 + 2 ";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
Add(
|
||||
Value(
|
||||
1,
|
||||
),
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(expr.parse(input).to_debug(), expected);
|
||||
|
||||
let input = " 12 + 6 - 4+ 3";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
Add(
|
||||
Sub(
|
||||
Add(
|
||||
Value(
|
||||
12,
|
||||
),
|
||||
Value(
|
||||
6,
|
||||
),
|
||||
),
|
||||
Value(
|
||||
4,
|
||||
),
|
||||
),
|
||||
Value(
|
||||
3,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(expr.parse(input).to_debug(), expected);
|
||||
|
||||
let input = " 1 + 2*3 + 4";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
Add(
|
||||
Add(
|
||||
Value(
|
||||
1,
|
||||
),
|
||||
Mul(
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
Value(
|
||||
3,
|
||||
),
|
||||
),
|
||||
),
|
||||
Value(
|
||||
4,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(expr.parse(input).to_debug(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parens_test() {
|
||||
let input = " ( 2 )";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
Paren(
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(expr.parse(input).to_debug(), expected);
|
||||
|
||||
let input = " 2* ( 3 + 4 ) ";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
Mul(
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
Paren(
|
||||
Add(
|
||||
Value(
|
||||
3,
|
||||
),
|
||||
Value(
|
||||
4,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(expr.parse(input).to_debug(), expected);
|
||||
|
||||
let input = " 2*2 / ( 5 - 1) + 3";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
Add(
|
||||
Div(
|
||||
Mul(
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
),
|
||||
Paren(
|
||||
Sub(
|
||||
Value(
|
||||
5,
|
||||
),
|
||||
Value(
|
||||
1,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Value(
|
||||
3,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(expr.parse(input).to_debug(), expected);
|
||||
}
|
||||
@@ -0,0 +1,438 @@
|
||||
use snapbox::assert_data_eq;
|
||||
use snapbox::prelude::*;
|
||||
use snapbox::str;
|
||||
use winnow::prelude::*;
|
||||
|
||||
use crate::parser_lexer::*;
|
||||
|
||||
#[test]
|
||||
fn lex_test() {
|
||||
let input = "3";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
[
|
||||
"3",
|
||||
Eof,
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(tokens.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = " 24 ";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
[
|
||||
"24",
|
||||
Eof,
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(tokens.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = " 12 *2 / 3";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
[
|
||||
"12",
|
||||
Mul,
|
||||
"2",
|
||||
Div,
|
||||
"3",
|
||||
Eof,
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(tokens.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = " 2*2 / ( 5 - 1) + 3";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
[
|
||||
"2",
|
||||
Mul,
|
||||
"2",
|
||||
Div,
|
||||
OpenParen,
|
||||
"5",
|
||||
Sub,
|
||||
"1",
|
||||
CloseParen,
|
||||
Add,
|
||||
"3",
|
||||
Eof,
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(tokens.parse_peek(input).to_debug(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn factor_test() {
|
||||
let input = "3";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
[
|
||||
Eof,
|
||||
],
|
||||
Value(
|
||||
3,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
let input = tokens.parse(input).unwrap();
|
||||
let input = Tokens::new(&input);
|
||||
assert_data_eq!(factor.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = " 12";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
[
|
||||
Eof,
|
||||
],
|
||||
Value(
|
||||
12,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
let input = tokens.parse(input).unwrap();
|
||||
let input = Tokens::new(&input);
|
||||
assert_data_eq!(factor.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = "537 ";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
[
|
||||
Eof,
|
||||
],
|
||||
Value(
|
||||
537,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
let input = tokens.parse(input).unwrap();
|
||||
let input = Tokens::new(&input);
|
||||
assert_data_eq!(factor.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = " 24 ";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
[
|
||||
Eof,
|
||||
],
|
||||
Value(
|
||||
24,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
let input = tokens.parse(input).unwrap();
|
||||
let input = Tokens::new(&input);
|
||||
assert_data_eq!(factor.parse_peek(input).to_debug(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn term_test() {
|
||||
let input = " 12 *2 / 3";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
[
|
||||
Eof,
|
||||
],
|
||||
Div(
|
||||
Mul(
|
||||
Value(
|
||||
12,
|
||||
),
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
),
|
||||
Value(
|
||||
3,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
let input = tokens.parse(input).unwrap();
|
||||
let input = Tokens::new(&input);
|
||||
assert_data_eq!(term.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = " 12 *2 / 3";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
[
|
||||
Eof,
|
||||
],
|
||||
Div(
|
||||
Mul(
|
||||
Value(
|
||||
12,
|
||||
),
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
),
|
||||
Value(
|
||||
3,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
let input = tokens.parse(input).unwrap();
|
||||
let input = Tokens::new(&input);
|
||||
assert_data_eq!(term.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = " 2* 3 *2 *2 / 3";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
[
|
||||
Eof,
|
||||
],
|
||||
Div(
|
||||
Mul(
|
||||
Mul(
|
||||
Mul(
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
Value(
|
||||
3,
|
||||
),
|
||||
),
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
),
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
),
|
||||
Value(
|
||||
3,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
let input = tokens.parse(input).unwrap();
|
||||
let input = Tokens::new(&input);
|
||||
assert_data_eq!(term.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = " 48 / 3/2";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
[
|
||||
Eof,
|
||||
],
|
||||
Div(
|
||||
Div(
|
||||
Value(
|
||||
48,
|
||||
),
|
||||
Value(
|
||||
3,
|
||||
),
|
||||
),
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
let input = tokens.parse(input).unwrap();
|
||||
let input = Tokens::new(&input);
|
||||
assert_data_eq!(term.parse_peek(input).to_debug(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expr_test() {
|
||||
let input = " 1 + 2 ";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
Add(
|
||||
Value(
|
||||
1,
|
||||
),
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
let input = tokens.parse(input).unwrap();
|
||||
let input = Tokens::new(&input);
|
||||
assert_data_eq!(expr.parse(input).to_debug(), expected);
|
||||
|
||||
let input = " 12 + 6 - 4+ 3";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
Add(
|
||||
Sub(
|
||||
Add(
|
||||
Value(
|
||||
12,
|
||||
),
|
||||
Value(
|
||||
6,
|
||||
),
|
||||
),
|
||||
Value(
|
||||
4,
|
||||
),
|
||||
),
|
||||
Value(
|
||||
3,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
let input = tokens.parse(input).unwrap();
|
||||
let input = Tokens::new(&input);
|
||||
assert_data_eq!(expr.parse(input).to_debug(), expected);
|
||||
|
||||
let input = " 1 + 2*3 + 4";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
Add(
|
||||
Add(
|
||||
Value(
|
||||
1,
|
||||
),
|
||||
Mul(
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
Value(
|
||||
3,
|
||||
),
|
||||
),
|
||||
),
|
||||
Value(
|
||||
4,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
let input = tokens.parse(input).unwrap();
|
||||
let input = Tokens::new(&input);
|
||||
assert_data_eq!(expr.parse(input).to_debug(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parens_test() {
|
||||
let input = " ( 2 )";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
Paren(
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
let input = tokens.parse(input).unwrap();
|
||||
let input = Tokens::new(&input);
|
||||
assert_data_eq!(expr.parse(input).to_debug(), expected);
|
||||
|
||||
let input = " 2* ( 3 + 4 ) ";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
Mul(
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
Paren(
|
||||
Add(
|
||||
Value(
|
||||
3,
|
||||
),
|
||||
Value(
|
||||
4,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
let input = tokens.parse(input).unwrap();
|
||||
let input = Tokens::new(&input);
|
||||
assert_data_eq!(expr.parse(input).to_debug(), expected);
|
||||
|
||||
let input = " 2*2 / ( 5 - 1) + 3";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
Add(
|
||||
Div(
|
||||
Mul(
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
),
|
||||
Paren(
|
||||
Sub(
|
||||
Value(
|
||||
5,
|
||||
),
|
||||
Value(
|
||||
1,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Value(
|
||||
3,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
let input = tokens.parse(input).unwrap();
|
||||
let input = Tokens::new(&input);
|
||||
assert_data_eq!(expr.parse(input).to_debug(), expected);
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
mod parser;
|
||||
|
||||
use criterion::black_box;
|
||||
use winnow::prelude::*;
|
||||
|
||||
fn pratt(c: &mut criterion::Criterion) {
|
||||
let i =
|
||||
"a = 2*-2 / ( &**foo.a->p! -+1) + 3^1 / 4 == 1 * (2 - 7 + 567 *12 /2) + 3*(1+2*( 45 /2))";
|
||||
parser::pratt_parser.parse(i).expect("should parse");
|
||||
c.bench_function("pratt_parser", |b| {
|
||||
b.iter(|| black_box(parser::pratt_parser.parse(i).unwrap()));
|
||||
});
|
||||
}
|
||||
|
||||
criterion::criterion_group!(benches, pratt);
|
||||
criterion::criterion_main!(benches);
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
use winnow::prelude::*;
|
||||
|
||||
mod parser;
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
|
||||
fn main() -> Result<(), lexopt::Error> {
|
||||
let args = Args::parse()?;
|
||||
|
||||
let input = args.input.as_deref().unwrap_or("1 + 1");
|
||||
match parser::pratt_parser.parse(input) {
|
||||
Ok(result) => {
|
||||
println!("{result}");
|
||||
}
|
||||
Err(err) => {
|
||||
println!("FAILED");
|
||||
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)
|
||||
}
|
||||
}
|
||||
+529
@@ -0,0 +1,529 @@
|
||||
//! Main points of the `c_expression` example:
|
||||
//!
|
||||
//! 1. [`Expr`], representing the AST of C-style expressions
|
||||
//! 2. [`pratt_parser()`], the core parser.
|
||||
//! 3. The [`test`]s, which demonstrate the expected input/outputs.
|
||||
//!
|
||||
//! There is one [`parse_example()`] function in this file, but the
|
||||
//! associated test module for this example shows the other inputs/outputs.
|
||||
//!
|
||||
//! # Errata:
|
||||
//! - There is a helper parser, [`identifier()`]
|
||||
//! - Two print implementations: [`Expr::fmt_ast_with_indent()`] and [`Expr::fmt_delimited()`]
|
||||
//! - For operator precedence, `1` has a low binding power while `13` has a high binding power.
|
||||
//!
|
||||
//! ## Printing
|
||||
//!
|
||||
//! `fmt_delimited()` is essentially prefix-notation.
|
||||
//!
|
||||
//! A short visual about the differences in printing, for parsing `1 + 1`:
|
||||
//!
|
||||
//! `fmt_ast_with_indent()` =
|
||||
//! ~~~text
|
||||
//! ADD
|
||||
//! VAL 1
|
||||
//! VAL 1
|
||||
//! ~~~
|
||||
//!
|
||||
//! `fmt_delimited()` =
|
||||
//! ~~~text
|
||||
//! (+ 1 1)
|
||||
//! ~~~
|
||||
//!
|
||||
//! ## Precedences
|
||||
//!
|
||||
//! Values uses an *inverted form* of the [C language operator precedence](c-precedence).
|
||||
//!
|
||||
//! In our implementation, a precedence of `1` is a very low precedence compared
|
||||
//! to a value of `13`.
|
||||
//!
|
||||
//! In the linked C operator page, the table shows precedence in *descending precedence order*,
|
||||
//! so `1` is the highest precedence, while `13` is a low precedence.
|
||||
//!
|
||||
//! The precedence levels you have to choose for your grammar depend on the language's
|
||||
//! semantics.
|
||||
//!
|
||||
//! [c-precedence]: https://en.cppreference.com/w/c/language/operator_precedence.html
|
||||
//!
|
||||
//! ### Precedence Table
|
||||
//!
|
||||
//! An overview of the different operators for this C-style expression language.
|
||||
//!
|
||||
//! Note: this does not include the operands themselves, such as literals or
|
||||
//! parenthesized expressions like: `(x)`
|
||||
//!
|
||||
//! Legend:
|
||||
//! - Kind: one of Prefix, Postfix, or Infix
|
||||
//! - Example: input text example
|
||||
//! - Name: the [`Expr`] variant it corresponds to
|
||||
//! - Power: the binding power/precedence of the operator
|
||||
//! - Assoc: infix-only, represents the left/right associativity of an operator
|
||||
//! - Recursive: only "TRUE" if parsing the operand invokes the parser again
|
||||
//!
|
||||
//! | Kind | Example | Name | Power | Assoc | Recursive |
|
||||
//! |---------|------------|------------------|-------|---------|-----------|
|
||||
//! | Prefix | `++x` | PreIncr | 18 | | |
|
||||
//! | Prefix | `+x` | Positive (no-op) | 18 | | |
|
||||
//! | Prefix | `--x` | PreDecr | 18 | | |
|
||||
//! | Prefix | `-x` | Neg | 18 | | |
|
||||
//! | Prefix | `&x` | Addr | 18 | | |
|
||||
//! | Prefix | `*x` | Deref | 18 | | |
|
||||
//! | Prefix | `!x` | Not | 18 | | |
|
||||
//! | Prefix | `~x` | BitwiseNot | 18 | | |
|
||||
//! | Postfix | `x!` | Fac | 19 | | |
|
||||
//! | Postfix | `x?` | Ternary | 3 | | TRUE |
|
||||
//! | Postfix | `[x]` | Index | 20 | | TRUE |
|
||||
//! | Postfix | `(x)` | Function call | 20 | | TRUE |
|
||||
//! | Postfix | `x++` | PostIncr | 20 | | |
|
||||
//! | Postfix | `x--` | PostDecr | 20 | | |
|
||||
//! | Infix | `a ** b` | Pow | 28 | Right | |
|
||||
//! | Infix | `a * b` | Mul | 16 | Left | |
|
||||
//! | Infix | `a / b` | Div | 16 | Left | |
|
||||
//! | Infix | `a % b` | Rem | 16 | Left | |
|
||||
//! | Infix | `a + b` | Add | 14 | Left | |
|
||||
//! | Infix | `a -ne b` | NotEq | 10 | Neither | |
|
||||
//! | Infix | `a -eq b` | Eq | 10 | Neither | |
|
||||
//! | Infix | `a -gt b` | Greater | 12 | Neither | |
|
||||
//! | Infix | `a -ge b` | GreaterEq | 12 | Neither | |
|
||||
//! | Infix | `a -lt b` | Less | 12 | Neither | |
|
||||
//! | Infix | `a -le b` | LessEqual | 12 | Neither | |
|
||||
//! | Infix | `a->b` | ArrowOp | 20 | Left | |
|
||||
//! | Infix | `a - b` | Sub | 14 | Left | |
|
||||
//! | Infix | `a.b` | Dot | 20 | Left | |
|
||||
//! | Infix | `a && b` | And | 6 | Left | |
|
||||
//! | Infix | `a & b` | BitAnd | 12 | Left | |
|
||||
//! | Infix | `a ^ b` | BitXor | 8 | Left | |
|
||||
//! | Infix | `a == b` | Eq | 10 | Neither | |
|
||||
//! | Infix | `a = b` | Assign | 2 | Right | |
|
||||
//! | Infix | `a >= b` | GreaterEq | 12 | Neither | |
|
||||
//! | Infix | `a > b` | Greater | 12 | Neither | |
|
||||
//! | Infix | `a <= b` | LessEqual | 12 | Neither | |
|
||||
//! | Infix | `a < b` | Less | 12 | Neither | |
|
||||
//! | Infix | `a, b` | Comma | 0 | Left | |
|
||||
//! | Infix | `a != b` | NotEq | 10 | Neither | |
|
||||
//! | Infix | `a \|\| b` | Or | 4 | Left | |
|
||||
|
||||
use winnow::ascii::{digit1, multispace0};
|
||||
use winnow::combinator::{
|
||||
alt, cut_err, delimited, expression, fail, not, opt, peek, separated_pair, trace,
|
||||
};
|
||||
use winnow::combinator::{Infix, Postfix, Prefix};
|
||||
use winnow::dispatch;
|
||||
use winnow::error::{ContextError, ErrMode};
|
||||
use winnow::prelude::*;
|
||||
use winnow::token::{any, one_of, take, take_while};
|
||||
|
||||
#[test]
|
||||
fn parse_example() {
|
||||
// Check out the [`crate::parser::test`] in this example for more samples.
|
||||
let result = pratt_parser
|
||||
.parse("a*a + b - c")
|
||||
.map(|r| format!("{}", r))
|
||||
.unwrap();
|
||||
|
||||
let expect = "\
|
||||
SUB
|
||||
ADD
|
||||
MUL
|
||||
NAME a
|
||||
NAME a
|
||||
NAME b
|
||||
NAME c
|
||||
|
||||
(- (+ (* a a) b) c)";
|
||||
|
||||
assert_eq!(result, expect);
|
||||
}
|
||||
|
||||
// Abstract syntax tree for an expression
|
||||
pub(crate) enum Expr {
|
||||
Name(String),
|
||||
Value(i64),
|
||||
|
||||
Assign(Box<Expr>, Box<Expr>),
|
||||
|
||||
Addr(Box<Expr>),
|
||||
Deref(Box<Expr>),
|
||||
|
||||
Dot(Box<Expr>, Box<Expr>),
|
||||
ArrowOp(Box<Expr>, Box<Expr>),
|
||||
Neg(Box<Expr>),
|
||||
Add(Box<Expr>, Box<Expr>),
|
||||
Sub(Box<Expr>, Box<Expr>),
|
||||
Mul(Box<Expr>, Box<Expr>),
|
||||
Div(Box<Expr>, Box<Expr>),
|
||||
Pow(Box<Expr>, Box<Expr>),
|
||||
Fac(Box<Expr>),
|
||||
|
||||
PreIncr(Box<Expr>),
|
||||
PostIncr(Box<Expr>),
|
||||
PreDecr(Box<Expr>),
|
||||
PostDecr(Box<Expr>),
|
||||
|
||||
And(Box<Expr>, Box<Expr>),
|
||||
Or(Box<Expr>, Box<Expr>),
|
||||
|
||||
// `==`
|
||||
Eq(Box<Expr>, Box<Expr>),
|
||||
// `!=`
|
||||
NotEq(Box<Expr>, Box<Expr>),
|
||||
// `!`
|
||||
Not(Box<Expr>),
|
||||
Greater(Box<Expr>, Box<Expr>),
|
||||
GreaterEqual(Box<Expr>, Box<Expr>),
|
||||
Less(Box<Expr>, Box<Expr>),
|
||||
LessEqual(Box<Expr>, Box<Expr>),
|
||||
|
||||
// A parenthesized expression.
|
||||
Paren(Box<Expr>),
|
||||
FunctionCall(Box<Expr>, Option<Box<Expr>>),
|
||||
Ternary(Box<Expr>, Box<Expr>, Box<Expr>),
|
||||
// foo[...]
|
||||
Index(Box<Expr>, Box<Expr>),
|
||||
// a, b
|
||||
Comma(Box<Expr>, Box<Expr>),
|
||||
|
||||
// %
|
||||
Rem(Box<Expr>, Box<Expr>),
|
||||
BitXor(Box<Expr>, Box<Expr>),
|
||||
BitAnd(Box<Expr>, Box<Expr>),
|
||||
BitwiseNot(Box<Expr>),
|
||||
}
|
||||
|
||||
/// Parser definition
|
||||
///
|
||||
/// We define a helper function `parser()` and call it at the very end.
|
||||
///
|
||||
/// `parser()` accepts a minimum `precedence_level` for the entire expression,
|
||||
/// and it returns a [`Parser`] that takes in a string and returns an [`Expr`] on
|
||||
/// success.
|
||||
pub(crate) fn pratt_parser(i: &mut &str) -> ModalResult<Expr> {
|
||||
fn parser<'i>(precedence: i64) -> impl Parser<&'i str, Expr, ErrMode<ContextError>> {
|
||||
move |i: &mut &str| {
|
||||
use Infix::{Left, Neither, Right};
|
||||
expression(
|
||||
// parsing an operand, optionally surrounded by whitespace
|
||||
delimited(
|
||||
multispace0,
|
||||
dispatch! {peek(any);
|
||||
// Case one: Parenthesized expression
|
||||
'(' => delimited('(', parser(0).map(|e| Expr::Paren(Box::new(e))), cut_err(')')),
|
||||
_ => alt((
|
||||
// Case two: A C-style identifier
|
||||
identifier.map(|s| Expr::Name(s.into())),
|
||||
// Case three: An integer
|
||||
digit1.parse_to::<i64>().map(Expr::Value)
|
||||
)),
|
||||
},
|
||||
multispace0,
|
||||
)
|
||||
)
|
||||
.current_precedence_level(precedence)
|
||||
.prefix(
|
||||
// parsing prefix operators, optionally surrounded by whitespace
|
||||
// for example: `++1`
|
||||
// ||^ operand, called `a` below
|
||||
// ^^ prefix 'pre-increment' operator
|
||||
delimited(
|
||||
multispace0,
|
||||
dispatch! {any;
|
||||
'+' => alt((
|
||||
// ++
|
||||
'+'.value(Prefix(18, |_: &mut _, a| Ok(Expr::PreIncr(Box::new(a))))),
|
||||
Prefix(18, |_: &mut _, a| Ok(a) )
|
||||
)),
|
||||
'-' => alt((
|
||||
// --
|
||||
'-'.value(Prefix(18, |_: &mut _, a| Ok(Expr::PreDecr(Box::new(a))))),
|
||||
Prefix(18, |_: &mut _, a| Ok(Expr::Neg(Box::new(a))))
|
||||
)),
|
||||
'&' => Prefix(18, |_: &mut _, a| Ok(Expr::Addr(Box::new(a)))),
|
||||
'*' => Prefix(18, |_: &mut _, a| Ok(Expr::Deref(Box::new(a)))),
|
||||
'!' => Prefix(18, |_: &mut _, a| Ok(Expr::Not(Box::new(a)))),
|
||||
'~' => Prefix(18, |_: &mut _, a| Ok(Expr::BitwiseNot(Box::new(a)))),
|
||||
_ => fail
|
||||
},
|
||||
multispace0,
|
||||
)
|
||||
)
|
||||
.postfix(
|
||||
// parsing postfix operators, optionally surrounded by whitespace
|
||||
// for example: `(1 == 1) ? 100 : 0`
|
||||
// |||||||| | \\\\\\\\___ remainder of the input, called `i` below
|
||||
// |||||||| ^ postfix ternary operator
|
||||
// ^^^^^^^^ operand, called `cond` below
|
||||
delimited(
|
||||
multispace0,
|
||||
alt((
|
||||
dispatch! {any;
|
||||
'!' => not('=').value(Postfix(19, |_: &mut _, a| Ok(Expr::Fac(Box::new(a))))),
|
||||
'?' => Postfix(3, |i: &mut &str, cond| {
|
||||
let (left, right) = cut_err(separated_pair(parser(0), delimited(multispace0, ':', multispace0), parser(3))).parse_next(i)?;
|
||||
Ok(Expr::Ternary(Box::new(cond), Box::new(left), Box::new(right)))
|
||||
}),
|
||||
'[' => Postfix(20, |i: &mut &str, a| {
|
||||
let index = delimited(multispace0, parser(0), (multispace0, cut_err(']'), multispace0)).parse_next(i)?;
|
||||
Ok(Expr::Index(Box::new(a), Box::new(index)))
|
||||
}),
|
||||
'(' => Postfix(20, |i: &mut &str, a| {
|
||||
let args = delimited(multispace0, opt(parser(0)), (multispace0, cut_err(')'), multispace0)).parse_next(i)?;
|
||||
Ok(Expr::FunctionCall(Box::new(a), args.map(Box::new)))
|
||||
}),
|
||||
_ => fail,
|
||||
},
|
||||
dispatch! {take(2usize);
|
||||
"++" => Postfix(20, |_: &mut _, a| Ok(Expr::PostIncr(Box::new(a)))),
|
||||
"--" => Postfix(20, |_: &mut _, a| Ok(Expr::PostDecr(Box::new(a)))),
|
||||
_ => fail,
|
||||
},
|
||||
)),
|
||||
multispace0,
|
||||
),
|
||||
)
|
||||
.infix(
|
||||
// parsing infix operators
|
||||
//
|
||||
// we do not need to deal with whitespace here, it would be redundant
|
||||
// since the operands already ignore whitespace
|
||||
//
|
||||
// an example infix operator: `123 + 456`
|
||||
// operand, called `a` ^^^ | ^^^ operand, called `b` below
|
||||
// |
|
||||
// ^ infix addition operator
|
||||
alt((
|
||||
dispatch! {any;
|
||||
'*' => alt((
|
||||
// **
|
||||
"*".value(Right(28, |_: &mut _, a, b| Ok(Expr::Pow(Box::new(a), Box::new(b))))),
|
||||
Left(16, |_: &mut _, a, b| Ok(Expr::Mul(Box::new(a), Box::new(b)))),
|
||||
)),
|
||||
'/' => Left(16, |_: &mut _, a, b| Ok(Expr::Div(Box::new(a), Box::new(b)))),
|
||||
'%' => Left(16, |_: &mut _, a, b| Ok(Expr::Rem(Box::new(a), Box::new(b)))),
|
||||
|
||||
'+' => Left(14, |_: &mut _, a, b| Ok(Expr::Add(Box::new(a), Box::new(b)))),
|
||||
'-' => alt((
|
||||
dispatch!{take(2usize);
|
||||
"ne" => Neither(10, |_: &mut _, a, b| Ok(Expr::NotEq(Box::new(a), Box::new(b)))),
|
||||
"eq" => Neither(10, |_: &mut _, a, b| Ok(Expr::Eq(Box::new(a), Box::new(b)))),
|
||||
"gt" => Neither(12, |_: &mut _, a, b| Ok(Expr::Greater(Box::new(a), Box::new(b)))),
|
||||
"ge" => Neither(12, |_: &mut _, a, b| Ok(Expr::GreaterEqual(Box::new(a), Box::new(b)))),
|
||||
"lt" => Neither(12, |_: &mut _, a, b| Ok(Expr::Less(Box::new(a), Box::new(b)))),
|
||||
"le" => Neither(12, |_: &mut _, a, b| Ok(Expr::LessEqual(Box::new(a), Box::new(b)))),
|
||||
_ => fail
|
||||
},
|
||||
'>'.value(Left(20, |_: &mut _, a, b| Ok(Expr::ArrowOp(Box::new(a), Box::new(b))))),
|
||||
Left(14, |_: &mut _, a, b| Ok(Expr::Sub(Box::new(a), Box::new(b))))
|
||||
)),
|
||||
'.' => Left(20, |_: &mut _, a, b| Ok(Expr::Dot(Box::new(a), Box::new(b)))),
|
||||
'&' => alt((
|
||||
// &&
|
||||
"&".value(Left(6, |_: &mut _, a, b| Ok(Expr::And(Box::new(a), Box::new(b))))),
|
||||
|
||||
Left(12, |_: &mut _, a, b| Ok(Expr::BitAnd(Box::new(a), Box::new(b)))),
|
||||
)),
|
||||
'^' => Left(8, |_: &mut _, a, b| Ok(Expr::BitXor(Box::new(a), Box::new(b)))),
|
||||
'=' => alt((
|
||||
// ==
|
||||
"=".value(Neither(10, |_: &mut _, a, b| Ok(Expr::Eq(Box::new(a), Box::new(b))))),
|
||||
Right(2, |_: &mut _, a, b| Ok(Expr::Assign(Box::new(a), Box::new(b))))
|
||||
)),
|
||||
|
||||
'>' => alt((
|
||||
// >=
|
||||
"=".value(Neither(12, |_: &mut _, a, b| Ok(Expr::GreaterEqual(Box::new(a), Box::new(b))))),
|
||||
Neither(12, |_: &mut _, a, b| Ok(Expr::Greater(Box::new(a), Box::new(b))))
|
||||
)),
|
||||
'<' => alt((
|
||||
// <=
|
||||
"=".value(Neither(12, |_: &mut _, a, b| Ok(Expr::LessEqual(Box::new(a), Box::new(b))))),
|
||||
Neither(12, |_: &mut _, a, b| Ok(Expr::Less(Box::new(a), Box::new(b))))
|
||||
)),
|
||||
',' => Left(0, |_: &mut _, a, b| Ok(Expr::Comma(Box::new(a), Box::new(b)))),
|
||||
_ => fail
|
||||
},
|
||||
dispatch! {take(2usize);
|
||||
"!=" => Neither(10, |_: &mut _, a, b| Ok(Expr::NotEq(Box::new(a), Box::new(b)))),
|
||||
"||" => Left(4, |_: &mut _, a, b| Ok(Expr::Or(Box::new(a), Box::new(b)))),
|
||||
_ => fail
|
||||
},
|
||||
)),
|
||||
)
|
||||
.parse_next(i)
|
||||
}
|
||||
}
|
||||
|
||||
parser(0).parse_next(i)
|
||||
}
|
||||
|
||||
// Helper parsers
|
||||
|
||||
fn identifier<'i>(i: &mut &'i str) -> ModalResult<&'i str> {
|
||||
trace(
|
||||
"identifier",
|
||||
(
|
||||
one_of(|c: char| c.is_alpha() || c == '_'),
|
||||
take_while(0.., |c: char| c.is_alphanum() || c == '_'),
|
||||
),
|
||||
)
|
||||
.take()
|
||||
.parse_next(i)
|
||||
}
|
||||
|
||||
// Formatted print implementations
|
||||
impl Expr {
|
||||
fn fmt_ast_with_indent(
|
||||
&self,
|
||||
indent: u32,
|
||||
f: &mut core::fmt::Formatter<'_>,
|
||||
) -> core::fmt::Result {
|
||||
for _ in 0..indent {
|
||||
write!(f, " ")?;
|
||||
}
|
||||
macro_rules! binary_fmt {
|
||||
($a:ident, $b:ident, $name:literal) => {{
|
||||
writeln!(f, $name)?;
|
||||
$a.fmt_ast_with_indent(indent + 1, f)?;
|
||||
$b.fmt_ast_with_indent(indent + 1, f)
|
||||
}};
|
||||
}
|
||||
macro_rules! unary_fmt {
|
||||
($a:ident, $name:literal) => {{
|
||||
writeln!(f, $name)?;
|
||||
$a.fmt_ast_with_indent(indent + 1, f)
|
||||
}};
|
||||
}
|
||||
match self {
|
||||
Self::Name(name) => writeln!(f, "NAME {name}"),
|
||||
Self::Value(value) => writeln!(f, "VAL {value}"),
|
||||
Self::Addr(a) => unary_fmt!(a, "ADDR"),
|
||||
Self::Deref(a) => unary_fmt!(a, "DEREF"),
|
||||
Self::Neg(a) => unary_fmt!(a, "NEG"),
|
||||
Self::Fac(a) => unary_fmt!(a, "FAC"),
|
||||
Self::PreIncr(a) => unary_fmt!(a, "PRE_INCR"),
|
||||
Self::PostIncr(a) => unary_fmt!(a, "POST_INCR"),
|
||||
Self::PreDecr(a) => unary_fmt!(a, "PRE_DECR"),
|
||||
Self::PostDecr(a) => unary_fmt!(a, "POST_DECR"),
|
||||
Self::Not(a) => unary_fmt!(a, "NOT"),
|
||||
Self::BitwiseNot(a) => unary_fmt!(a, "BIT_NOT"),
|
||||
Self::Paren(a) => unary_fmt!(a, "PAREN"),
|
||||
Self::Assign(a, b) => binary_fmt!(a, b, "ASSIGN"),
|
||||
Self::ArrowOp(a, b) => binary_fmt!(a, b, "ARROW"),
|
||||
Self::Dot(a, b) => binary_fmt!(a, b, "ARROW"),
|
||||
Self::FunctionCall(a, b) => {
|
||||
writeln!(f, "CALL")?;
|
||||
a.fmt_ast_with_indent(indent + 1, f)?;
|
||||
if let Some(b) = b {
|
||||
b.fmt_ast_with_indent(indent + 1, f)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Self::Add(a, b) => binary_fmt!(a, b, "ADD"),
|
||||
Self::Sub(a, b) => binary_fmt!(a, b, "SUB"),
|
||||
Self::Mul(a, b) => binary_fmt!(a, b, "MUL"),
|
||||
Self::Div(a, b) => binary_fmt!(a, b, "DIV"),
|
||||
Self::Pow(a, b) => binary_fmt!(a, b, "POW"),
|
||||
Self::And(a, b) => binary_fmt!(a, b, "AND"),
|
||||
Self::Or(a, b) => binary_fmt!(a, b, "OR"),
|
||||
Self::Eq(a, b) => binary_fmt!(a, b, "EQ"),
|
||||
Self::NotEq(a, b) => binary_fmt!(a, b, "NEQ"),
|
||||
Self::Greater(a, b) => binary_fmt!(a, b, "GREATER"),
|
||||
Self::GreaterEqual(a, b) => binary_fmt!(a, b, "GTEQ"),
|
||||
Self::Less(a, b) => binary_fmt!(a, b, "LESS"),
|
||||
Self::LessEqual(a, b) => binary_fmt!(a, b, "LESSEQ"),
|
||||
Self::BitXor(a, b) => binary_fmt!(a, b, "BIT_XOR"),
|
||||
Self::Rem(a, b) => binary_fmt!(a, b, "REM"),
|
||||
Self::BitAnd(a, b) => binary_fmt!(a, b, "BIT_AND"),
|
||||
Self::Index(a, b) => binary_fmt!(a, b, "INDEX"),
|
||||
Self::Comma(a, b) => binary_fmt!(a, b, "COMMA"),
|
||||
Self::Ternary(cond, a, b) => {
|
||||
writeln!(f, "TERNARY")?;
|
||||
cond.fmt_ast_with_indent(indent + 1, f)?;
|
||||
a.fmt_ast_with_indent(indent + 2, f)?;
|
||||
b.fmt_ast_with_indent(indent + 2, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
fn fmt_delimited(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
Self::Name(name) => return write!(f, "{name}"),
|
||||
Self::Value(value) => return write!(f, "{value}"),
|
||||
Self::Paren(a) => return a.fmt_delimited(f),
|
||||
_ => (),
|
||||
}
|
||||
macro_rules! unary {
|
||||
($op:literal, $a:ident) => {{
|
||||
write!(f, $op)?;
|
||||
$a.fmt_delimited(f)?;
|
||||
}};
|
||||
}
|
||||
macro_rules! binary {
|
||||
($op:literal, $a:ident, $b:ident) => {{
|
||||
write!(f, "{} ", $op)?;
|
||||
$a.fmt_delimited(f)?;
|
||||
write!(f, " ")?;
|
||||
$b.fmt_delimited(f)?;
|
||||
}};
|
||||
}
|
||||
write!(f, "(")?;
|
||||
match self {
|
||||
Self::Assign(a, b) => binary!("=", a, b),
|
||||
Self::FunctionCall(a, b) => {
|
||||
write!(f, "call ")?;
|
||||
a.fmt_delimited(f)?;
|
||||
if let Some(b) = b {
|
||||
write!(f, " ")?;
|
||||
b.fmt_delimited(f)?;
|
||||
}
|
||||
}
|
||||
Self::ArrowOp(a, b) => binary!("->", a, b),
|
||||
Self::Dot(a, b) => binary!(".", a, b),
|
||||
Self::Addr(a) => unary!("&", a),
|
||||
Self::Deref(a) => unary!("*", a),
|
||||
Self::Neg(a) => unary!("-", a),
|
||||
Self::Fac(a) => unary!("!", a),
|
||||
Self::Not(a) => unary!("!", a),
|
||||
Self::BitwiseNot(a) => unary!("~", a),
|
||||
Self::PreIncr(a) => unary!("pre++", a),
|
||||
Self::PostIncr(a) => unary!("post++", a),
|
||||
Self::PreDecr(a) => unary!("pre--", a),
|
||||
Self::PostDecr(a) => unary!("post--", a),
|
||||
Self::Add(a, b) => binary!("+", a, b),
|
||||
Self::Sub(a, b) => binary!("-", a, b),
|
||||
Self::Mul(a, b) => binary!("*", a, b),
|
||||
Self::Div(a, b) => binary!("/", a, b),
|
||||
Self::Pow(a, b) => binary!("**", a, b),
|
||||
Self::And(a, b) => binary!("&&", a, b),
|
||||
Self::Or(a, b) => binary!("||", a, b),
|
||||
Self::Eq(a, b) => binary!("==", a, b),
|
||||
Self::NotEq(a, b) => binary!("!=", a, b),
|
||||
Self::Greater(a, b) => binary!(">", a, b),
|
||||
Self::GreaterEqual(a, b) => binary!(">=", a, b),
|
||||
Self::Less(a, b) => binary!("<", a, b),
|
||||
Self::LessEqual(a, b) => binary!("<=", a, b),
|
||||
Self::BitXor(a, b) => binary!("^", a, b),
|
||||
Self::Rem(a, b) => binary!("%", a, b),
|
||||
Self::BitAnd(a, b) => binary!("&", a, b),
|
||||
Self::Index(a, b) => binary!("[]", a, b),
|
||||
Self::Comma(a, b) => binary!(",", a, b),
|
||||
Self::Ternary(cond, a, b) => {
|
||||
write!(f, "? ")?;
|
||||
cond.fmt_delimited(f)?;
|
||||
write!(f, " ")?;
|
||||
a.fmt_delimited(f)?;
|
||||
write!(f, " ")?;
|
||||
b.fmt_delimited(f)?;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
write!(f, ")")
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for Expr {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
self.fmt_ast_with_indent(0, f)?;
|
||||
writeln!(f)?;
|
||||
self.fmt_delimited(f)
|
||||
}
|
||||
}
|
||||
+248
@@ -0,0 +1,248 @@
|
||||
#[allow(clippy::useless_attribute)]
|
||||
#[allow(unused_imports)] // its dead for benches
|
||||
use winnow::error::ParseError;
|
||||
use winnow::Parser;
|
||||
|
||||
use crate::parser::pratt_parser;
|
||||
use crate::parser::Expr;
|
||||
|
||||
#[allow(dead_code)]
|
||||
// to invoke fmt_delimited()
|
||||
struct PrefixNotation(Expr);
|
||||
|
||||
impl core::fmt::Display for PrefixNotation {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
self.0.fmt_delimited(f)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn parse(i: &str) -> Result<String, ParseError<&str, winnow::error::ContextError>> {
|
||||
pratt_parser
|
||||
.parse(i)
|
||||
.map(|r| format!("{}", PrefixNotation(r)))
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn parse_ok(i: &str, expect: &str) {
|
||||
assert_eq!(parse(i).unwrap(), expect);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn op() {
|
||||
parse_ok(" 1 ", "1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn neither() {
|
||||
assert!(parse("1 == 2 == 3").is_err());
|
||||
assert!(parse("1 -le 2 -gt 3").is_err());
|
||||
assert!(parse("1 < 2 < 3").is_err());
|
||||
assert!(parse("1 != 2 == 3").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn equal() {
|
||||
parse_ok("x=3", "(= x 3)");
|
||||
parse_ok("x = 2*3", "(= x (* 2 3))");
|
||||
parse_ok("x = y", "(= x y)");
|
||||
parse_ok("a = b = 10", "(= a (= b 10))");
|
||||
parse_ok("x = ((y*4)-2)", "(= x (- (* y 4) 2))");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unary() {
|
||||
parse_ok("- - a", "(-(-a))");
|
||||
parse_ok("+ - a", "(-a)");
|
||||
parse_ok("++ -- a", "(pre++(pre--a))");
|
||||
parse_ok("a ++ --", "(post--(post++a))");
|
||||
parse_ok("!x", "(!x)");
|
||||
parse_ok("x--", "(post--x)");
|
||||
parse_ok("x[1]--", "(post--([] x 1))");
|
||||
parse_ok("--x", "(pre--x)");
|
||||
parse_ok("++x[1]", "(pre++([] x 1))");
|
||||
parse_ok("!x--", "(!(post--x))");
|
||||
parse_ok("~x++", "(~(post++x))");
|
||||
parse_ok("x++ - y++", "(- (post++x) (post++y))");
|
||||
parse_ok("++x - ++y", "(- (pre++x) (pre++y))");
|
||||
parse_ok("--1 * 2", "(* (pre--1) 2)");
|
||||
parse_ok("--f . g", "(pre--(. f g))");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn same_precedence() {
|
||||
// left associative
|
||||
parse_ok("1 + 2 + 3", "(+ (+ 1 2) 3)");
|
||||
parse_ok("1 - 2 - 3", "(- (- 1 2) 3)");
|
||||
parse_ok("1 * 2 * 3", "(* (* 1 2) 3)");
|
||||
parse_ok("1 / 2 / 3", "(/ (/ 1 2) 3)");
|
||||
parse_ok("1 % 2 % 3", "(% (% 1 2) 3)");
|
||||
parse_ok("1 ^ 2 ^ 3", "(^ (^ 1 2) 3)");
|
||||
parse_ok("+-+1", "(-1)");
|
||||
parse_ok("f . g . h", "(. (. f g) h)");
|
||||
parse_ok("++--++1", "(pre++(pre--(pre++1)))");
|
||||
// right associative
|
||||
parse_ok("2 ** 3 ** 2", "(** 2 (** 3 2))");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn different_precedence() {
|
||||
parse_ok("1 + 2 * 3", "(+ 1 (* 2 3))");
|
||||
parse_ok("1 + 2 * 3 - 4 / 5", "(- (+ 1 (* 2 3)) (/ 4 5))");
|
||||
parse_ok("a + b * c * d + e", "(+ (+ a (* (* b c) d)) e)");
|
||||
parse_ok("1 + ++2 * 3 * 5 + 6", "(+ (+ 1 (* (* (pre++2) 3) 5)) 6)");
|
||||
parse_ok("**3 + &1", "(+ (*(*3)) (&1))");
|
||||
parse_ok("x*y - y*z", "(- (* x y) (* y z))");
|
||||
parse_ok("x/y - y%z", "(- (/ x y) (% y z))");
|
||||
parse_ok("1<2 * 3", "(< 1 (* 2 3))");
|
||||
parse_ok(
|
||||
" 1 + 2 + f . g . h * 3 * 4",
|
||||
"(+ (+ 1 2) (* (* (. (. f g) h) 3) 4))",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prefix_postfix_power() {
|
||||
// https://en.cppreference.com/w/c/language/operator_precedence
|
||||
// `post++` has `1`, `pre--` and `*` have 2
|
||||
parse_ok("--**3++", "(pre--(*(*(post++3))))");
|
||||
parse_ok("**--3++", "(*(*(pre--(post++3))))");
|
||||
parse_ok("&foo()[0]", "(&([] (call foo) 0))");
|
||||
parse_ok("-9!", "(-(!9))");
|
||||
parse_ok("f . g !", "(!(. f g))");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prefix_infix() {
|
||||
parse_ok("x - -y", "(- x (-y))");
|
||||
parse_ok("-1 * -2", "(* (-1) (-2))");
|
||||
parse_ok("-x * -y", "(* (-x) (-y))");
|
||||
parse_ok("x - -234", "(- x (-234))");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ternary() {
|
||||
parse_ok("a ? 2 + c : -2 * 2", "(? a (+ 2 c) (* (-2) 2))");
|
||||
parse_ok("a ? b : c ? d : e", "(? a b (? c d e))");
|
||||
parse_ok("2! > 1 ? 3 : 1", "(? (> (!2) 1) 3 1)");
|
||||
parse_ok(
|
||||
"2 > 1 ? 1 -ne 3 ? 4 : 5 : 1",
|
||||
"(? (> 2 1) (? (!= 1 3) 4 5) 1)",
|
||||
);
|
||||
parse_ok("a > b ? 0 : 1", "(? (> a b) 0 1)");
|
||||
parse_ok("a > b ? x+1 : y+1", "(? (> a b) (+ x 1) (+ y 1))");
|
||||
parse_ok(
|
||||
"1 ? true1 : 2 ? true2 : false",
|
||||
"(? 1 true1 (? 2 true2 false))",
|
||||
);
|
||||
parse_ok(
|
||||
"1 ? true1 : (2 ? true2 : false)",
|
||||
"(? 1 true1 (? 2 true2 false))",
|
||||
);
|
||||
|
||||
parse_ok(
|
||||
"1 ? (2 ? true : false1) : false2",
|
||||
"(? 1 (? 2 true false1) false2)",
|
||||
);
|
||||
parse_ok(
|
||||
"1 ? 2 ? true : false1 : false2",
|
||||
"(? 1 (? 2 true false1) false2)",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn comma() {
|
||||
parse_ok("x=1,y=2,z=3", "(, (, (= x 1) (= y 2)) (= z 3))");
|
||||
parse_ok("a, b, c", "(, (, a b) c)");
|
||||
parse_ok("(a, b, c)", "(, (, a b) c)");
|
||||
parse_ok("f(a, b, c), d", "(, (call f (, (, a b) c)) d)");
|
||||
parse_ok("(a, b, c), d", "(, (, (, a b) c) d)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn comma_ternary() {
|
||||
parse_ok("x ? 1 : 2, y ? 3 : 4", "(, (? x 1 2) (? y 3 4))");
|
||||
// Comma expressions can be inside
|
||||
parse_ok("a , b ? c, d : e, f", "(, (, a (? b (, c d) e)) f)");
|
||||
parse_ok("a = 0 ? b : c = d", "(= a (= (? 0 b c) d))");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn braces() {
|
||||
parse_ok("4*(2+3)", "(* 4 (+ 2 3))");
|
||||
parse_ok("(2+3)*4", "(* (+ 2 3) 4)");
|
||||
parse_ok("(((0)))", "0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn logical() {
|
||||
parse_ok("a && b || c && d", "(|| (&& a b) (&& c d))");
|
||||
parse_ok("!a && !b", "(&& (!a) (!b))");
|
||||
parse_ok("a != b && c == d", "(&& (!= a b) (== c d))");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array() {
|
||||
parse_ok("x[1,2]", "([] x (, 1 2))");
|
||||
parse_ok("x[1]", "([] x 1)");
|
||||
parse_ok("x[a+b]", "([] x (+ a b))");
|
||||
parse_ok("c = pal[i*8]", "(= c ([] pal (* i 8)))");
|
||||
parse_ok("f[x] = 1", "(= ([] f x) 1)");
|
||||
parse_ok("x[0][1]", "([] ([] x 0) 1)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_call() {
|
||||
parse_ok("a()", "(call a)");
|
||||
parse_ok("a(+1)", "(call a 1)");
|
||||
parse_ok("a()+1", "(+ (call a) 1)");
|
||||
parse_ok("f(a, b, c)", "(call f (, (, a b) c))");
|
||||
parse_ok("print(x)", "(call print x)");
|
||||
parse_ok(
|
||||
"x = y(2)*3 + y(4)*5",
|
||||
"(= x (+ (* (call y 2) 3) (* (call y 4) 5)))",
|
||||
);
|
||||
parse_ok("x(1,2)+y(3,4)", "(+ (call x (, 1 2)) (call y (, 3 4)))");
|
||||
parse_ok("x(a,b,c[d])", "(call x (, (, a b) ([] c d)))");
|
||||
parse_ok(
|
||||
"x(1,2)*j+y(3,4)*k+z(5,6)*l",
|
||||
"(+ (+ (* (call x (, 1 2)) j) (* (call y (, 3 4)) k)) (* (call z (, 5 6)) l))",
|
||||
);
|
||||
parse_ok("print(test(2,3))", "(call print (call test (, 2 3)))");
|
||||
parse_ok("min(255,n*2)", "(call min (, 255 (* n 2)))");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn member_access() {
|
||||
parse_ok("a.b", "(. a b)");
|
||||
parse_ok("a.b.c", "(. (. a b) c)");
|
||||
parse_ok("a->b", "(-> a b)");
|
||||
parse_ok("++a->b", "(pre++(-> a b))");
|
||||
parse_ok("a++ ->b", "(-> (post++a) b)");
|
||||
parse_ok("a.(x)", "(. a x)");
|
||||
parse_ok("a.(x+3)", "(. a (+ x 3))");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn errors() {
|
||||
assert!(parse("x + a b").is_err());
|
||||
assert!(parse("x[a b]").is_err());
|
||||
assert!(parse("x[a)]").is_err());
|
||||
assert!(parse("x(a])").is_err());
|
||||
assert!(parse("[a + b]").is_err());
|
||||
assert!(parse("[a b]").is_err());
|
||||
assert!(parse("+").is_err());
|
||||
assert!(parse("a +").is_err());
|
||||
assert!(parse("<=").is_err());
|
||||
assert!(parse("<= - a + b").is_err());
|
||||
assert!(parse("a b").is_err());
|
||||
assert!(parse("a + b @").is_err());
|
||||
assert!(parse("a + b )").is_err());
|
||||
assert!(parse("( a + b").is_err());
|
||||
assert!(parse("( a + b) c").is_err());
|
||||
assert!(parse("f ( a + b ) c").is_err());
|
||||
assert!(parse("@ a + b").is_err());
|
||||
assert!(parse("a @ b").is_err());
|
||||
assert!(parse("(a @ b)").is_err());
|
||||
assert!(parse(")").is_err());
|
||||
}
|
||||
Vendored
+62
@@ -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,
|
||||
}
|
||||
))
|
||||
);
|
||||
}
|
||||
Vendored
+36
@@ -0,0 +1,36 @@
|
||||
use winnow::combinator::seq;
|
||||
use winnow::prelude::*;
|
||||
use winnow::token::take_while;
|
||||
use winnow::Result;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub(crate) struct Color {
|
||||
pub(crate) red: u8,
|
||||
pub(crate) green: u8,
|
||||
pub(crate) 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(crate) fn hex_color(input: &mut &str) -> Result<Color> {
|
||||
seq!(Color {
|
||||
_: '#',
|
||||
red: hex_primary,
|
||||
green: hex_primary,
|
||||
blue: hex_primary
|
||||
})
|
||||
.parse_next(input)
|
||||
}
|
||||
|
||||
fn hex_primary(input: &mut &str) -> Result<u8> {
|
||||
take_while(2, |c: char| c.is_ascii_hexdigit())
|
||||
.try_map(|input| u8::from_str_radix(input, 16))
|
||||
.parse_next(input)
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
use winnow::error::AddContext;
|
||||
use winnow::error::ErrMode;
|
||||
use winnow::error::FromExternalError;
|
||||
use winnow::error::ParserError;
|
||||
use winnow::prelude::*;
|
||||
use winnow::stream::Stream;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum CustomError<I> {
|
||||
MyError,
|
||||
Winnow(I),
|
||||
External {
|
||||
cause: Box<dyn std::error::Error + Send + Sync + 'static>,
|
||||
input: I,
|
||||
},
|
||||
}
|
||||
|
||||
impl<I: Stream + Clone> ParserError<I> for CustomError<I> {
|
||||
type Inner = Self;
|
||||
|
||||
fn from_input(input: &I) -> Self {
|
||||
CustomError::Winnow(input.clone())
|
||||
}
|
||||
|
||||
fn into_inner(self) -> Result<Self::Inner, Self> {
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, I: Stream> AddContext<I, C> for CustomError<I> {
|
||||
#[inline]
|
||||
fn add_context(
|
||||
self,
|
||||
_input: &I,
|
||||
_token_start: &<I as Stream>::Checkpoint,
|
||||
_context: C,
|
||||
) -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: Stream + Clone, E: std::error::Error + Send + Sync + 'static> FromExternalError<I, E>
|
||||
for CustomError<I>
|
||||
{
|
||||
#[inline]
|
||||
fn from_external_error(input: &I, e: E) -> Self {
|
||||
CustomError::External {
|
||||
cause: Box::new(e),
|
||||
input: input.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse<'s>(_input: &mut &'s str) -> ModalResult<&'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();
|
||||
assert!(matches!(err, ErrMode::Backtrack(CustomError::MyError)));
|
||||
}
|
||||
}
|
||||
Vendored
+36
@@ -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);
|
||||
Vendored
+47
@@ -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)
|
||||
}
|
||||
}
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
use winnow::combinator::seq;
|
||||
use winnow::prelude::*;
|
||||
use winnow::{ascii::line_ending, combinator::repeat, token::take_while};
|
||||
|
||||
pub(crate) type Stream<'i> = &'i [u8];
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub(crate) struct Request<'a> {
|
||||
method: &'a [u8],
|
||||
uri: &'a [u8],
|
||||
version: &'a [u8],
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub(crate) struct Header<'a> {
|
||||
name: &'a [u8],
|
||||
value: Vec<&'a [u8]>,
|
||||
}
|
||||
|
||||
pub(crate) 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>) -> ModalResult<(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>) -> ModalResult<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>) -> ModalResult<&'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>) -> ModalResult<&'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>) -> ModalResult<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'
|
||||
}
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
use winnow::combinator::seq;
|
||||
use winnow::{
|
||||
ascii::line_ending, combinator::repeat, prelude::*, stream::Partial, token::take_while,
|
||||
};
|
||||
|
||||
pub(crate) type Stream<'i> = Partial<&'i [u8]>;
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub(crate) struct Request<'a> {
|
||||
method: &'a [u8],
|
||||
uri: &'a [u8],
|
||||
version: &'a [u8],
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub(crate) struct Header<'a> {
|
||||
name: &'a [u8],
|
||||
value: Vec<&'a [u8]>,
|
||||
}
|
||||
|
||||
pub(crate) 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>) -> ModalResult<(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>) -> ModalResult<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>) -> ModalResult<&'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>) -> ModalResult<&'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>) -> ModalResult<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'
|
||||
}
|
||||
Vendored
+62
@@ -0,0 +1,62 @@
|
||||
use winnow::combinator::repeat;
|
||||
use winnow::prelude::*;
|
||||
use winnow::Result;
|
||||
|
||||
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>) -> Result<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);
|
||||
Vendored
+60
@@ -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)
|
||||
}
|
||||
}
|
||||
Vendored
+149
@@ -0,0 +1,149 @@
|
||||
use std::collections::HashMap;
|
||||
use std::str;
|
||||
|
||||
use winnow::prelude::*;
|
||||
use winnow::Result;
|
||||
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(crate) type Stream<'i> = &'i [u8];
|
||||
|
||||
pub(crate) fn categories<'s>(
|
||||
i: &mut Stream<'s>,
|
||||
) -> Result<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>) -> Result<&'s str> {
|
||||
delimited('[', take_while(0.., |c| c != b']'), ']')
|
||||
.try_map(str::from_utf8)
|
||||
.parse_next(i)
|
||||
}
|
||||
|
||||
pub(crate) fn key_value<'s>(i: &mut Stream<'s>) -> Result<(&'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)));
|
||||
}
|
||||
+209
@@ -0,0 +1,209 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use winnow::prelude::*;
|
||||
use winnow::Result;
|
||||
use winnow::{
|
||||
ascii::{alphanumeric1 as alphanumeric, space0 as space},
|
||||
combinator::opt,
|
||||
combinator::repeat,
|
||||
combinator::{delimited, terminated},
|
||||
token::{take_till, take_while},
|
||||
};
|
||||
|
||||
pub(crate) type Stream<'i> = &'i str;
|
||||
|
||||
pub(crate) fn categories<'s>(
|
||||
input: &mut Stream<'s>,
|
||||
) -> Result<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>) -> Result<(&'s str, HashMap<&'s str, &'s str>)> {
|
||||
(category, keys_and_values).parse_next(i)
|
||||
}
|
||||
|
||||
fn category<'s>(i: &mut Stream<'s>) -> Result<&'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>) -> Result<HashMap<&'s str, &'s str>> {
|
||||
repeat(0.., key_value).parse_next(input)
|
||||
}
|
||||
|
||||
fn key_value<'s>(i: &mut Stream<'s>) -> Result<(&'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>) -> Result<&'s str> {
|
||||
take_while(0.., |c| c != '\r' && c != '\n').parse_next(i)
|
||||
}
|
||||
|
||||
fn space_or_line_ending<'s>(i: &mut Stream<'s>) -> Result<&'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: {i} | o: {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: {i} | o: ({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: {i} | o: ({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: {i} | o: ({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: {i} | o: {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: {i} | o: {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: {i} | o: {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)));
|
||||
}
|
||||
Vendored
+66
@@ -0,0 +1,66 @@
|
||||
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) -> ModalResult<&'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 || parser.parse_next(&mut data).ok());
|
||||
|
||||
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 i = data;
|
||||
let mut winnow_it = iterator(
|
||||
&mut i,
|
||||
terminated(separated_pair(alphanumeric1, ":", alphanumeric1), ","),
|
||||
);
|
||||
|
||||
let res = winnow_it
|
||||
.map(|(k, v)| (k.to_uppercase(), v))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let parser_result: Result<_, ()> = winnow_it.finish();
|
||||
parser_result.unwrap();
|
||||
|
||||
// will print "iterator returned {"key1": "value1", "key3": "value3", "key2": "value2"}, remaining input is ';'"
|
||||
println!("iterator returned {res:?}, remaining input is '{i}'");
|
||||
}
|
||||
Vendored
+78
@@ -0,0 +1,78 @@
|
||||
use winnow::prelude::*;
|
||||
use winnow::Partial;
|
||||
|
||||
mod json;
|
||||
mod parser_alt;
|
||||
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("dispatch", name),
|
||||
&len,
|
||||
|b, _| {
|
||||
type Error = winnow::error::ErrMode<winnow::error::ContextError>;
|
||||
|
||||
b.iter(|| parser_dispatch::json::<Error>.parse_peek(sample).unwrap());
|
||||
},
|
||||
);
|
||||
group.bench_with_input(
|
||||
criterion::BenchmarkId::new("modeless", name),
|
||||
&len,
|
||||
|b, _| {
|
||||
type Error = winnow::error::ContextError;
|
||||
|
||||
b.iter(|| parser_dispatch::json::<Error>.parse_peek(sample).unwrap());
|
||||
},
|
||||
);
|
||||
group.bench_with_input(
|
||||
criterion::BenchmarkId::new("empty-error", name),
|
||||
&len,
|
||||
|b, _| {
|
||||
type Error<'i> = winnow::error::EmptyError;
|
||||
|
||||
b.iter(|| {
|
||||
parser_dispatch::json::<Error<'_>>
|
||||
.parse_peek(sample)
|
||||
.unwrap()
|
||||
});
|
||||
},
|
||||
);
|
||||
group.bench_with_input(criterion::BenchmarkId::new("alt", name), &len, |b, _| {
|
||||
type Error = winnow::error::ContextError;
|
||||
|
||||
b.iter(|| parser_alt::json::<Error>.parse_peek(sample).unwrap());
|
||||
});
|
||||
group.bench_with_input(
|
||||
criterion::BenchmarkId::new("streaming", name),
|
||||
&len,
|
||||
|b, _| {
|
||||
type Error = winnow::error::ContextError;
|
||||
|
||||
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);
|
||||
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub(crate) enum JsonValue {
|
||||
Null,
|
||||
Boolean(bool),
|
||||
Str(String),
|
||||
Num(f64),
|
||||
Array(Vec<JsonValue>),
|
||||
Object(HashMap<String, JsonValue>),
|
||||
}
|
||||
Vendored
+94
@@ -0,0 +1,94 @@
|
||||
mod json;
|
||||
mod parser_alt;
|
||||
mod parser_dispatch;
|
||||
#[allow(dead_code)]
|
||||
mod parser_partial;
|
||||
|
||||
use winnow::error::EmptyError;
|
||||
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_alt::json::<EmptyError>.parse(data),
|
||||
Impl::Dispatch => parser_dispatch::json::<EmptyError>.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,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
enum Impl {
|
||||
#[default]
|
||||
Naive,
|
||||
Dispatch,
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
+323
@@ -0,0 +1,323 @@
|
||||
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, StrContext},
|
||||
token::{any, none_of, take, take_while},
|
||||
};
|
||||
|
||||
use crate::json::JsonValue;
|
||||
|
||||
pub(crate) type Stream<'i> = &'i str;
|
||||
|
||||
/// The root element of a JSON parser is any value
|
||||
///
|
||||
/// A parser has the following signature:
|
||||
/// `&mut Stream -> ModalResult<Output, ContextError>`, with `ModalResult` defined as:
|
||||
/// `type ModalResult<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(crate) fn json<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, StrContext>>(
|
||||
input: &mut Stream<'i>,
|
||||
) -> ModalResult<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>, StrContext>>(
|
||||
input: &mut Stream<'i>,
|
||||
) -> ModalResult<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)
|
||||
}
|
||||
|
||||
/// `literal(string)` generates a parser that takes 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>) -> ModalResult<&'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>) -> ModalResult<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 take 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>, StrContext>>(
|
||||
input: &mut Stream<'i>,
|
||||
) -> ModalResult<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(StrContext::Expected("string".into()))
|
||||
.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>) -> ModalResult<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>) -> ModalResult<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>) -> ModalResult<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>, StrContext>>(
|
||||
input: &mut Stream<'i>,
|
||||
) -> ModalResult<Vec<JsonValue>, E> {
|
||||
preceded(
|
||||
('[', ws),
|
||||
cut_err(terminated(
|
||||
separated(0.., json_value, (ws, ',', ws)),
|
||||
(ws, ']'),
|
||||
)),
|
||||
)
|
||||
.context(StrContext::Expected("array".into()))
|
||||
.parse_next(input)
|
||||
}
|
||||
|
||||
fn object<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, StrContext>>(
|
||||
input: &mut Stream<'i>,
|
||||
) -> ModalResult<HashMap<String, JsonValue>, E> {
|
||||
preceded(
|
||||
('{', ws),
|
||||
cut_err(terminated(
|
||||
separated(0.., key_value, (ws, ',', ws)),
|
||||
(ws, '}'),
|
||||
)),
|
||||
)
|
||||
.context(StrContext::Expected("object".into()))
|
||||
.parse_next(input)
|
||||
}
|
||||
|
||||
fn key_value<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, StrContext>>(
|
||||
input: &mut Stream<'i>,
|
||||
) -> ModalResult<(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>) -> ModalResult<&'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(unused_imports)] // its dead for benches
|
||||
use super::*;
|
||||
|
||||
#[allow(clippy::useless_attribute)]
|
||||
#[allow(dead_code)] // its dead for benches
|
||||
type Error = winnow::error::ContextError;
|
||||
|
||||
#[test]
|
||||
fn json_string() {
|
||||
assert_eq!(string::<Error>.parse_peek("\"\""), Ok(("", "".to_owned())));
|
||||
assert_eq!(
|
||||
string::<Error>.parse_peek("\"abc\""),
|
||||
Ok(("", "abc".to_owned()))
|
||||
);
|
||||
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_owned())),
|
||||
);
|
||||
assert_eq!(
|
||||
string::<Error>.parse_peek("\"\\uD83D\\uDE10\""),
|
||||
Ok(("", "😐".to_owned()))
|
||||
);
|
||||
|
||||
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_owned(), Num(42.0)),
|
||||
("b".to_owned(), Str("x".to_owned())),
|
||||
]
|
||||
.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_owned())]);
|
||||
|
||||
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_owned(), Null),
|
||||
("true".to_owned(), Boolean(true)),
|
||||
("false".to_owned(), Boolean(false)),
|
||||
("number".to_owned(), Num(123e4)),
|
||||
("string".to_owned(), Str(" abc 123 ".to_owned())),
|
||||
(
|
||||
"array".to_owned(),
|
||||
Array(vec![Boolean(false), Num(1.0), Str("two".to_owned())])
|
||||
),
|
||||
(
|
||||
"object".to_owned(),
|
||||
Object(
|
||||
vec![
|
||||
("a".to_owned(), Num(1.0)),
|
||||
("b".to_owned(), Str("c".to_owned())),
|
||||
]
|
||||
.into_iter()
|
||||
.collect()
|
||||
)
|
||||
),
|
||||
("empty_array".to_owned(), Array(vec![]),),
|
||||
("empty_object".to_owned(), Object(HashMap::new()),),
|
||||
]
|
||||
.into_iter()
|
||||
.collect()
|
||||
)
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
+319
@@ -0,0 +1,319 @@
|
||||
use std::collections::HashMap;
|
||||
use std::str;
|
||||
|
||||
use winnow::prelude::*;
|
||||
use winnow::Result;
|
||||
use winnow::{
|
||||
ascii::float,
|
||||
combinator::empty,
|
||||
combinator::fail,
|
||||
combinator::peek,
|
||||
combinator::{alt, dispatch},
|
||||
combinator::{delimited, preceded, separated_pair, terminated},
|
||||
combinator::{repeat, separated},
|
||||
error::{AddContext, ParserError, StrContext},
|
||||
token::{any, none_of, take, take_while},
|
||||
};
|
||||
|
||||
use crate::json::JsonValue;
|
||||
|
||||
pub(crate) type Stream<'i> = &'i str;
|
||||
|
||||
/// The root element of a JSON parser is any value
|
||||
///
|
||||
/// A parser has the following signature:
|
||||
/// `&mut Stream -> Result<Output ContextError>`
|
||||
///
|
||||
/// 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(crate) fn json<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, StrContext>>(
|
||||
input: &mut Stream<'i>,
|
||||
) -> Result<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>, StrContext>>(
|
||||
input: &mut Stream<'i>,
|
||||
) -> Result<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)
|
||||
}
|
||||
|
||||
/// `literal(string)` generates a parser that takes 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>) -> Result<&'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>) -> Result<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>) -> Result<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 take 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>, StrContext>>(
|
||||
input: &mut Stream<'i>,
|
||||
) -> Result<String, E> {
|
||||
preceded(
|
||||
'\"',
|
||||
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(StrContext::Expected("string".into()))
|
||||
.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>) -> Result<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>) -> Result<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>) -> Result<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>, StrContext>>(
|
||||
input: &mut Stream<'i>,
|
||||
) -> Result<Vec<JsonValue>, E> {
|
||||
preceded(
|
||||
('[', ws),
|
||||
terminated(separated(0.., json_value, (ws, ',', ws)), (ws, ']')),
|
||||
)
|
||||
.context(StrContext::Expected("array".into()))
|
||||
.parse_next(input)
|
||||
}
|
||||
|
||||
fn object<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, StrContext>>(
|
||||
input: &mut Stream<'i>,
|
||||
) -> Result<HashMap<String, JsonValue>, E> {
|
||||
preceded(
|
||||
('{', ws),
|
||||
terminated(separated(0.., key_value, (ws, ',', ws)), (ws, '}')),
|
||||
)
|
||||
.context(StrContext::Expected("object".into()))
|
||||
.parse_next(input)
|
||||
}
|
||||
|
||||
fn key_value<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, StrContext>>(
|
||||
input: &mut Stream<'i>,
|
||||
) -> Result<(String, JsonValue), E> {
|
||||
separated_pair(string, (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>) -> Result<&'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(unused_imports)] // its dead for benches
|
||||
use super::*;
|
||||
|
||||
#[allow(clippy::useless_attribute)]
|
||||
#[allow(dead_code)] // its dead for benches
|
||||
type Error = winnow::error::ContextError;
|
||||
|
||||
#[test]
|
||||
fn json_string() {
|
||||
assert_eq!(string::<Error>.parse_peek("\"\""), Ok(("", "".to_owned())));
|
||||
assert_eq!(
|
||||
string::<Error>.parse_peek("\"abc\""),
|
||||
Ok(("", "abc".to_owned()))
|
||||
);
|
||||
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_owned())),
|
||||
);
|
||||
assert_eq!(
|
||||
string::<Error>.parse_peek("\"\\uD83D\\uDE10\""),
|
||||
Ok(("", "😐".to_owned()))
|
||||
);
|
||||
|
||||
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_owned(), Num(42.0)),
|
||||
("b".to_owned(), Str("x".to_owned())),
|
||||
]
|
||||
.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_owned())]);
|
||||
|
||||
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_owned(), Null),
|
||||
("true".to_owned(), Boolean(true)),
|
||||
("false".to_owned(), Boolean(false)),
|
||||
("number".to_owned(), Num(123e4)),
|
||||
("string".to_owned(), Str(" abc 123 ".to_owned())),
|
||||
(
|
||||
"array".to_owned(),
|
||||
Array(vec![Boolean(false), Num(1.0), Str("two".to_owned())])
|
||||
),
|
||||
(
|
||||
"object".to_owned(),
|
||||
Object(
|
||||
vec![
|
||||
("a".to_owned(), Num(1.0)),
|
||||
("b".to_owned(), Str("c".to_owned())),
|
||||
]
|
||||
.into_iter()
|
||||
.collect()
|
||||
)
|
||||
),
|
||||
("empty_array".to_owned(), Array(vec![]),),
|
||||
("empty_object".to_owned(), Object(HashMap::new()),),
|
||||
]
|
||||
.into_iter()
|
||||
.collect()
|
||||
)
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
+350
@@ -0,0 +1,350 @@
|
||||
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, StrContext},
|
||||
stream::Partial,
|
||||
token::{any, none_of, rest, take, take_while},
|
||||
};
|
||||
|
||||
use crate::json::JsonValue;
|
||||
|
||||
pub(crate) type Stream<'i> = Partial<&'i str>;
|
||||
|
||||
/// The root element of a JSON parser is any value
|
||||
///
|
||||
/// A parser has the following signature:
|
||||
/// `&mut Stream -> ModalResult<Output, ContextError>`, with `ModalResult` defined as:
|
||||
/// `type ModalResult<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(crate) fn json<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, StrContext>>(
|
||||
input: &mut Stream<'i>,
|
||||
) -> ModalResult<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>, StrContext>>(
|
||||
input: &mut Stream<'i>,
|
||||
) -> ModalResult<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)
|
||||
}
|
||||
|
||||
/// `literal(string)` generates a parser that takes 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>) -> ModalResult<&'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>) -> ModalResult<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 take 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>, StrContext>>(
|
||||
input: &mut Stream<'i>,
|
||||
) -> ModalResult<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(StrContext::Expected("string".into()))
|
||||
.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>) -> ModalResult<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>) -> ModalResult<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>) -> ModalResult<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>, StrContext>>(
|
||||
input: &mut Stream<'i>,
|
||||
) -> ModalResult<Vec<JsonValue>, E> {
|
||||
preceded(
|
||||
('[', ws),
|
||||
cut_err(terminated(
|
||||
separated(0.., json_value, (ws, ',', ws)),
|
||||
(ws, ']'),
|
||||
)),
|
||||
)
|
||||
.context(StrContext::Expected("array".into()))
|
||||
.parse_next(input)
|
||||
}
|
||||
|
||||
fn object<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, StrContext>>(
|
||||
input: &mut Stream<'i>,
|
||||
) -> ModalResult<HashMap<String, JsonValue>, E> {
|
||||
preceded(
|
||||
('{', ws),
|
||||
cut_err(terminated(
|
||||
separated(0.., key_value, (ws, ',', ws)),
|
||||
(ws, '}'),
|
||||
)),
|
||||
)
|
||||
.context(StrContext::Expected("object".into()))
|
||||
.parse_next(input)
|
||||
}
|
||||
|
||||
fn key_value<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, StrContext>>(
|
||||
input: &mut Stream<'i>,
|
||||
) -> ModalResult<(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>) -> ModalResult<&'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>) -> ModalResult<&'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(unused_imports)] // its dead for benches
|
||||
use super::*;
|
||||
|
||||
#[allow(clippy::useless_attribute)]
|
||||
#[allow(dead_code)] // its dead for benches
|
||||
type Error = winnow::error::ContextError;
|
||||
|
||||
#[test]
|
||||
fn json_string() {
|
||||
assert_eq!(
|
||||
string::<Error>.parse_peek(Partial::new("\"\"")),
|
||||
Ok((Partial::new(""), "".to_owned()))
|
||||
);
|
||||
assert_eq!(
|
||||
string::<Error>.parse_peek(Partial::new("\"abc\"")),
|
||||
Ok((Partial::new(""), "abc".to_owned()))
|
||||
);
|
||||
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_owned()
|
||||
)),
|
||||
);
|
||||
assert_eq!(
|
||||
string::<Error>.parse_peek(Partial::new("\"\\uD83D\\uDE10\"")),
|
||||
Ok((Partial::new(""), "😐".to_owned()))
|
||||
);
|
||||
|
||||
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_owned(), Num(42.0)),
|
||||
("b".to_owned(), Str("x".to_owned())),
|
||||
]
|
||||
.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_owned())]);
|
||||
|
||||
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_owned(), Null),
|
||||
("true".to_owned(), Boolean(true)),
|
||||
("false".to_owned(), Boolean(false)),
|
||||
("number".to_owned(), Num(123e4)),
|
||||
("string".to_owned(), Str(" abc 123 ".to_owned())),
|
||||
(
|
||||
"array".to_owned(),
|
||||
Array(vec![Boolean(false), Num(1.0), Str("two".to_owned())])
|
||||
),
|
||||
(
|
||||
"object".to_owned(),
|
||||
Object(
|
||||
vec![
|
||||
("a".to_owned(), Num(1.0)),
|
||||
("b".to_owned(), Str("c".to_owned())),
|
||||
]
|
||||
.into_iter()
|
||||
.collect()
|
||||
)
|
||||
),
|
||||
("empty_array".to_owned(), Array(vec![]),),
|
||||
("empty_object".to_owned(), Object(HashMap::new()),),
|
||||
]
|
||||
.into_iter()
|
||||
.collect()
|
||||
)
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
+311
@@ -0,0 +1,311 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use winnow::prelude::*;
|
||||
use winnow::{
|
||||
ascii::{alphanumeric1 as alphanumeric, float, take_escaped},
|
||||
combinator::alt,
|
||||
combinator::cut_err,
|
||||
combinator::separated,
|
||||
combinator::{preceded, separated_pair, terminated},
|
||||
error::ParserError,
|
||||
error::StrContext,
|
||||
stream::Offset,
|
||||
token::one_of,
|
||||
token::{literal, 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 literal::<_, _, ()>("[").parse_next(&mut data) {
|
||||
Err(_) => None,
|
||||
Ok(_) => {
|
||||
println!("[");
|
||||
self.offset(data);
|
||||
let mut first = true;
|
||||
let mut done = false;
|
||||
let mut previous = 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 literal::<_, _, ()>("]").parse_next(&mut data).is_ok() {
|
||||
println!("]");
|
||||
v.offset(data);
|
||||
done = true;
|
||||
return None;
|
||||
}
|
||||
|
||||
if first {
|
||||
first = false;
|
||||
} else {
|
||||
match literal::<_, _, ()>(",").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 literal::<_, _, ()>("{").parse_next(&mut data) {
|
||||
Err(_) => None,
|
||||
Ok(_) => {
|
||||
self.offset(data);
|
||||
|
||||
println!("{{");
|
||||
|
||||
let mut first = true;
|
||||
let mut done = false;
|
||||
let mut previous = 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 literal::<_, _, ()>("}").parse_next(&mut data).is_ok() {
|
||||
println!("}}");
|
||||
v.offset(data);
|
||||
done = true;
|
||||
return None;
|
||||
}
|
||||
|
||||
if first {
|
||||
first = false;
|
||||
} else {
|
||||
match literal::<_, _, ()>(",").parse_next(&mut data) {
|
||||
Ok(_) => {
|
||||
println!(",");
|
||||
v.offset(data);
|
||||
}
|
||||
Err(_) => {
|
||||
done = true;
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match string(&mut data) {
|
||||
Ok(key) => {
|
||||
v.offset(data);
|
||||
|
||||
match literal::<_, _, ()>(":").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) -> ModalResult<&'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) -> ModalResult<&'a str, E> {
|
||||
take_escaped(alphanumeric, '\\', one_of(['"', 'n', '\\'])).parse_next(i)
|
||||
}
|
||||
|
||||
fn string<'s>(i: &mut &'s str) -> ModalResult<&'s str> {
|
||||
preceded('\"', cut_err(terminated(parse_str, '\"')))
|
||||
.context(StrContext::Label("string"))
|
||||
.parse_next(i)
|
||||
}
|
||||
|
||||
fn boolean(input: &mut &str) -> ModalResult<bool> {
|
||||
alt(("false".map(|_| false), "true".map(|_| true))).parse_next(input)
|
||||
}
|
||||
|
||||
fn array(i: &mut &str) -> ModalResult<()> {
|
||||
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) -> ModalResult<(&'s str, ())> {
|
||||
separated_pair(preceded(sp, string), cut_err(preceded(sp, ':')), value).parse_next(i)
|
||||
}
|
||||
|
||||
fn hash(i: &mut &str) -> ModalResult<()> {
|
||||
preceded(
|
||||
'{',
|
||||
cut_err(terminated(
|
||||
separated(0.., key_value, preceded(sp, ',')),
|
||||
preceded(sp, '}'),
|
||||
)),
|
||||
)
|
||||
.context(StrContext::Label("map"))
|
||||
.parse_next(i)
|
||||
}
|
||||
|
||||
fn value(i: &mut &str) -> ModalResult<()> {
|
||||
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:?}");
|
||||
}
|
||||
};
|
||||
}
|
||||
+158
@@ -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}
|
||||
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
mod parser;
|
||||
|
||||
use std::io::Read;
|
||||
|
||||
use winnow::error::ContextError;
|
||||
use winnow::error::ErrMode;
|
||||
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 mut input =
|
||||
parser::Stream::new(std::str::from_utf8(buffer.data()).map_err(to_lexopt)?);
|
||||
let start = input.checkpoint();
|
||||
match parser::ndjson::<ContextError>.parse_next(&mut input) {
|
||||
Ok(value) => {
|
||||
println!("{value:?}");
|
||||
println!();
|
||||
// Tell the buffer how much we read
|
||||
let consumed = input.offset_from(&start);
|
||||
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())
|
||||
}
|
||||
+340
@@ -0,0 +1,340 @@
|
||||
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, StrContext},
|
||||
stream::Partial,
|
||||
token::{any, none_of, take, take_while},
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub(crate) 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(crate) type Stream<'i> = Partial<&'i str>;
|
||||
|
||||
pub(crate) fn ndjson<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, StrContext>>(
|
||||
input: &mut Stream<'i>,
|
||||
) -> ModalResult<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>, StrContext>>(
|
||||
input: &mut Stream<'i>,
|
||||
) -> ModalResult<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)
|
||||
}
|
||||
|
||||
/// `literal(string)` generates a parser that takes 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>) -> ModalResult<&'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>) -> ModalResult<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 take 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>, StrContext>>(
|
||||
input: &mut Stream<'i>,
|
||||
) -> ModalResult<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(StrContext::Expected("string".into()))
|
||||
.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>) -> ModalResult<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>) -> ModalResult<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>) -> ModalResult<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>, StrContext>>(
|
||||
input: &mut Stream<'i>,
|
||||
) -> ModalResult<Vec<JsonValue>, E> {
|
||||
preceded(
|
||||
('[', ws),
|
||||
cut_err(terminated(
|
||||
separated(0.., json_value, (ws, ',', ws)),
|
||||
(ws, ']'),
|
||||
)),
|
||||
)
|
||||
.context(StrContext::Expected("array".into()))
|
||||
.parse_next(input)
|
||||
}
|
||||
|
||||
fn object<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, StrContext>>(
|
||||
input: &mut Stream<'i>,
|
||||
) -> ModalResult<HashMap<String, JsonValue>, E> {
|
||||
preceded(
|
||||
('{', ws),
|
||||
cut_err(terminated(
|
||||
separated(0.., key_value, (ws, ',', ws)),
|
||||
(ws, '}'),
|
||||
)),
|
||||
)
|
||||
.context(StrContext::Expected("object".into()))
|
||||
.parse_next(input)
|
||||
}
|
||||
|
||||
fn key_value<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, StrContext>>(
|
||||
input: &mut Stream<'i>,
|
||||
) -> ModalResult<(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>) -> ModalResult<&'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(unused_imports)] // its dead for benches
|
||||
use super::*;
|
||||
|
||||
#[allow(clippy::useless_attribute)]
|
||||
#[allow(dead_code)] // its dead for benches
|
||||
type Error = winnow::error::ContextError;
|
||||
|
||||
#[test]
|
||||
fn json_string() {
|
||||
assert_eq!(
|
||||
string::<Error>.parse_peek(Partial::new("\"\"")),
|
||||
Ok((Partial::new(""), "".to_owned()))
|
||||
);
|
||||
assert_eq!(
|
||||
string::<Error>.parse_peek(Partial::new("\"abc\"")),
|
||||
Ok((Partial::new(""), "abc".to_owned()))
|
||||
);
|
||||
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_owned()
|
||||
)),
|
||||
);
|
||||
assert_eq!(
|
||||
string::<Error>.parse_peek(Partial::new("\"\\uD83D\\uDE10\"")),
|
||||
Ok((Partial::new(""), "😐".to_owned()))
|
||||
);
|
||||
|
||||
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_owned(), Num(42.0)),
|
||||
("b".to_owned(), Str("x".to_owned())),
|
||||
]
|
||||
.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_owned())]);
|
||||
|
||||
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_owned(), Null),
|
||||
("true".to_owned(), Boolean(true)),
|
||||
("false".to_owned(), Boolean(false)),
|
||||
("number".to_owned(), Num(123e4)),
|
||||
("string".to_owned(), Str(" abc 123 ".to_owned())),
|
||||
(
|
||||
"array".to_owned(),
|
||||
Array(vec![Boolean(false), Num(1.0), Str("two".to_owned())])
|
||||
),
|
||||
(
|
||||
"object".to_owned(),
|
||||
Object(
|
||||
vec![
|
||||
("a".to_owned(), Num(1.0)),
|
||||
("b".to_owned(), Str("c".to_owned())),
|
||||
]
|
||||
.into_iter()
|
||||
.collect()
|
||||
)
|
||||
),
|
||||
("empty_array".to_owned(), Array(vec![]),),
|
||||
("empty_object".to_owned(), Object(HashMap::new()),),
|
||||
]
|
||||
.into_iter()
|
||||
.collect()
|
||||
))
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
+20
@@ -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)
|
||||
);
|
||||
}
|
||||
+361
@@ -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(crate) 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_owned()))
|
||||
}
|
||||
|
||||
/// 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(crate) 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(crate) 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(crate) 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) -> ModalResult<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) -> ModalResult<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) -> ModalResult<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) -> ModalResult<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) -> ModalResult<Atom> {
|
||||
alt((
|
||||
"#t".map(|_| Atom::Boolean(true)),
|
||||
"#f".map(|_| Atom::Boolean(false)),
|
||||
))
|
||||
.parse_next(i)
|
||||
}
|
||||
|
||||
fn parse_builtin(i: &mut &'_ str) -> ModalResult<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) -> ModalResult<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) -> ModalResult<Atom> {
|
||||
preceded(":", cut_err(alpha1))
|
||||
.context(StrContext::Label("keyword"))
|
||||
.map(|sym_str: &str| Atom::Keyword(sym_str.to_owned()))
|
||||
.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) -> ModalResult<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) -> ModalResult<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) -> ModalResult<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 ModalParser<&'a str, O1, ContextError>
|
||||
where
|
||||
F: ModalParser<&'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
|
||||
}
|
||||
}
|
||||
+70
@@ -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"))
|
||||
);
|
||||
}
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
//! 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};
|
||||
use winnow::Result;
|
||||
|
||||
/// Parse a string. Use a loop of `parse_fragment` and push all of the fragments
|
||||
/// into an output string.
|
||||
pub(crate) fn parse_string<'a, E>(input: &mut &'a str) -> Result<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) -> Result<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) -> Result<&'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) -> Result<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) -> Result<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,
|
||||
) -> Result<&'a str, E> {
|
||||
preceded('\\', multispace1).parse_next(input)
|
||||
}
|
||||
Reference in new issue
Block a user