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:
swaitsandClaude Opus 4.6 committed 2026-03-29 20:42:54 -06:00
1 parent 7064110293
commit 3102fc747a
17624 files changed
+4564928 -38

No files matched your search

+185
View File
@@ -0,0 +1,185 @@
use crate::combinator::trace;
use crate::error::ParserError;
use crate::stream::Stream;
use crate::{Parser, Result};
#[doc(inline)]
pub use crate::dispatch;
/// Helper trait for the [`alt()`] combinator.
///
/// This trait is implemented for tuples of up to 21 elements
pub trait Alt<I, O, E> {
/// Tests each parser in the tuple and returns the result of the first one that succeeds
fn choice(&mut self, input: &mut I) -> Result<O, E>;
}
/// Pick the first successful parser
///
/// To stop on an error, rather than trying further cases, see
/// [`cut_err`][crate::combinator::cut_err] ([example][crate::_tutorial::chapter_7]).
///
/// For tight control over the error when no match is found, add a final case using [`fail`][crate::combinator::fail].
/// Alternatively, with a [custom error type][crate::_topic::error], it is possible to track all
/// errors or return the error of the parser that went the farthest in the input data.
///
/// When the alternative cases have unique prefixes, [`dispatch`] can offer better performance.
///
/// # Example
///
/// ```rust
/// # #[cfg(feature = "ascii")] {
/// # use winnow::{error::ErrMode, error::Needed};
/// # use winnow::prelude::*;
/// use winnow::ascii::{alpha1, digit1};
/// use winnow::combinator::alt;
/// fn parser<'i>(input: &mut &'i str) -> ModalResult<&'i str> {
/// alt((alpha1, digit1)).parse_next(input)
/// };
///
/// // the first parser, alpha1, takes the input
/// assert_eq!(parser.parse_peek("abc"), Ok(("", "abc")));
///
/// // the first parser returns an error, so alt tries the second one
/// assert_eq!(parser.parse_peek("123456"), Ok(("", "123456")));
///
/// // both parsers failed, and with the default error type, alt will return the last error
/// assert!(parser.parse_peek(" ").is_err());
/// # }
/// ```
#[doc(alias = "choice")]
#[inline(always)]
pub fn alt<Input: Stream, Output, Error, Alternatives>(
mut alternatives: Alternatives,
) -> impl Parser<Input, Output, Error>
where
Alternatives: Alt<Input, Output, Error>,
Error: ParserError<Input>,
{
trace("alt", move |i: &mut Input| alternatives.choice(i))
}
impl<const N: usize, I: Stream, O, E: ParserError<I>, P: Parser<I, O, E>> Alt<I, O, E> for [P; N] {
fn choice(&mut self, input: &mut I) -> Result<O, E> {
let mut error: Option<E> = None;
let start = input.checkpoint();
for branch in self {
input.reset(&start);
match branch.parse_next(input) {
Err(e) if e.is_backtrack() => {
error = match error {
Some(error) => Some(error.or(e)),
None => Some(e),
};
}
res => return res,
}
}
match error {
Some(e) => Err(e.append(input, &start)),
None => Err(ParserError::assert(
input,
"`alt` needs at least one parser",
)),
}
}
}
impl<I: Stream, O, E: ParserError<I>, P: Parser<I, O, E>> Alt<I, O, E> for &mut [P] {
fn choice(&mut self, input: &mut I) -> Result<O, E> {
let mut error: Option<E> = None;
let start = input.checkpoint();
for branch in self.iter_mut() {
input.reset(&start);
match branch.parse_next(input) {
Err(e) if e.is_backtrack() => {
error = match error {
Some(error) => Some(error.or(e)),
None => Some(e),
};
}
res => return res,
}
}
match error {
Some(e) => Err(e.append(input, &start)),
None => Err(ParserError::assert(
input,
"`alt` needs at least one parser",
)),
}
}
}
macro_rules! alt_trait(
($first:ident $second:ident $($id: ident)+) => (
alt_trait!(__impl $first $second; $($id)+);
);
(__impl $($current:ident)*; $head:ident $($id: ident)+) => (
alt_trait_impl!($($current)*);
alt_trait!(__impl $($current)* $head; $($id)+);
);
(__impl $($current:ident)*; $head:ident) => (
alt_trait_impl!($($current)*);
alt_trait_impl!($($current)* $head);
);
);
macro_rules! alt_trait_impl(
($($id:ident)+) => (
impl<
I: Stream, Output, Error: ParserError<I>,
$($id: Parser<I, Output, Error>),+
> Alt<I, Output, Error> for ( $($id),+ ) {
fn choice(&mut self, input: &mut I) -> Result<Output, Error> {
let start = input.checkpoint();
match self.0.parse_next(input) {
Err(e) if e.is_backtrack() => alt_trait_inner!(1, self, input, start, e, $($id)+),
res => res,
}
}
}
);
);
macro_rules! succ (
(1, $submac:ident ! ($($rest:tt)*)) => ($submac!(2, $($rest)*));
(2, $submac:ident ! ($($rest:tt)*)) => ($submac!(3, $($rest)*));
(3, $submac:ident ! ($($rest:tt)*)) => ($submac!(4, $($rest)*));
(4, $submac:ident ! ($($rest:tt)*)) => ($submac!(5, $($rest)*));
(5, $submac:ident ! ($($rest:tt)*)) => ($submac!(6, $($rest)*));
(6, $submac:ident ! ($($rest:tt)*)) => ($submac!(7, $($rest)*));
(7, $submac:ident ! ($($rest:tt)*)) => ($submac!(8, $($rest)*));
(8, $submac:ident ! ($($rest:tt)*)) => ($submac!(9, $($rest)*));
);
macro_rules! alt_trait_inner(
($it:tt, $self:expr, $input:expr, $start:ident, $err:expr, $head:ident $($id:ident)+) => ({
$input.reset(&$start);
match $self.$it.parse_next($input) {
Err(e) if e.is_backtrack() => {
let err = $err.or(e);
succ!($it, alt_trait_inner!($self, $input, $start, err, $($id)+))
}
res => res,
}
});
($it:tt, $self:expr, $input:expr, $start:ident, $err:expr, $head:ident) => ({
Err($err.append($input, &$start))
});
);
alt_trait!(Alt2 Alt3 Alt4 Alt5 Alt6 Alt7 Alt8 Alt9 Alt10);
// Manually implement Alt for (A,), the 1-tuple type
impl<I: Stream, O, E: ParserError<I>, A: Parser<I, O, E>> Alt<I, O, E> for (A,) {
fn choice(&mut self, input: &mut I) -> Result<O, E> {
self.0.parse_next(input)
}
}
+400
View File
@@ -0,0 +1,400 @@
use crate::combinator::trace;
use crate::error::{ModalError, ParserError};
use crate::stream::Stream;
use crate::{Parser, Result};
/// Apply a [`Parser`], producing `None` on [`ErrMode::Backtrack`][crate::error::ErrMode::Backtrack].
///
/// To chain an error up, see [`cut_err`].
///
/// # Example
///
/// ```rust
/// # #[cfg(feature = "ascii")] {
/// # use winnow::prelude::*;
/// use winnow::combinator::opt;
/// use winnow::ascii::alpha1;
///
/// fn parser<'i>(i: &mut &'i str) -> ModalResult<Option<&'i str>> {
/// opt(alpha1).parse_next(i)
/// }
///
/// assert_eq!(parser.parse_peek("abcd;"), Ok((";", Some("abcd"))));
/// assert_eq!(parser.parse_peek("123;"), Ok(("123;", None)));
/// # }
/// ```
pub fn opt<Input: Stream, Output, Error, ParseNext>(
mut parser: ParseNext,
) -> impl Parser<Input, Option<Output>, Error>
where
ParseNext: Parser<Input, Output, Error>,
Error: ParserError<Input>,
{
trace("opt", move |input: &mut Input| {
let start = input.checkpoint();
match parser.parse_next(input) {
Ok(o) => Ok(Some(o)),
Err(e) if e.is_backtrack() => {
input.reset(&start);
Ok(None)
}
Err(e) => Err(e),
}
})
}
/// Calls the parser if the condition is met.
///
/// # Example
///
/// ```rust
/// # #[cfg(feature = "ascii")] {
/// # use winnow::prelude::*;
/// # use winnow::combinator::opt;
/// use winnow::combinator::cond;
/// use winnow::ascii::alpha1;
///
/// fn parser<'i>(i: &mut &'i str) -> ModalResult<Option<&'i str>> {
/// let prefix = opt("-").parse_next(i)?;
/// let condition = prefix.is_some();
/// cond(condition, alpha1).parse_next(i)
/// }
///
/// assert_eq!(parser.parse_peek("-abcd;"), Ok((";", Some("abcd"))));
/// assert_eq!(parser.parse_peek("abcd;"), Ok(("abcd;", None)));
/// assert!(parser.parse_peek("-123;").is_err());
/// assert_eq!(parser.parse_peek("123;"), Ok(("123;", None)));
/// # }
/// ```
pub fn cond<Input, Output, Error, ParseNext>(
cond: bool,
mut parser: ParseNext,
) -> impl Parser<Input, Option<Output>, Error>
where
Input: Stream,
ParseNext: Parser<Input, Output, Error>,
Error: ParserError<Input>,
{
trace("cond", move |input: &mut Input| {
if cond {
parser.parse_next(input).map(Some)
} else {
Ok(None)
}
})
}
/// Apply the parser without advancing the input.
///
/// To lookahead and only advance on success, see [`opt`].
///
/// # Example
///
/// ```rust
/// # #[cfg(feature = "ascii")] {
/// # use winnow::prelude::*;
/// use winnow::combinator::peek;
/// use winnow::ascii::alpha1;
///
/// fn parser<'i>(input: &mut &'i str) -> ModalResult<&'i str> {
/// peek(alpha1).parse_next(input)
/// }
///
/// assert_eq!(parser.parse_peek("abcd;"), Ok(("abcd;", "abcd")));
/// assert!(parser.parse_peek("123;").is_err());
/// # }
/// ```
#[doc(alias = "look_ahead")]
#[doc(alias = "rewind")]
pub fn peek<Input, Output, Error, ParseNext>(
mut parser: ParseNext,
) -> impl Parser<Input, Output, Error>
where
Input: Stream,
Error: ParserError<Input>,
ParseNext: Parser<Input, Output, Error>,
{
trace("peek", move |input: &mut Input| {
let start = input.checkpoint();
let res = parser.parse_next(input);
input.reset(&start);
res
})
}
/// Match the end of the [`Stream`]
///
/// Otherwise, it will error.
///
/// # Effective Signature
///
/// Assuming you are parsing a `&str` [Stream]:
/// ```rust
/// # use winnow::prelude::*;;
/// pub fn eof<'i>(input: &mut &'i str) -> ModalResult<&'i str>
/// # {
/// # winnow::combinator::eof.parse_next(input)
/// # }
/// ```
///
/// # Example
///
/// ```rust
/// # use std::str;
/// # use winnow::combinator::eof;
/// # use winnow::prelude::*;
///
/// fn parser<'i>(input: &mut &'i str) -> ModalResult<&'i str> {
/// eof.parse_next(input)
/// }
/// assert!(parser.parse_peek("abc").is_err());
/// assert_eq!(parser.parse_peek(""), Ok(("", "")));
/// ```
#[doc(alias = "end")]
#[doc(alias = "eoi")]
pub fn eof<Input, Error>(input: &mut Input) -> Result<<Input as Stream>::Slice, Error>
where
Input: Stream,
Error: ParserError<Input>,
{
trace("eof", move |input: &mut Input| {
if input.eof_offset() == 0 {
Ok(input.next_slice(0))
} else {
Err(ParserError::from_input(input))
}
})
.parse_next(input)
}
/// Succeeds if the child parser returns an error.
///
/// <div class="warning">
///
/// **Note:** This does not advance the [`Stream`]
///
/// </div>
///
/// # Example
///
/// ```rust
/// # #[cfg(feature = "ascii")] {
/// # use winnow::prelude::*;
/// use winnow::combinator::not;
/// use winnow::ascii::alpha1;
///
/// fn parser<'i>(input: &mut &'i str) -> ModalResult<()> {
/// not(alpha1).parse_next(input)
/// }
///
/// assert_eq!(parser.parse_peek("123"), Ok(("123", ())));
/// assert!(parser.parse_peek("abcd").is_err());
/// # }
/// ```
pub fn not<Input, Output, Error, ParseNext>(mut parser: ParseNext) -> impl Parser<Input, (), Error>
where
Input: Stream,
Error: ParserError<Input>,
ParseNext: Parser<Input, Output, Error>,
{
trace("not", move |input: &mut Input| {
let start = input.checkpoint();
let res = parser.parse_next(input);
input.reset(&start);
match res {
Ok(_) => Err(ParserError::from_input(input)),
Err(e) if e.is_backtrack() => Ok(()),
Err(e) => Err(e),
}
})
}
/// Transforms an [`ErrMode::Backtrack`][crate::error::ErrMode::Backtrack] (recoverable) to [`ErrMode::Cut`][crate::error::ErrMode::Cut] (unrecoverable)
///
/// This commits the parse result, preventing alternative branch paths like with
/// [`winnow::combinator::alt`][crate::combinator::alt].
///
/// See the [tutorial][crate::_tutorial::chapter_7] for more details.
///
/// # Example
///
/// Without `cut_err`:
/// ```rust
/// # #[cfg(feature = "ascii")] {
/// # use winnow::token::one_of;
/// # use winnow::token::rest;
/// # use winnow::ascii::digit1;
/// # use winnow::combinator::alt;
/// # use winnow::combinator::preceded;
/// # use winnow::prelude::*;
///
/// fn parser<'i>(input: &mut &'i str) -> ModalResult<&'i str> {
/// alt((
/// preceded(one_of(['+', '-']), digit1),
/// rest
/// )).parse_next(input)
/// }
///
/// assert_eq!(parser.parse_peek("+10 ab"), Ok((" ab", "10")));
/// assert_eq!(parser.parse_peek("ab"), Ok(("", "ab")));
/// assert_eq!(parser.parse_peek("+"), Ok(("", "+")));
/// # }
/// ```
///
/// With `cut_err`:
/// ```rust
/// # #[cfg(feature = "ascii")] {
/// # use winnow::{error::ErrMode, error::ContextError};
/// # use winnow::prelude::*;
/// # use winnow::token::one_of;
/// # use winnow::token::rest;
/// # use winnow::ascii::digit1;
/// # use winnow::combinator::alt;
/// # use winnow::combinator::preceded;
/// use winnow::combinator::cut_err;
///
/// fn parser<'i>(input: &mut &'i str) -> ModalResult<&'i str> {
/// alt((
/// preceded(one_of(['+', '-']), cut_err(digit1)),
/// rest
/// )).parse_next(input)
/// }
///
/// assert_eq!(parser.parse_peek("+10 ab"), Ok((" ab", "10")));
/// assert_eq!(parser.parse_peek("ab"), Ok(("", "ab")));
/// assert_eq!(parser.parse_peek("+"), Err(ErrMode::Cut(ContextError::new())));
/// # }
/// ```
pub fn cut_err<Input, Output, Error, ParseNext>(
mut parser: ParseNext,
) -> impl Parser<Input, Output, Error>
where
Input: Stream,
Error: ParserError<Input> + ModalError,
ParseNext: Parser<Input, Output, Error>,
{
trace("cut_err", move |input: &mut Input| {
parser.parse_next(input).map_err(|e| e.cut())
})
}
/// Transforms an [`ErrMode::Cut`][crate::error::ErrMode::Cut] (unrecoverable) to [`ErrMode::Backtrack`][crate::error::ErrMode::Backtrack] (recoverable)
///
/// This attempts the parse, allowing other parsers to be tried on failure, like with
/// [`winnow::combinator::alt`][crate::combinator::alt].
pub fn backtrack_err<Input, Output, Error, ParseNext>(
mut parser: ParseNext,
) -> impl Parser<Input, Output, Error>
where
Input: Stream,
Error: ParserError<Input> + ModalError,
ParseNext: Parser<Input, Output, Error>,
{
trace("backtrack_err", move |input: &mut Input| {
parser.parse_next(input).map_err(|e| e.backtrack())
})
}
/// A placeholder for a not-yet-implemented [`Parser`]
///
/// This is analogous to the [`todo!`] macro and helps with prototyping.
///
/// # Panic
///
/// This will panic when parsing
///
/// # Example
///
/// ```rust
/// # use winnow::prelude::*;
/// # use winnow::combinator::todo;
///
/// fn parser(input: &mut &str) -> ModalResult<u64> {
/// todo(input)
/// }
/// ```
#[track_caller]
pub fn todo<Input, Output, Error>(input: &mut Input) -> Result<Output, Error>
where
Input: Stream,
Error: ParserError<Input>,
{
#![allow(clippy::todo)]
trace("todo", move |_input: &mut Input| {
todo!("unimplemented parse")
})
.parse_next(input)
}
/// Succeed, consuming no input
///
/// For example, it can be used as the last alternative in `alt` to
/// specify the default case.
///
/// Useful with:
/// - [`Parser::value`]
/// - [`Parser::default_value`]
/// - [`Parser::map`]
///
/// <div class="warning">
///
/// **Note:** This never advances the [`Stream`]
///
/// </div>
///
/// # Example
///
/// ```rust
/// # use winnow::prelude::*;
/// use winnow::combinator::alt;
/// use winnow::combinator::empty;
///
/// fn sign(input: &mut &str) -> ModalResult<isize> {
/// alt((
/// '-'.value(-1),
/// '+'.value(1),
/// empty.value(1)
/// )).parse_next(input)
/// }
/// assert_eq!(sign.parse_peek("+10"), Ok(("10", 1)));
/// assert_eq!(sign.parse_peek("-10"), Ok(("10", -1)));
/// assert_eq!(sign.parse_peek("10"), Ok(("10", 1)));
/// ```
#[doc(alias = "value")]
#[doc(alias = "success")]
#[inline]
pub fn empty<Input, Error>(_input: &mut Input) -> Result<(), Error>
where
Input: Stream,
Error: ParserError<Input>,
{
Ok(())
}
/// A parser which always fails.
///
/// For example, it can be used as the last alternative in `alt` to
/// control the error message given.
///
/// # Example
///
/// ```rust
/// # use winnow::{error::ErrMode, error::InputError};
/// # use winnow::prelude::*;
/// use winnow::combinator::fail;
///
/// fn parser<'i>(input: &mut &'i str) -> ModalResult<(), InputError<&'i str>> {
/// fail.parse_next(input)
/// }
///
/// assert_eq!(parser.parse_peek("string"), Err(ErrMode::Backtrack(InputError::at("string"))));
/// ```
#[doc(alias = "unexpected")]
#[inline]
pub fn fail<Input, Output, Error>(i: &mut Input) -> Result<Output, Error>
where
Input: Stream,
Error: ParserError<Input>,
{
trace("fail", |i: &mut Input| Err(ParserError::from_input(i))).parse_next(i)
}
+298
View File
@@ -0,0 +1,298 @@
#![cfg(feature = "std")]
use std::io::Write;
use crate::error::ParserError;
use crate::stream::Stream;
use crate::{Parser, Result};
pub(crate) struct Trace<P, D, I, O, E>
where
P: Parser<I, O, E>,
I: Stream,
D: std::fmt::Display,
E: ParserError<I>,
{
parser: P,
name: D,
call_count: usize,
marker: core::marker::PhantomData<(I, O, E)>,
}
impl<P, D, I, O, E> Trace<P, D, I, O, E>
where
P: Parser<I, O, E>,
I: Stream,
D: std::fmt::Display,
E: ParserError<I>,
{
#[inline(always)]
pub(crate) fn new(parser: P, name: D) -> Self {
Self {
parser,
name,
call_count: 0,
marker: Default::default(),
}
}
}
impl<P, D, I, O, E> Parser<I, O, E> for Trace<P, D, I, O, E>
where
P: Parser<I, O, E>,
I: Stream,
D: std::fmt::Display,
E: ParserError<I>,
{
#[inline]
fn parse_next(&mut self, i: &mut I) -> Result<O, E> {
let depth = Depth::new();
let original = i.checkpoint();
start(*depth, &self.name, self.call_count, i);
let res = self.parser.parse_next(i);
let consumed = i.offset_from(&original);
let severity = Severity::with_result(&res);
end(*depth, &self.name, self.call_count, consumed, severity);
self.call_count += 1;
res
}
}
pub(crate) struct Depth {
depth: usize,
inc: bool,
}
impl Depth {
pub(crate) fn new() -> Self {
let depth = DEPTH.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
let inc = true;
Self { depth, inc }
}
pub(crate) fn existing() -> Self {
let depth = DEPTH.load(std::sync::atomic::Ordering::SeqCst);
let inc = false;
Self { depth, inc }
}
}
impl Drop for Depth {
fn drop(&mut self) {
if self.inc {
let _ = DEPTH.fetch_sub(1, std::sync::atomic::Ordering::SeqCst);
}
}
}
impl AsRef<usize> for Depth {
#[inline(always)]
fn as_ref(&self) -> &usize {
&self.depth
}
}
impl core::ops::Deref for Depth {
type Target = usize;
#[inline(always)]
fn deref(&self) -> &Self::Target {
&self.depth
}
}
static DEPTH: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
pub(crate) enum Severity {
Success,
Backtrack,
Cut,
Incomplete,
}
impl Severity {
pub(crate) fn with_result<T, I: Stream, E: ParserError<I>>(result: &Result<T, E>) -> Self {
match result {
Ok(_) => Self::Success,
Err(e) if e.is_backtrack() => Self::Backtrack,
Err(e) if e.is_incomplete() => Self::Incomplete,
_ => Self::Cut,
}
}
}
pub(crate) fn start<I: Stream>(
depth: usize,
name: &dyn core::fmt::Display,
count: usize,
input: &I,
) {
let gutter_style = anstyle::Style::new().bold();
let input_style = anstyle::Style::new().underline();
let eof_style = anstyle::Style::new().fg_color(Some(anstyle::AnsiColor::Cyan.into()));
let (call_width, input_width) = column_widths();
let count = if 0 < count {
format!(":{count}")
} else {
"".to_owned()
};
let call_column = format!("{:depth$}> {name}{count}", "");
// The debug version of `slice` might be wider, either due to rendering one byte as two nibbles or
// escaping in strings.
let mut debug_slice = format!("{:?}", crate::util::from_fn(|f| input.trace(f)));
let (debug_slice, eof) = if let Some(debug_offset) = debug_slice
.char_indices()
.enumerate()
.find_map(|(pos, (offset, _))| (input_width <= pos).then_some(offset))
{
debug_slice.truncate(debug_offset);
let eof = "";
(debug_slice, eof)
} else {
let eof = if debug_slice.chars().count() < input_width {
"∅"
} else {
""
};
(debug_slice, eof)
};
let writer = anstream::stderr();
let mut writer = writer.lock();
let _ = writeln!(
writer,
"{call_column:call_width$} {gutter_style}|{gutter_reset} {input_style}{debug_slice}{input_reset}{eof_style}{eof}{eof_reset}",
gutter_style=gutter_style.render(),
gutter_reset=gutter_style.render_reset(),
input_style=input_style.render(),
input_reset=input_style.render_reset(),
eof_style=eof_style.render(),
eof_reset=eof_style.render_reset(),
);
}
pub(crate) fn end(
depth: usize,
name: &dyn core::fmt::Display,
count: usize,
consumed: usize,
severity: Severity,
) {
let gutter_style = anstyle::Style::new().bold();
let (call_width, _) = column_widths();
let count = if 0 < count {
format!(":{count}")
} else {
"".to_owned()
};
let call_column = format!("{:depth$}< {name}{count}", "");
let (status_style, status) = match severity {
Severity::Success => {
let style = anstyle::Style::new().fg_color(Some(anstyle::AnsiColor::Green.into()));
let status = format!("+{consumed}");
(style, status)
}
Severity::Backtrack => (
anstyle::Style::new().fg_color(Some(anstyle::AnsiColor::Yellow.into())),
"backtrack".to_owned(),
),
Severity::Cut => (
anstyle::Style::new().fg_color(Some(anstyle::AnsiColor::Red.into())),
"cut".to_owned(),
),
Severity::Incomplete => (
anstyle::Style::new().fg_color(Some(anstyle::AnsiColor::Red.into())),
"incomplete".to_owned(),
),
};
let writer = anstream::stderr();
let mut writer = writer.lock();
let _ = writeln!(
writer,
"{status_style}{call_column:call_width$}{status_reset} {gutter_style}|{gutter_reset} {status_style}{status}{status_reset}",
gutter_style=gutter_style.render(),
gutter_reset=gutter_style.render_reset(),
status_style=status_style.render(),
status_reset=status_style.render_reset(),
);
}
pub(crate) fn result(depth: usize, name: &dyn core::fmt::Display, severity: Severity) {
let gutter_style = anstyle::Style::new().bold();
let (call_width, _) = column_widths();
let call_column = format!("{:depth$}| {name}", "");
let (status_style, status) = match severity {
Severity::Success => (
anstyle::Style::new().fg_color(Some(anstyle::AnsiColor::Green.into())),
"",
),
Severity::Backtrack => (
anstyle::Style::new().fg_color(Some(anstyle::AnsiColor::Yellow.into())),
"backtrack",
),
Severity::Cut => (
anstyle::Style::new().fg_color(Some(anstyle::AnsiColor::Red.into())),
"cut",
),
Severity::Incomplete => (
anstyle::Style::new().fg_color(Some(anstyle::AnsiColor::Red.into())),
"incomplete",
),
};
let writer = anstream::stderr();
let mut writer = writer.lock();
let _ = writeln!(
writer,
"{status_style}{call_column:call_width$}{status_reset} {gutter_style}|{gutter_reset} {status_style}{status}{status_reset}",
gutter_style=gutter_style.render(),
gutter_reset=gutter_style.render_reset(),
status_style=status_style.render(),
status_reset=status_style.render_reset(),
);
}
fn column_widths() -> (usize, usize) {
let term_width = term_width();
let min_call_width = 40;
let min_input_width = 20;
let decor_width = 3;
let extra_width = term_width.saturating_sub(min_call_width + min_input_width + decor_width);
let call_width = min_call_width + 2 * extra_width / 3;
let input_width = min_input_width + extra_width / 3;
(call_width, input_width)
}
fn term_width() -> usize {
columns_env().or_else(query_width).unwrap_or(80)
}
fn query_width() -> Option<usize> {
use is_terminal_polyfill::IsTerminal;
if std::io::stderr().is_terminal() {
terminal_size::terminal_size().map(|(w, _h)| w.0.into())
} else {
None
}
}
fn columns_env() -> Option<usize> {
std::env::var("COLUMNS")
.ok()
.and_then(|c| c.parse::<usize>().ok())
}
+96
View File
@@ -0,0 +1,96 @@
#![cfg_attr(feature = "debug", allow(clippy::std_instead_of_core))]
#[cfg(feature = "debug")]
mod internals;
use crate::error::ParserError;
use crate::stream::Stream;
use crate::Parser;
/// Trace the execution of the parser
///
/// Note that [`Parser::context`] also provides high level trace information.
///
/// See [tutorial][crate::_tutorial::chapter_8] for more details.
///
/// # Example
///
/// ```rust
/// # use winnow::{error::ErrMode, error::Needed};
/// # use winnow::token::take_while;
/// # use winnow::stream::AsChar;
/// # use winnow::prelude::*;
/// use winnow::combinator::trace;
///
/// fn short_alpha<'s>(s: &mut &'s [u8]) -> ModalResult<&'s [u8]> {
/// trace("short_alpha",
/// take_while(3..=6, AsChar::is_alpha)
/// ).parse_next(s)
/// }
///
/// assert_eq!(short_alpha.parse_peek(b"latin123"), Ok((&b"123"[..], &b"latin"[..])));
/// assert_eq!(short_alpha.parse_peek(b"lengthy"), Ok((&b"y"[..], &b"length"[..])));
/// assert_eq!(short_alpha.parse_peek(b"latin"), Ok((&b""[..], &b"latin"[..])));
/// assert!(short_alpha.parse_peek(b"ed").is_err());
/// assert!(short_alpha.parse_peek(b"12345").is_err());
/// ```
#[cfg_attr(not(feature = "debug"), allow(unused_variables))]
#[cfg_attr(not(feature = "debug"), allow(unused_mut))]
#[cfg_attr(not(feature = "debug"), inline(always))]
pub fn trace<I: Stream, O, E: ParserError<I>>(
name: impl core::fmt::Display,
parser: impl Parser<I, O, E>,
) -> impl Parser<I, O, E> {
#[cfg(feature = "debug")]
{
internals::Trace::new(parser, name)
}
#[cfg(not(feature = "debug"))]
{
parser
}
}
#[cfg_attr(not(feature = "debug"), allow(unused_variables))]
pub(crate) fn trace_result<T, I: Stream, E: ParserError<I>>(
name: impl core::fmt::Display,
res: &Result<T, E>,
) {
#[cfg(feature = "debug")]
{
let depth = internals::Depth::existing();
let severity = internals::Severity::with_result(res);
internals::result(*depth, &name, severity);
}
}
pub(crate) struct DisplayDebug<D>(pub(crate) D);
impl<D: core::fmt::Debug> core::fmt::Display for DisplayDebug<D> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{:?}", self.0)
}
}
#[test]
#[cfg(feature = "std")]
#[cfg_attr(miri, ignore)]
#[cfg(unix)]
#[cfg(feature = "debug")]
fn example() {
use term_transcript::{test::TestConfig, ShellOptions};
let path = snapbox::cmd::compile_example("string", ["--features=debug"]).unwrap();
let current_dir = path.parent().unwrap();
let cmd = path.file_name().unwrap();
// HACK: term_transcript doesn't allow non-UTF8 paths
let cmd = format!("./{}", cmd.to_string_lossy());
TestConfig::new(
ShellOptions::default()
.with_current_dir(current_dir)
.with_env("CLICOLOR_FORCE", "1"),
)
.test("assets/trace.svg", [format!(r#"{cmd} '"abc"'"#).as_str()]);
}
+561
View File
@@ -0,0 +1,561 @@
use crate::combinator::empty;
use crate::combinator::fail;
use crate::combinator::opt;
use crate::combinator::trace;
use crate::error::ParserError;
use crate::stream::Stream;
use crate::stream::StreamIsPartial;
use crate::Parser;
use crate::Result;
/// Parses an expression based on operator precedence.
///
/// It uses a Pratt parsing algorithm, where operators are
/// associated with a binding power. The higher the power,
/// the more tightly an operator will bind to its operands.
///
/// This method returns an [`Expression`], which configures
/// the Pratt parser.
///
/// Each operator type is configured with [`Prefix`], [`Postfix`],
/// and [`Infix`]. These describe the operator's binding power,
/// a function that applies the operator to its operand, and the
/// operator's associativity (infix only).
///
/// For a more full-featured example, look at the [C-style Expressions][crate::_topic::language#c-style-expressions]
/// topic.
///
/// # Example
///
/// Parsing a simple arithmetic expression without parenthesis.
///
/// ```rust
/// # #[cfg(feature = "ascii")] {
/// # use winnow::prelude::*;
/// # use winnow::error::ContextError;
/// # use winnow::ascii::digit1;
/// # use winnow::combinator::{dispatch, fail};
/// # use winnow::token::any;
/// use winnow::combinator::expression;
/// use winnow::combinator::{Prefix, Postfix, Infix};
///
/// fn parser<'i>() -> impl Parser<&'i str, i32, ContextError> {
/// move |i: &mut &str| {
/// use Infix::*;
/// expression(digit1.parse_to::<i32>()) // operands are 32-bit integers
/// .prefix(dispatch! {any;
/// '-' => Prefix(12, |_, a: i32| Ok(-a)),
/// _ => fail,
/// })
/// .infix(dispatch! {any;
/// '+' => Left(5, |_, a, b| Ok(a + b)),
/// '-' => Left(5, |_, a, b| Ok(a - b)),
/// '*' => Left(7, |_, a, b| Ok(a * b)),
/// '/' => Left(7, |_, a: i32, b| Ok(a.checked_div(b).unwrap_or_default())),
/// _ => fail,
/// })
/// .postfix(dispatch! {any;
/// '!' => Postfix(15, |_, a| if a < 1 { Ok(1) } else { Ok((1..=a).fold(1, |acc, a| acc*a)) }),
/// _ => fail,
/// })
/// .parse_next(i)
/// }
/// }
///
/// assert_eq!(parser().parse("1+1"), Ok(2));
/// assert_eq!(parser().parse("0!"), Ok(1));
/// assert_eq!(parser().parse("-1*5*2*10+30/3!"), Ok(-95));
/// # }
/// ```
#[doc(alias = "pratt")]
#[doc(alias = "separated")]
#[doc(alias = "shunting_yard")]
#[doc(alias = "precedence_climbing")]
#[inline(always)]
#[allow(clippy::type_complexity)]
pub fn expression<I, ParseOperand, O, E>(
parse_operand: ParseOperand,
) -> Expression<
I,
O,
ParseOperand,
impl Parser<I, Prefix<I, O, E>, E>,
impl Parser<I, Postfix<I, O, E>, E>,
impl Parser<I, Infix<I, O, E>, E>,
E,
>
where
I: Stream + StreamIsPartial,
ParseOperand: Parser<I, O, E>,
E: ParserError<I>,
{
Expression {
precedence_level: 0,
parse_operand,
parse_prefix: fail,
parse_postfix: fail,
parse_infix: fail,
marker: Default::default(),
}
}
/// A helper struct for [`expression()`].
///
/// Holds the configuration for the Pratt parser, including
/// the operator and operand parsers. A precedence level can
/// also be set, which is useful to disambiguate parse trees
/// based on the parent operator's precedence.
///
/// Implements [`Parser`]. When parsing an input, it applies
/// the Pratt parser.
pub struct Expression<I, O, ParseOperand, Pre, Post, Pix, E>
where
I: Stream + StreamIsPartial,
ParseOperand: Parser<I, O, E>,
E: ParserError<I>,
{
precedence_level: i64,
parse_operand: ParseOperand,
parse_prefix: Pre,
parse_postfix: Post,
parse_infix: Pix,
marker: core::marker::PhantomData<(I, O, E)>,
}
impl<I, O, ParseOperand, Pre, Post, Pix, E> Expression<I, O, ParseOperand, Pre, Post, Pix, E>
where
ParseOperand: Parser<I, O, E>,
I: Stream + StreamIsPartial,
E: ParserError<I>,
{
/// Sets the prefix operator parser.
///
/// The parser should parse the input to a [`Prefix`],
/// which contains the operator's binding power and
/// a fold function which applies the operator to its
/// operands.
#[inline(always)]
pub fn prefix<NewParsePrefix>(
self,
parser: NewParsePrefix,
) -> Expression<I, O, ParseOperand, NewParsePrefix, Post, Pix, E>
where
NewParsePrefix: Parser<I, Prefix<I, O, E>, E>,
{
Expression {
precedence_level: self.precedence_level,
parse_operand: self.parse_operand,
parse_prefix: parser,
parse_postfix: self.parse_postfix,
parse_infix: self.parse_infix,
marker: Default::default(),
}
}
/// Sets the postfix operator parser.
///
/// The parser should parse the input to a [`Postfix`],
/// which contains the operator's binding power and
/// a fold function which applies the operator to its
/// operands.
#[inline(always)]
pub fn postfix<NewParsePostfix>(
self,
parser: NewParsePostfix,
) -> Expression<I, O, ParseOperand, Pre, NewParsePostfix, Pix, E>
where
NewParsePostfix: Parser<I, Postfix<I, O, E>, E>,
{
Expression {
precedence_level: self.precedence_level,
parse_operand: self.parse_operand,
parse_prefix: self.parse_prefix,
parse_postfix: parser,
parse_infix: self.parse_infix,
marker: Default::default(),
}
}
/// Sets the infix operator parser.
///
/// The parser should parse the input to a [`Infix`],
/// which contains the operator's binding power and
/// a fold function which applies the operator to its
/// operands.
#[inline(always)]
pub fn infix<NewParseInfix>(
self,
parser: NewParseInfix,
) -> Expression<I, O, ParseOperand, Pre, Post, NewParseInfix, E>
where
NewParseInfix: Parser<I, Infix<I, O, E>, E>,
{
Expression {
precedence_level: self.precedence_level,
parse_operand: self.parse_operand,
parse_prefix: self.parse_prefix,
parse_postfix: self.parse_postfix,
parse_infix: parser,
marker: Default::default(),
}
}
/// Sets the precedence level for the current instance of the parser.
///
/// It defaults to 0, which is traditionally treated as the "lowest"
/// possible precedence when parsing an expression.
///
/// This is useful to disambiguate grammars based on the parent operator's
/// precedence. This comes up primarily when parsing recursive expressions.
///
/// The parsing machinery underpinning [`Expression`] assumes that a "more
/// tightly binding" operator is numerically large, while a "more loosely
/// binding" operator is numerically small. For example, `13` is a higher
/// precedence level than `1` because `13 > 1`.
///
/// Other ways of describing this relationship:
/// - `13` has a higher precedence compared to `1`
/// - `13` has a higher binding power compared to `1`
///
/// Note: Binding power and precedence both refer to the same concept and
/// may be used interchangeably.
///
/// # Motivation
///
/// If you don't understand why this is useful to have, this section tries
/// to explain in more detail.
///
/// The [C-style Expressions][crate::_topic::arithmetic#c-style-expression]
/// example has source code for parsing the expression described below, and
/// can provide a clearer usage example.
///
/// Consider the following expression in the C language:
///
/// ```c
/// int x = (1 == 1 ? 0 : 1, -123); // <-- let's parse this
/// printf("%d\n", x); // -123
/// ```
///
/// Let's look at the right-hand side of the expression on the first line,
/// and replace some of the sub-expressions with symbols:
///
/// ```text
/// (1 == 1 ? 0 : 1, -123) // rhs
/// (a ? b : c, d ) // symbolic
/// (a ? b : c, d) // remove whitespace
/// (, (? a b c) d) // prefix notation
/// ```
///
/// Written symbolically:
/// - `a` is the condition, like `1 == 1`
/// - `b` is the value when the condition is true
/// - `c` is the value when the condition is false
/// - `d` is a secondary expression unrelated to the ternary
///
/// In prefix notation, it's easier to see the specific operators and what
/// they bind to:
/// - COMMA (`,`) binds to `(? a b c)` and `d`
/// - TERNARY (`?`) binds to `a`, `b`, and `c`
///
/// ## Parsing `c` and `d`
///
/// Let's focus on parsing the sub-expressions `c` and `d`, as that
/// motivates why a parser precedence level is necessary.
///
/// To parse `c`, we would really like to re-use the parser produced by
/// [`expression()`], because `c` is really *any* valid expression that
/// can be parsed by `expression()` already.
///
/// However, we can't re-use the parser naively. When parsing `c`, we need
/// to "escape" from the inner parser when encountering the comma separating
/// `c` from `d`.
///
/// The reason we have to "escape" is because of how operator precedence is
/// defined in the C language: the comma operator has the lowest precedence
/// among all the operators. When we're parsing `c`, we're in the context of
/// the ternary operator. We don't want to parse any valid expression! Just
/// what the ternary operator captures.
///
/// That's where the precedence level comes in: you specify the minimum
/// precedence this parser is willing to accept. If you come across an
/// expression in the top-level with a lower binding power than the starting
/// precedence, you know to stop parsing.
///
/// The parsing machinery inside of [`Expression`] handles most of this for
/// you, but it can't determine what the precedence level should be for a
/// given expression. That's a language-specific detail, and it depends on
/// what you want to parse.
#[inline(always)]
pub fn current_precedence_level(
mut self,
level: i64,
) -> Expression<I, O, ParseOperand, Pre, Post, Pix, E> {
self.precedence_level = level;
self
}
}
impl<I, O, Pop, Pre, Post, Pix, E> Parser<I, O, E> for Expression<I, O, Pop, Pre, Post, Pix, E>
where
I: Stream + StreamIsPartial,
Pop: Parser<I, O, E>,
Pix: Parser<I, Infix<I, O, E>, E>,
Pre: Parser<I, Prefix<I, O, E>, E>,
Post: Parser<I, Postfix<I, O, E>, E>,
E: ParserError<I>,
{
#[inline(always)]
fn parse_next(&mut self, input: &mut I) -> Result<O, E> {
trace("expression", move |i: &mut I| {
expression_impl(
i,
&mut self.parse_operand,
&mut self.parse_prefix,
&mut self.parse_postfix,
&mut self.parse_infix,
self.precedence_level,
)
})
.parse_next(input)
}
}
/// Opaque implementation of the Pratt parser.
fn expression_impl<I, O, Pop, Pre, Post, Pix, E>(
i: &mut I,
parse_operand: &mut Pop,
prefix: &mut Pre,
postfix: &mut Post,
infix: &mut Pix,
min_power: i64,
) -> Result<O, E>
where
I: Stream + StreamIsPartial,
Pop: Parser<I, O, E>,
Pix: Parser<I, Infix<I, O, E>, E>,
Pre: Parser<I, Prefix<I, O, E>, E>,
Post: Parser<I, Postfix<I, O, E>, E>,
E: ParserError<I>,
{
let operand = opt(trace("operand", parse_operand.by_ref())).parse_next(i)?;
let mut operand = if let Some(operand) = operand {
operand
} else {
// Prefix unary operators
let len = i.eof_offset();
let Prefix(power, fold_prefix) = trace("prefix", prefix.by_ref()).parse_next(i)?;
// infinite loop check: the parser must always consume
if i.eof_offset() == len {
return Err(E::assert(i, "`prefix` parsers must always consume"));
}
let operand = expression_impl(i, parse_operand, prefix, postfix, infix, power)?;
fold_prefix(i, operand)?
};
// A variable to stop the `'parse` loop when `Assoc::Neither` with the same
// precedence is encountered e.g. `a == b == c`. `Assoc::Neither` has similar
// associativity rules as `Assoc::Left`, but we stop parsing when the next operator
// is the same as the current one.
let mut prev_op_is_neither = None;
'parse: while i.eof_offset() > 0 {
// Postfix unary operators
let start = i.checkpoint();
if let Some(Postfix(power, fold_postfix)) =
opt(trace("postfix", postfix.by_ref())).parse_next(i)?
{
// control precedence over the prefix e.g.:
// `--(i++)` or `(--i)++`
if power < min_power {
i.reset(&start);
break 'parse;
}
operand = fold_postfix(i, operand)?;
continue 'parse;
}
// Infix binary operators
let start = i.checkpoint();
let parse_result = opt(trace("infix", infix.by_ref())).parse_next(i)?;
if let Some(infix_op) = parse_result {
let mut is_neither = None;
let (lpower, rpower, fold_infix) = match infix_op {
Infix::Right(p, f) => (p, p - 1, f),
Infix::Left(p, f) => (p, p + 1, f),
Infix::Neither(p, f) => {
is_neither = Some(p);
(p, p + 1, f)
}
};
if lpower < min_power
// MSRV: `is_some_and`
|| match prev_op_is_neither {
None => false,
Some(p) => lpower == p,
}
{
i.reset(&start);
break 'parse;
}
prev_op_is_neither = is_neither;
let rhs = expression_impl(i, parse_operand, prefix, postfix, infix, rpower)?;
operand = fold_infix(i, operand, rhs)?;
continue 'parse;
}
break 'parse;
}
Ok(operand)
}
/// Define an [`expression()`]'s prefix operator
///
/// It requires an operator binding power, as well as a
/// fold function which applies the operator.
pub struct Prefix<I, O, E>(
/// Binding power
pub i64,
/// Unary operator
pub fn(&mut I, O) -> Result<O, E>,
);
impl<I, O, E> Clone for Prefix<I, O, E> {
#[inline(always)]
fn clone(&self) -> Self {
Prefix(self.0, self.1)
}
}
impl<I: Stream, O, E: ParserError<I>> Parser<I, Prefix<I, O, E>, E> for Prefix<I, O, E> {
#[inline(always)]
fn parse_next(&mut self, input: &mut I) -> Result<Prefix<I, O, E>, E> {
empty.value(self.clone()).parse_next(input)
}
}
/// Define an [`expression()`]'s postfix operator
///
/// It requires an operator binding power, as well as a
/// fold function which applies the operator.
pub struct Postfix<I, O, E>(
/// Binding power
pub i64,
/// Unary operator
pub fn(&mut I, O) -> Result<O, E>,
);
impl<I, O, E> Clone for Postfix<I, O, E> {
#[inline(always)]
fn clone(&self) -> Self {
Postfix(self.0, self.1)
}
}
impl<I: Stream, O, E: ParserError<I>> Parser<I, Postfix<I, O, E>, E> for Postfix<I, O, E> {
#[inline(always)]
fn parse_next(&mut self, input: &mut I) -> Result<Postfix<I, O, E>, E> {
empty.value(self.clone()).parse_next(input)
}
}
/// Define an [`expression()`]'s infix operator
///
/// It requires an operator binding power, as well as a
/// fold function which applies the operator.
pub enum Infix<I, O, E> {
/// Left-associative operator
///
/// The operators will bind more tightly to their rightmost operands.
///
/// e.g `A op B op C` -> `(A op B) op C`
Left(
/// Binding power
i64,
/// Binary operator
fn(&mut I, O, O) -> Result<O, E>,
),
/// Right-associative operator
///
/// The operators will bind more tightly to their leftmost operands.
///
/// e.g `A op B op C` -> `A op (B op C)`
Right(
/// Binding power
i64,
/// Binary operator
fn(&mut I, O, O) -> Result<O, E>,
),
/// Neither left or right associative
///
/// `Infix::Neither` has similar associativity rules as `Assoc::Left`, but we stop
/// parsing when the next operator is the same as the current one.
///
/// e.g. `a == b == c` -> `(a == b)`, fail: `(== c)`
Neither(
/// Binding power
i64,
/// Binary operator
fn(&mut I, O, O) -> Result<O, E>,
),
}
impl<I, O, E> Clone for Infix<I, O, E> {
#[inline(always)]
fn clone(&self) -> Self {
match self {
Infix::Left(p, f) => Infix::Left(*p, *f),
Infix::Right(p, f) => Infix::Right(*p, *f),
Infix::Neither(p, f) => Infix::Neither(*p, *f),
}
}
}
impl<I: Stream, O, E: ParserError<I>> Parser<I, Infix<I, O, E>, E> for Infix<I, O, E> {
#[inline(always)]
fn parse_next(&mut self, input: &mut I) -> Result<Infix<I, O, E>, E> {
empty.value(self.clone()).parse_next(input)
}
}
#[cfg(all(test, feature = "ascii"))]
mod tests {
use crate::ascii::digit1;
use crate::combinator::fail;
use crate::dispatch;
use crate::error::ContextError;
use crate::token::any;
use super::*;
fn parser<'i>() -> impl Parser<&'i str, i32, ContextError> {
move |i: &mut &str| {
use Infix::*;
expression(digit1.parse_to::<i32>())
.current_precedence_level(0)
.prefix(dispatch! {any;
'+' => Prefix(12, |_, a| Ok(a)),
'-' => Prefix(12, |_, a: i32| Ok(-a)),
_ => fail
})
.infix(dispatch! {any;
'+' => Left(5, |_, a, b| Ok(a + b)),
'-' => Left(5, |_, a, b| Ok(a - b)),
'*' => Left(7, |_, a, b| Ok(a * b)),
'/' => Left(7, |_, a, b| Ok(a / b)),
'%' => Left(7, |_, a, b| Ok(a % b)),
'^' => Left(9, |_, a, b| Ok(a ^ b)),
_ => fail
})
.parse_next(i)
}
}
#[test]
fn test_expression() {
assert_eq!(parser().parse("-3+-3*4"), Ok(-15));
assert_eq!(parser().parse("+2+3*4"), Ok(14));
assert_eq!(parser().parse("2*3+4"), Ok(10));
}
}
+794
View File
@@ -0,0 +1,794 @@
//! Opaque implementations of [`Parser`]
use crate::combinator::trace;
use crate::combinator::trace_result;
use crate::combinator::DisplayDebug;
#[cfg(feature = "unstable-recover")]
#[cfg(feature = "std")]
use crate::error::FromRecoverableError;
use crate::error::ParseError;
use crate::error::{AddContext, FromExternalError, ParserError};
#[cfg(feature = "unstable-recover")]
#[cfg(feature = "std")]
use crate::stream::Recover;
use crate::stream::StreamIsPartial;
use crate::stream::{Location, Stream};
use crate::{Parser, Result};
use core::borrow::Borrow;
use core::ops::Range;
/// Iterator implementation for [`Parser::parse_iter`]
pub struct ParseIter<'p, P, I, O, E>
where
I: Stream,
{
pub(crate) parser: &'p mut P,
pub(crate) input: Option<I>,
pub(crate) start: Option<I::Checkpoint>,
pub(crate) marker: core::marker::PhantomData<(O, E)>,
}
impl<'p, I, O, E, P> Iterator for ParseIter<'p, P, I, O, E>
where
P: Parser<I, O, E>,
I: Stream,
// Force users to deal with `Incomplete` when `StreamIsPartial<true>`
I: StreamIsPartial,
E: ParserError<I>,
<E as ParserError<I>>::Inner: ParserError<I>,
{
type Item = Result<O, ParseError<I, <E as ParserError<I>>::Inner>>;
fn next(&mut self) -> Option<Self::Item> {
let input = self.input.as_mut()?;
let len = input.eof_offset();
if len == 0 {
self.input = None;
return None;
}
let mut output = self.parser.parse_next(input);
// infinite loop check: the parser must always consume
if output.is_ok() && input.eof_offset() == len {
let err = <E as ParserError<I>>::assert(
input,
"`Parser::parse_iter` parsers must always consume",
);
output = Err(err);
}
match output {
Ok(output) => Some(Ok(output)),
Err(err) => {
let err = err.into_inner().unwrap_or_else(|_err| {
panic!("complete parsers should not report `ErrMode::Incomplete(_)`")
});
let input = self.input.take()?;
let start = self.start.take()?;
Some(Err(ParseError::new(input, start, err)))
}
}
}
}
/// [`Parser`] implementation for [`Parser::by_ref`]
pub struct ByRef<'p, P, I, O, E> {
pub(crate) p: &'p mut P,
pub(crate) marker: core::marker::PhantomData<(I, O, E)>,
}
impl<I, O, E, P> Parser<I, O, E> for ByRef<'_, P, I, O, E>
where
P: Parser<I, O, E>,
{
#[inline(always)]
fn parse_next(&mut self, i: &mut I) -> Result<O, E> {
self.p.parse_next(i)
}
}
/// [`Parser`] implementation for [`Parser::map`]
pub struct Map<F, G, I, O, O2, E>
where
F: Parser<I, O, E>,
G: FnMut(O) -> O2,
{
pub(crate) parser: F,
pub(crate) map: G,
pub(crate) marker: core::marker::PhantomData<(I, O, E, O2)>,
}
impl<F, G, I, O, O2, E> Parser<I, O2, E> for Map<F, G, I, O, O2, E>
where
F: Parser<I, O, E>,
G: FnMut(O) -> O2,
{
#[inline]
fn parse_next(&mut self, i: &mut I) -> Result<O2, E> {
match self.parser.parse_next(i) {
Err(e) => Err(e),
Ok(o) => Ok((self.map)(o)),
}
}
}
/// [`Parser`] implementation for [`Parser::try_map`]
pub struct TryMap<F, G, I, O, O2, E, E2>
where
F: Parser<I, O, E>,
G: FnMut(O) -> Result<O2, E2>,
I: Stream,
E: FromExternalError<I, E2>,
E: ParserError<I>,
{
pub(crate) parser: F,
pub(crate) map: G,
pub(crate) marker: core::marker::PhantomData<(I, O, O2, E, E2)>,
}
impl<F, G, I, O, O2, E, E2> Parser<I, O2, E> for TryMap<F, G, I, O, O2, E, E2>
where
F: Parser<I, O, E>,
G: FnMut(O) -> Result<O2, E2>,
I: Stream,
E: FromExternalError<I, E2>,
E: ParserError<I>,
{
#[inline]
fn parse_next(&mut self, input: &mut I) -> Result<O2, E> {
let start = input.checkpoint();
let o = self.parser.parse_next(input)?;
let res = (self.map)(o).map_err(|err| {
input.reset(&start);
E::from_external_error(input, err)
});
trace_result("verify", &res);
res
}
}
/// [`Parser`] implementation for [`Parser::verify_map`]
pub struct VerifyMap<F, G, I, O, O2, E>
where
F: Parser<I, O, E>,
G: FnMut(O) -> Option<O2>,
I: Stream,
E: ParserError<I>,
{
pub(crate) parser: F,
pub(crate) map: G,
pub(crate) marker: core::marker::PhantomData<(I, O, E, O2)>,
}
impl<F, G, I, O, O2, E> Parser<I, O2, E> for VerifyMap<F, G, I, O, O2, E>
where
F: Parser<I, O, E>,
G: FnMut(O) -> Option<O2>,
I: Stream,
E: ParserError<I>,
{
#[inline]
fn parse_next(&mut self, input: &mut I) -> Result<O2, E> {
let start = input.checkpoint();
let o = self.parser.parse_next(input)?;
let res = (self.map)(o).ok_or_else(|| {
input.reset(&start);
ParserError::from_input(input)
});
trace_result("verify", &res);
res
}
}
/// [`Parser`] implementation for [`Parser::and_then`]
pub struct AndThen<F, G, I, O, O2, E>
where
F: Parser<I, O, E>,
G: Parser<O, O2, E>,
O: StreamIsPartial,
I: Stream,
{
pub(crate) outer: F,
pub(crate) inner: G,
pub(crate) marker: core::marker::PhantomData<(I, O, O2, E)>,
}
impl<F, G, I, O, O2, E> Parser<I, O2, E> for AndThen<F, G, I, O, O2, E>
where
F: Parser<I, O, E>,
G: Parser<O, O2, E>,
O: StreamIsPartial,
I: Stream,
{
#[inline(always)]
fn parse_next(&mut self, i: &mut I) -> Result<O2, E> {
let start = i.checkpoint();
let mut o = self.outer.parse_next(i)?;
let _ = o.complete();
let o2 = self.inner.parse_next(&mut o).map_err(|err| {
i.reset(&start);
err
})?;
Ok(o2)
}
}
/// [`Parser`] implementation for [`Parser::parse_to`]
pub struct ParseTo<P, I, O, O2, E>
where
P: Parser<I, O, E>,
I: Stream,
O: crate::stream::ParseSlice<O2>,
E: ParserError<I>,
{
pub(crate) p: P,
pub(crate) marker: core::marker::PhantomData<(I, O, O2, E)>,
}
impl<P, I, O, O2, E> Parser<I, O2, E> for ParseTo<P, I, O, O2, E>
where
P: Parser<I, O, E>,
I: Stream,
O: crate::stream::ParseSlice<O2>,
E: ParserError<I>,
{
#[inline]
fn parse_next(&mut self, i: &mut I) -> Result<O2, E> {
let start = i.checkpoint();
let o = self.p.parse_next(i)?;
let res = o.parse_slice().ok_or_else(|| {
i.reset(&start);
ParserError::from_input(i)
});
trace_result("verify", &res);
res
}
}
/// [`Parser`] implementation for [`Parser::flat_map`]
pub struct FlatMap<F, G, H, I, O, O2, E>
where
F: Parser<I, O, E>,
G: FnMut(O) -> H,
H: Parser<I, O2, E>,
{
pub(crate) f: F,
pub(crate) g: G,
pub(crate) marker: core::marker::PhantomData<(H, I, O, O2, E)>,
}
impl<F, G, H, I, O, O2, E> Parser<I, O2, E> for FlatMap<F, G, H, I, O, O2, E>
where
F: Parser<I, O, E>,
G: FnMut(O) -> H,
H: Parser<I, O2, E>,
{
#[inline(always)]
fn parse_next(&mut self, i: &mut I) -> Result<O2, E> {
let o = self.f.parse_next(i)?;
(self.g)(o).parse_next(i)
}
}
/// [`Parser`] implementation for [`Parser::complete_err`]
pub struct CompleteErr<P, I, O, E> {
pub(crate) p: P,
pub(crate) marker: core::marker::PhantomData<(I, O, E)>,
}
impl<P, I, O, E> Parser<I, O, E> for CompleteErr<P, I, O, E>
where
P: Parser<I, O, E>,
I: Stream,
E: ParserError<I>,
{
#[inline]
fn parse_next(&mut self, input: &mut I) -> Result<O, E> {
trace("complete_err", |input: &mut I| {
match (self.p).parse_next(input) {
Err(err) => match err.needed() {
Some(_) => Err(ParserError::from_input(input)),
None => Err(err),
},
rest => rest,
}
})
.parse_next(input)
}
}
/// [`Parser`] implementation for [`Parser::verify`]
pub struct Verify<F, G, I, O, O2, E>
where
F: Parser<I, O, E>,
G: FnMut(&O2) -> bool,
I: Stream,
O: Borrow<O2>,
O2: ?Sized,
E: ParserError<I>,
{
pub(crate) parser: F,
pub(crate) filter: G,
pub(crate) marker: core::marker::PhantomData<(I, O, E, O2)>,
}
impl<F, G, I, O, O2, E> Parser<I, O, E> for Verify<F, G, I, O, O2, E>
where
F: Parser<I, O, E>,
G: FnMut(&O2) -> bool,
I: Stream,
O: Borrow<O2>,
O2: ?Sized,
E: ParserError<I>,
{
#[inline]
fn parse_next(&mut self, input: &mut I) -> Result<O, E> {
let start = input.checkpoint();
let o = self.parser.parse_next(input)?;
let res = (self.filter)(o.borrow()).then_some(o).ok_or_else(|| {
input.reset(&start);
ParserError::from_input(input)
});
trace_result("verify", &res);
res
}
}
/// [`Parser`] implementation for [`Parser::value`]
pub struct Value<F, I, O, O2, E>
where
F: Parser<I, O, E>,
O2: Clone,
{
pub(crate) parser: F,
pub(crate) val: O2,
pub(crate) marker: core::marker::PhantomData<(I, O, E)>,
}
impl<F, I, O, O2, E> Parser<I, O2, E> for Value<F, I, O, O2, E>
where
F: Parser<I, O, E>,
O2: Clone,
{
#[inline]
fn parse_next(&mut self, input: &mut I) -> Result<O2, E> {
(self.parser).parse_next(input).map(|_| self.val.clone())
}
}
/// [`Parser`] implementation for [`Parser::default_value`]
pub struct DefaultValue<F, I, O, O2, E>
where
F: Parser<I, O, E>,
O2: core::default::Default,
{
pub(crate) parser: F,
pub(crate) marker: core::marker::PhantomData<(O2, I, O, E)>,
}
impl<F, I, O, O2, E> Parser<I, O2, E> for DefaultValue<F, I, O, O2, E>
where
F: Parser<I, O, E>,
O2: core::default::Default,
{
#[inline]
fn parse_next(&mut self, input: &mut I) -> Result<O2, E> {
(self.parser).parse_next(input).map(|_| O2::default())
}
}
/// [`Parser`] implementation for [`Parser::void`]
pub struct Void<F, I, O, E>
where
F: Parser<I, O, E>,
{
pub(crate) parser: F,
pub(crate) marker: core::marker::PhantomData<(I, O, E)>,
}
impl<F, I, O, E> Parser<I, (), E> for Void<F, I, O, E>
where
F: Parser<I, O, E>,
{
#[inline(always)]
fn parse_next(&mut self, input: &mut I) -> Result<(), E> {
(self.parser).parse_next(input).map(|_| ())
}
}
/// [`Parser`] implementation for [`Parser::take`]
pub struct Take<F, I, O, E>
where
F: Parser<I, O, E>,
I: Stream,
{
pub(crate) parser: F,
pub(crate) marker: core::marker::PhantomData<(I, O, E)>,
}
impl<I, O, E, F> Parser<I, <I as Stream>::Slice, E> for Take<F, I, O, E>
where
F: Parser<I, O, E>,
I: Stream,
{
#[inline]
fn parse_next(&mut self, input: &mut I) -> Result<<I as Stream>::Slice, E> {
let checkpoint = input.checkpoint();
match (self.parser).parse_next(input) {
Ok(_) => {
let offset = input.offset_from(&checkpoint);
input.reset(&checkpoint);
let taken = input.next_slice(offset);
Ok(taken)
}
Err(e) => Err(e),
}
}
}
/// [`Parser`] implementation for [`Parser::with_taken`]
pub struct WithTaken<F, I, O, E>
where
F: Parser<I, O, E>,
I: Stream,
{
pub(crate) parser: F,
pub(crate) marker: core::marker::PhantomData<(I, O, E)>,
}
impl<F, I, O, E> Parser<I, (O, <I as Stream>::Slice), E> for WithTaken<F, I, O, E>
where
F: Parser<I, O, E>,
I: Stream,
{
#[inline]
fn parse_next(&mut self, input: &mut I) -> Result<(O, <I as Stream>::Slice), E> {
let checkpoint = input.checkpoint();
match (self.parser).parse_next(input) {
Ok(result) => {
let offset = input.offset_from(&checkpoint);
input.reset(&checkpoint);
let taken = input.next_slice(offset);
Ok((result, taken))
}
Err(e) => Err(e),
}
}
}
/// [`Parser`] implementation for [`Parser::span`]
pub struct Span<F, I, O, E>
where
F: Parser<I, O, E>,
I: Stream + Location,
{
pub(crate) parser: F,
pub(crate) marker: core::marker::PhantomData<(I, O, E)>,
}
impl<I, O, E, F> Parser<I, Range<usize>, E> for Span<F, I, O, E>
where
F: Parser<I, O, E>,
I: Stream + Location,
{
#[inline]
fn parse_next(&mut self, input: &mut I) -> Result<Range<usize>, E> {
let start = input.current_token_start();
self.parser.parse_next(input).map(move |_| {
let end = input.previous_token_end();
start..end
})
}
}
/// [`Parser`] implementation for [`Parser::with_span`]
pub struct WithSpan<F, I, O, E>
where
F: Parser<I, O, E>,
I: Stream + Location,
{
pub(crate) parser: F,
pub(crate) marker: core::marker::PhantomData<(I, O, E)>,
}
impl<F, I, O, E> Parser<I, (O, Range<usize>), E> for WithSpan<F, I, O, E>
where
F: Parser<I, O, E>,
I: Stream + Location,
{
#[inline]
fn parse_next(&mut self, input: &mut I) -> Result<(O, Range<usize>), E> {
let start = input.current_token_start();
self.parser.parse_next(input).map(move |output| {
let end = input.previous_token_end();
(output, (start..end))
})
}
}
/// [`Parser`] implementation for [`Parser::output_into`]
pub struct OutputInto<F, I, O, O2, E>
where
F: Parser<I, O, E>,
O: Into<O2>,
{
pub(crate) parser: F,
pub(crate) marker: core::marker::PhantomData<(I, O, O2, E)>,
}
impl<F, I, O, O2, E> Parser<I, O2, E> for OutputInto<F, I, O, O2, E>
where
F: Parser<I, O, E>,
O: Into<O2>,
{
#[inline]
fn parse_next(&mut self, i: &mut I) -> Result<O2, E> {
self.parser.parse_next(i).map(|o| o.into())
}
}
/// [`Parser`] implementation for [`Parser::err_into`]
pub struct ErrInto<F, I, O, E, E2>
where
F: Parser<I, O, E>,
E: Into<E2>,
{
pub(crate) parser: F,
pub(crate) marker: core::marker::PhantomData<(I, O, E, E2)>,
}
impl<F, I, O, E, E2> Parser<I, O, E2> for ErrInto<F, I, O, E, E2>
where
F: Parser<I, O, E>,
E: Into<E2>,
{
#[inline]
fn parse_next(&mut self, i: &mut I) -> Result<O, E2> {
self.parser.parse_next(i).map_err(|err| err.into())
}
}
/// [`Parser`] implementation for [`Parser::context`]
pub struct Context<F, I, O, E, C>
where
F: Parser<I, O, E>,
I: Stream,
E: AddContext<I, C>,
E: ParserError<I>,
C: Clone + core::fmt::Debug,
{
pub(crate) parser: F,
pub(crate) context: C,
pub(crate) marker: core::marker::PhantomData<(I, O, E)>,
}
impl<F, I, O, E, C> Parser<I, O, E> for Context<F, I, O, E, C>
where
F: Parser<I, O, E>,
I: Stream,
E: AddContext<I, C>,
E: ParserError<I>,
C: Clone + core::fmt::Debug,
{
#[inline]
fn parse_next(&mut self, i: &mut I) -> Result<O, E> {
let context = self.context.clone();
trace(DisplayDebug(self.context.clone()), move |i: &mut I| {
let start = i.checkpoint();
(self.parser)
.parse_next(i)
.map_err(|err| err.add_context(i, &start, context.clone()))
})
.parse_next(i)
}
}
/// [`Parser`] implementation for [`Parser::context`]
pub struct ContextWith<P, I, O, E, F, C, FI>
where
P: Parser<I, O, E>,
I: Stream,
E: AddContext<I, C>,
E: ParserError<I>,
F: Fn() -> FI + Clone,
C: core::fmt::Debug,
FI: Iterator<Item = C>,
{
pub(crate) parser: P,
pub(crate) context: F,
pub(crate) marker: core::marker::PhantomData<(I, O, E, C, FI)>,
}
impl<P, I, O, E, F, C, FI> Parser<I, O, E> for ContextWith<P, I, O, E, F, C, FI>
where
P: Parser<I, O, E>,
I: Stream,
E: AddContext<I, C>,
E: ParserError<I>,
F: Fn() -> FI + Clone,
C: core::fmt::Debug,
FI: Iterator<Item = C>,
{
#[inline]
fn parse_next(&mut self, i: &mut I) -> Result<O, E> {
let context = self.context.clone();
let start = i.checkpoint();
(self.parser).parse_next(i).map_err(|mut err| {
for context in context() {
err = err.add_context(i, &start, context);
}
err
})
}
}
/// [`Parser`] implementation for [`Parser::map_err`]
pub struct MapErr<F, G, I, O, E, E2>
where
F: Parser<I, O, E>,
G: FnMut(E) -> E2,
{
pub(crate) parser: F,
pub(crate) map: G,
pub(crate) marker: core::marker::PhantomData<(I, O, E, E2)>,
}
impl<F, G, I, O, E, E2> Parser<I, O, E2> for MapErr<F, G, I, O, E, E2>
where
F: Parser<I, O, E>,
G: FnMut(E) -> E2,
{
#[inline]
fn parse_next(&mut self, i: &mut I) -> Result<O, E2> {
match self.parser.parse_next(i) {
Err(e) => Err((self.map)(e)),
Ok(o) => Ok(o),
}
}
}
/// [`Parser`] implementation for [`Parser::retry_after`]
#[cfg(feature = "unstable-recover")]
#[cfg(feature = "std")]
pub struct RetryAfter<P, R, I, O, E>
where
P: Parser<I, O, E>,
R: Parser<I, (), E>,
I: Stream,
I: Recover<E>,
E: ParserError<I> + FromRecoverableError<I, E>,
{
pub(crate) parser: P,
pub(crate) recover: R,
pub(crate) marker: core::marker::PhantomData<(I, O, E)>,
}
#[cfg(feature = "unstable-recover")]
#[cfg(feature = "std")]
impl<P, R, I, O, E> Parser<I, O, E> for RetryAfter<P, R, I, O, E>
where
P: Parser<I, O, E>,
R: Parser<I, (), E>,
I: Stream,
I: Recover<E>,
E: ParserError<I> + FromRecoverableError<I, E>,
{
#[inline(always)]
fn parse_next(&mut self, i: &mut I) -> Result<O, E> {
if I::is_recovery_supported() {
retry_after_inner(&mut self.parser, &mut self.recover, i)
} else {
self.parser.parse_next(i)
}
}
}
#[cfg(feature = "unstable-recover")]
#[cfg(feature = "std")]
fn retry_after_inner<P, R, I, O, E>(parser: &mut P, recover: &mut R, i: &mut I) -> Result<O, E>
where
P: Parser<I, O, E>,
R: Parser<I, (), E>,
I: Stream,
I: Recover<E>,
E: ParserError<I> + FromRecoverableError<I, E>,
{
loop {
let token_start = i.checkpoint();
let mut err = match parser.parse_next(i) {
Ok(o) => {
return Ok(o);
}
Err(e) if e.is_incomplete() => return Err(e),
Err(err) => err,
};
let err_start = i.checkpoint();
let err_start_eof_offset = i.eof_offset();
if recover.parse_next(i).is_ok() {
let i_eof_offset = i.eof_offset();
if err_start_eof_offset == i_eof_offset {
// Didn't advance so bubble the error up
} else if let Err(err_) = i.record_err(&token_start, &err_start, err) {
err = err_;
} else {
continue;
}
}
i.reset(&err_start);
err = E::from_recoverable_error(&token_start, &err_start, i, err);
return Err(err);
}
}
/// [`Parser`] implementation for [`Parser::resume_after`]
#[cfg(feature = "unstable-recover")]
#[cfg(feature = "std")]
pub struct ResumeAfter<P, R, I, O, E>
where
P: Parser<I, O, E>,
R: Parser<I, (), E>,
I: Stream,
I: Recover<E>,
E: ParserError<I> + FromRecoverableError<I, E>,
{
pub(crate) parser: P,
pub(crate) recover: R,
pub(crate) marker: core::marker::PhantomData<(I, O, E)>,
}
#[cfg(feature = "unstable-recover")]
#[cfg(feature = "std")]
impl<P, R, I, O, E> Parser<I, Option<O>, E> for ResumeAfter<P, R, I, O, E>
where
P: Parser<I, O, E>,
R: Parser<I, (), E>,
I: Stream,
I: Recover<E>,
E: ParserError<I> + FromRecoverableError<I, E>,
{
#[inline(always)]
fn parse_next(&mut self, i: &mut I) -> Result<Option<O>, E> {
if I::is_recovery_supported() {
resume_after_inner(&mut self.parser, &mut self.recover, i)
} else {
self.parser.parse_next(i).map(Some)
}
}
}
#[cfg(feature = "unstable-recover")]
#[cfg(feature = "std")]
fn resume_after_inner<P, R, I, O, E>(
parser: &mut P,
recover: &mut R,
i: &mut I,
) -> Result<Option<O>, E>
where
P: Parser<I, O, E>,
R: Parser<I, (), E>,
I: Stream,
I: Recover<E>,
E: ParserError<I> + FromRecoverableError<I, E>,
{
let token_start = i.checkpoint();
let mut err = match parser.parse_next(i) {
Ok(o) => {
return Ok(Some(o));
}
Err(e) if e.is_incomplete() => return Err(e),
Err(err) => err,
};
let err_start = i.checkpoint();
if recover.parse_next(i).is_ok() {
if let Err(err_) = i.record_err(&token_start, &err_start, err) {
err = err_;
} else {
return Ok(None);
}
}
i.reset(&err_start);
err = FromRecoverableError::from_recoverable_error(&token_start, &err_start, i, err);
Err(err)
}
+189
View File
@@ -0,0 +1,189 @@
//! # List of parsers and combinators
//!
//! <div class="warning">
//!
//! **Note**: this list is meant to provide a nicer way to find a parser than reading through the documentation on docs.rs. Function combinators are organized in module so they are a bit easier to find.
//!
//! </div>
//!
//! ## Basic elements
//!
//! Those are used to take a series of tokens for the lowest level elements of your grammar, like, "here is a dot", or "here is an big endian integer".
//!
//! | combinator | usage | input | new input | output | comment |
//! |---|---|---|---|---|---|
//! | [`one_of`][crate::token::one_of] | `one_of(['a', 'b', 'c'])` | `"abc"` | `"bc"` | `Ok('a')` |Matches one of the provided [set of tokens][crate::stream::ContainsToken] (works with non ASCII characters too)|
//! | [`none_of`][crate::token::none_of] | `none_of(['a', 'b', 'c'])` | `"xyab"` | `"yab"` | `Ok('x')` |Matches anything but one of the provided [set of tokens][crate::stream::ContainsToken]|
//! | [`literal`][crate::token::literal] | `"hello"` | `"hello world"` | `" world"` | `Ok("hello")` |Recognizes a specific suite of characters or bytes (see also [`Caseless`][crate::ascii::Caseless])|
//! | [`take`][crate::token::take] | `take(4)` | `"hello"` | `"o"` | `Ok("hell")` |Takes a specific number of bytes or characters|
//! | [`take_while`][crate::token::take_while] | `take_while(0.., is_alphabetic)` | `"abc123"` | `"123"` | `Ok("abc")` |Returns the longest slice of bytes or characters for which the provided [set of tokens][crate::stream::ContainsToken] matches.|
//! | [`take_till`][crate::token::take_till] | `take_till(0.., is_alphabetic)` | `"123abc"` | `"abc"` | `Ok("123")` |Returns a slice of bytes or characters until the provided [set of tokens][crate::stream::ContainsToken] matches. This is the reverse behaviour from `take_while`: `take_till(f)` is equivalent to `take_while(0.., \|c\| !f(c))`|
//! | [`take_until`][crate::token::take_until] | `take_until(0.., "world")` | `"Hello world"` | `"world"` | `Ok("Hello ")` |Returns a slice of bytes or characters until the provided [literal][crate::token::literal] is found.|
//!
//! ## Choice combinators
//!
//! | combinator | usage | input | new input | output | comment |
//! |---|---|---|---|---|---|
//! | [`alt`] | `alt(("ab", "cd"))` | `"cdef"` | `"ef"` | `Ok("cd")` |Try a list of parsers and return the result of the first successful one|
//! | [`dispatch`] | \- | \- | \- | \- | `match` for parsers |
//!
//! ## Sequence combinators
//!
//! | combinator | usage | input | new input | output | comment |
//! |---|---|---|---|---|---|
//! | [`(...)` (tuples)][crate::Parser] | `("ab", "XY", take(1))` | `"abXYZ!"` | `"!"` | `Ok(("ab", "XY", "Z"))` |Parse a series of values|
//! | [`seq!`] | `seq!(_: '(', take(2), _: ')')` | `"(ab)cd"` | `"cd"` | `Ok("ab")` |Parse a series of values, discarding those you specify|
//! | [`delimited`] | `delimited('(', take(2), ')')` | `"(ab)cd"` | `"cd"` | `Ok("ab")` |Parse three values, discarding the first and third value|
//! | [`preceded`] | `preceded("ab", "XY")` | `"abXYZ"` | `"Z"` | `Ok("XY")` |Parse two values, discarding the first value|
//! | [`terminated`] | `terminated("ab", "XY")` | `"abXYZ"` | `"Z"` | `Ok("ab")` |Parse two values, discarding the second value|
//! | [`separated_pair`] | `separated_pair("hello", ',', "world")` | `"hello,world!"` | `"!"` | `Ok(("hello", "world"))` | Parse three values, discarding the middle value|
//!
//! ## Applying a parser multiple times
//!
//! | combinator | usage | input | new input | output | comment |
//! |---|---|---|---|---|---|
//! | [`repeat`] | `repeat(1..=3, "ab")` | `"ababc"` | `"c"` | `Ok(vec!["ab", "ab"])` |Applies the parser between m and n times (n included) and returns the list of results in a Vec|
//! | [`repeat_till`] | `repeat_till(0.., "ab", "ef")` | `"ababefg"` | `"g"` | `Ok((vec!["ab", "ab"], "ef"))` |Applies the first parser until the second applies. Returns a tuple containing the list of results from the first in a Vec and the result of the second|
//! | [`separated`] | `separated(1..=3, "ab", ",")` | `"ab,ab,ab."` | `"."` | `Ok(vec!["ab", "ab", "ab"])` |Applies the parser and separator between m and n times (n included) and returns the list of results in a Vec|
//! | [`Repeat::fold`] | <code>repeat(1..=2, `be_u8`).fold(\|\| 0, \|acc, item\| acc + item)</code> | `[1, 2, 3]` | `[3]` | `Ok(3)` |Applies the parser between m and n times (n included) and folds the list of return value|
//!
//! ## Partial related
//!
//! - [`eof`]: Returns its input if it is at the end of input data
//! - [`Parser::complete_err`]: Replaces an `Incomplete` returned by the child parser with an `Backtrack`
//!
//! ## Modifiers
//!
//! - [`cond`]: Conditional combinator. Wraps another parser and calls it if the condition is met
//! - [`Parser::flat_map`]: method to map a new parser from the output of the first parser, then apply that parser over the rest of the input
//! - [`Parser::value`]: method to replace the result of a parser
//! - [`Parser::default_value`]: method to replace the result of a parser
//! - [`Parser::void`]: method to discard the result of a parser
//! - [`Parser::map`]: method to map a function on the result of a parser
//! - [`Parser::and_then`]: Applies a second parser over the output of the first one
//! - [`Parser::verify_map`]: Maps a function returning an `Option` on the output of a parser
//! - [`Parser::try_map`]: Maps a function returning a `Result` on the output of a parser
//! - [`Parser::parse_to`]: Apply [`std::str::FromStr`] to the output of the parser
//! - [`not`]: Returns a result only if the embedded parser returns `Backtrack` or `Incomplete`. Does not consume the input
//! - [`opt`]: Make the underlying parser optional
//! - [`peek`]: Returns a result without consuming the input
//! - [`Parser::take`]: If the child parser was successful, return the consumed input as the produced value
//! - [`Parser::with_taken`]: If the child parser was successful, return a tuple of the consumed input and the produced output.
//! - [`Parser::span`]: If the child parser was successful, return the location of the consumed input as the produced value
//! - [`Parser::with_span`]: If the child parser was successful, return a tuple of the location of the consumed input and the produced output.
//! - [`Parser::verify`]: Returns the result of the child parser if it satisfies a verification function
//!
//! ## Error management and debugging
//!
//! - [`cut_err`]: Commit the parse result, disallowing alternative parsers from being attempted
//! - [`backtrack_err`]: Attempts a parse, allowing alternative parsers to be attempted despite
//! use of `cut_err`
//! - [`Parser::context`]: Add context to the error if the parser fails
//! - [`trace`]: Print the parse state with the `debug` feature flag
//! - [`todo()`]: Placeholder parser
//!
//! ## Remaining combinators
//!
//! - [`empty`]: Succeed, consuming no input
//! - [`fail`]: Inversion of [`empty`]. Always fails.
//! - [`Parser::by_ref`]: Allow moving `&mut impl Parser` into other parsers
//!
//! ## Text parsing
//!
//! - [`any`][crate::token::any]: Matches one token
//! - [`tab`][crate::ascii::tab]: Matches a tab character `\t`
//! - [`crlf`][crate::ascii::crlf]: Recognizes the string `\r\n`
//! - [`line_ending`][crate::ascii::line_ending]: Recognizes an end of line (both `\n` and `\r\n`)
//! - [`newline`][crate::ascii::newline]: Matches a newline character `\n`
//! - [`till_line_ending`][crate::ascii::till_line_ending]: Recognizes a string of any char except `\r` or `\n`
//! - [`rest`][crate::token::rest]: Return the remaining input
//!
//! - [`alpha0`][crate::ascii::alpha0]: Recognizes zero or more lowercase and uppercase alphabetic characters: `[a-zA-Z]`. [`alpha1`][crate::ascii::alpha1] does the same but returns at least one character
//! - [`alphanumeric0`][crate::ascii::alphanumeric0]: Recognizes zero or more numerical and alphabetic characters: `[0-9a-zA-Z]`. [`alphanumeric1`][crate::ascii::alphanumeric1] does the same but returns at least one character
//! - [`space0`][crate::ascii::space0]: Recognizes zero or more spaces and tabs. [`space1`][crate::ascii::space1] does the same but returns at least one character
//! - [`multispace0`][crate::ascii::multispace0]: Recognizes zero or more spaces, tabs, carriage returns and line feeds. [`multispace1`][crate::ascii::multispace1] does the same but returns at least one character
//! - [`digit0`][crate::ascii::digit0]: Recognizes zero or more numerical characters: `[0-9]`. [`digit1`][crate::ascii::digit1] does the same but returns at least one character
//! - [`hex_digit0`][crate::ascii::hex_digit0]: Recognizes zero or more hexadecimal numerical characters: `[0-9A-Fa-f]`. [`hex_digit1`][crate::ascii::hex_digit1] does the same but returns at least one character
//! - [`oct_digit0`][crate::ascii::oct_digit0]: Recognizes zero or more octal characters: `[0-7]`. [`oct_digit1`][crate::ascii::oct_digit1] does the same but returns at least one character
//!
//! - [`float`][crate::ascii::float]: Parse a floating point number in a byte string
//! - [`dec_int`][crate::ascii::dec_int]: Decode a variable-width, decimal signed integer
//! - [`dec_uint`][crate::ascii::dec_uint]: Decode a variable-width, decimal unsigned integer
//! - [`hex_uint`][crate::ascii::hex_uint]: Decode a variable-width, hexadecimal integer
//!
//! - [`take_escaped`][crate::ascii::take_escaped]: Recognize the input slice with escaped characters
//! - [`escaped`][crate::ascii::escaped]: Parse escaped characters, unescaping them
//!
//! - [`expression()`]: Parse an operator precedence expression with Pratt parsing
//!
//! ### Character test functions
//!
//! Use these functions with a combinator like `take_while`:
//!
//! - [`AsChar::is_alpha`][crate::stream::AsChar::is_alpha]: Tests if byte is ASCII alphabetic: `[A-Za-z]`
//! - [`AsChar::is_alphanum`][crate::stream::AsChar::is_alphanum]: Tests if byte is ASCII alphanumeric: `[A-Za-z0-9]`
//! - [`AsChar::is_dec_digit`][crate::stream::AsChar::is_dec_digit]: Tests if byte is ASCII digit: `[0-9]`
//! - [`AsChar::is_hex_digit`][crate::stream::AsChar::is_hex_digit]: Tests if byte is ASCII hex digit: `[0-9A-Fa-f]`
//! - [`AsChar::is_oct_digit`][crate::stream::AsChar::is_oct_digit]: Tests if byte is ASCII octal digit: `[0-7]`
//! - [`AsChar::is_space`][crate::stream::AsChar::is_space]: Tests if byte is ASCII space or tab: `[ \t]`
//! - [`AsChar::is_newline`][crate::stream::AsChar::is_newline]: Tests if byte is ASCII newline: `[\n]`
//!
//! ## Binary format parsing
//!
//! - [`length_repeat`][crate::binary::length_repeat] Gets a number from the first parser, then applies the second parser that many times
//! - [`length_take`][crate::binary::length_take]: Gets a number from the first parser, then takes a subslice of the input of that size, and returns that subslice
//! - [`length_and_then`][crate::binary::length_and_then]: Gets a number from the first parser, takes a subslice of the input of that size, then applies the second parser on that subslice. If the second parser returns `Incomplete`, `length_value` will return an error
//!
//! ### Integers
//!
//! Parsing integers from binary formats can be done in two ways: With parser functions, or combinators with configurable endianness.
//!
//! - **configurable endianness:** [`i16`][crate::binary::i16], [`i32`][crate::binary::i32],
//! [`i64`][crate::binary::i64], [`u16`][crate::binary::u16], [`u32`][crate::binary::u32],
//! [`u64`][crate::binary::u64] are combinators that take as argument a
//! [`winnow::binary::Endianness`][crate::binary::Endianness], like this: `i16(endianness)`. If the
//! parameter is `winnow::binary::Endianness::Big`, parse a big endian `i16` integer, otherwise a
//! little endian `i16` integer.
//! - **fixed endianness**: The functions are prefixed by `be_` for big endian numbers, and by `le_` for little endian numbers, and the suffix is the type they parse to. As an example, `be_u32` parses a big endian unsigned integer stored in 32 bits.
//! - [`be_f32`][crate::binary::be_f32], [`be_f64`][crate::binary::be_f64]: Big endian floating point numbers
//! - [`le_f32`][crate::binary::le_f32], [`le_f64`][crate::binary::le_f64]: Little endian floating point numbers
//! - [`be_i8`][crate::binary::be_i8], [`be_i16`][crate::binary::be_i16], [`be_i24`][crate::binary::be_i24], [`be_i32`][crate::binary::be_i32], [`be_i64`][crate::binary::be_i64], [`be_i128`][crate::binary::be_i128]: Big endian signed integers
//! - [`be_u8`][crate::binary::be_u8], [`be_u16`][crate::binary::be_u16], [`be_u24`][crate::binary::be_u24], [`be_u32`][crate::binary::be_u32], [`be_u64`][crate::binary::be_u64], [`be_u128`][crate::binary::be_u128]: Big endian unsigned integers
//! - [`le_i8`][crate::binary::le_i8], [`le_i16`][crate::binary::le_i16], [`le_i24`][crate::binary::le_i24], [`le_i32`][crate::binary::le_i32], [`le_i64`][crate::binary::le_i64], [`le_i128`][crate::binary::le_i128]: Little endian signed integers
//! - [`le_u8`][crate::binary::le_u8], [`le_u16`][crate::binary::le_u16], [`le_u24`][crate::binary::le_u24], [`le_u32`][crate::binary::le_u32], [`le_u64`][crate::binary::le_u64], [`le_u128`][crate::binary::le_u128]: Little endian unsigned integers
//!
//! ### Bit stream parsing
//!
//! - [`bits`][crate::binary::bits::bits]: Transforms the current input type (byte slice `&[u8]`) to a bit stream on which bit specific parsers and more general combinators can be applied
//! - [`bytes`][crate::binary::bits::bytes]: Transforms its bits stream input back into a byte slice for the underlying parser
//! - [`take`][crate::binary::bits::take]: Take a set number of bits
//! - [`pattern`][crate::binary::bits::pattern]: Check if a set number of bits matches a pattern
//! - [`bool`][crate::binary::bits::bool]: Match any one bit
mod branch;
mod core;
mod debug;
mod expression;
mod multi;
mod sequence;
#[cfg(all(test, feature = "ascii", feature = "binary"))]
mod tests;
pub mod impls;
pub use self::branch::{alt, dispatch, Alt};
pub use self::core::{backtrack_err, cond, cut_err, empty, eof, fail, not, opt, peek, todo};
pub use self::debug::trace;
pub use self::expression::{expression, Expression, Infix, Postfix, Prefix};
#[cfg(feature = "alloc")]
pub use self::multi::separated_foldr1;
pub use self::multi::{
fill, iterator, repeat, repeat_till, separated, separated_foldl1, ParserIterator, Repeat,
};
pub use self::sequence::{delimited, preceded, separated_pair, seq, terminated, unordered_seq};
pub(crate) use self::debug::{trace_result, DisplayDebug};
#[allow(unused_imports)]
use crate::Parser;
File diff suppressed because it is too large. Load diff
+176
View File
@@ -0,0 +1,176 @@
use crate::combinator::trace;
use crate::error::ParserError;
use crate::stream::Stream;
use crate::Parser;
#[doc(inline)]
pub use crate::seq;
#[doc(inline)]
pub use crate::unordered_seq;
/// Sequence two parsers, only returning the output from the second.
///
/// See also [`seq`] to generalize this across any number of fields.
///
/// # Example
///
/// ```rust
/// # use winnow::{error::ErrMode, error::Needed};
/// # use winnow::prelude::*;
/// # use winnow::error::Needed::Size;
/// use winnow::combinator::preceded;
///
/// fn parser<'i>(input: &mut &'i str) -> ModalResult<&'i str> {
/// preceded("abc", "efg").parse_next(input)
/// }
///
/// assert_eq!(parser.parse_peek("abcefg"), Ok(("", "efg")));
/// assert_eq!(parser.parse_peek("abcefghij"), Ok(("hij", "efg")));
/// assert!(parser.parse_peek("").is_err());
/// assert!(parser.parse_peek("123").is_err());
/// ```
#[doc(alias = "ignore_then")]
pub fn preceded<Input, Ignored, Output, Error, IgnoredParser, ParseNext>(
mut ignored: IgnoredParser,
mut parser: ParseNext,
) -> impl Parser<Input, Output, Error>
where
Input: Stream,
Error: ParserError<Input>,
IgnoredParser: Parser<Input, Ignored, Error>,
ParseNext: Parser<Input, Output, Error>,
{
trace("preceded", move |input: &mut Input| {
let _ = ignored.parse_next(input)?;
parser.parse_next(input)
})
}
/// Sequence two parsers, only returning the output of the first.
///
/// See also [`seq`] to generalize this across any number of fields.
///
/// # Example
///
/// ```rust
/// # use winnow::{error::ErrMode, error::Needed};
/// # use winnow::prelude::*;
/// # use winnow::error::Needed::Size;
/// use winnow::combinator::terminated;
///
/// fn parser<'i>(input: &mut &'i str) -> ModalResult<&'i str> {
/// terminated("abc", "efg").parse_next(input)
/// }
///
/// assert_eq!(parser.parse_peek("abcefg"), Ok(("", "abc")));
/// assert_eq!(parser.parse_peek("abcefghij"), Ok(("hij", "abc")));
/// assert!(parser.parse_peek("").is_err());
/// assert!(parser.parse_peek("123").is_err());
/// ```
#[doc(alias = "then_ignore")]
pub fn terminated<Input, Output, Ignored, Error, ParseNext, IgnoredParser>(
mut parser: ParseNext,
mut ignored: IgnoredParser,
) -> impl Parser<Input, Output, Error>
where
Input: Stream,
Error: ParserError<Input>,
ParseNext: Parser<Input, Output, Error>,
IgnoredParser: Parser<Input, Ignored, Error>,
{
trace("terminated", move |input: &mut Input| {
let o = parser.parse_next(input)?;
ignored.parse_next(input).map(|_| o)
})
}
/// Sequence three parsers, only returning the values of the first and third.
///
/// See also [`seq`] to generalize this across any number of fields.
///
/// # Example
///
/// ```rust
/// # use winnow::{error::ErrMode, error::Needed};
/// # use winnow::error::Needed::Size;
/// # use winnow::prelude::*;
/// use winnow::combinator::separated_pair;
///
/// fn parser<'i>(input: &mut &'i str) -> ModalResult<(&'i str, &'i str)> {
/// separated_pair("abc", "|", "efg").parse_next(input)
/// }
///
/// assert_eq!(parser.parse_peek("abc|efg"), Ok(("", ("abc", "efg"))));
/// assert_eq!(parser.parse_peek("abc|efghij"), Ok(("hij", ("abc", "efg"))));
/// assert!(parser.parse_peek("").is_err());
/// assert!(parser.parse_peek("123").is_err());
/// ```
pub fn separated_pair<Input, O1, Sep, O2, Error, P1, SepParser, P2>(
mut first: P1,
mut sep: SepParser,
mut second: P2,
) -> impl Parser<Input, (O1, O2), Error>
where
Input: Stream,
Error: ParserError<Input>,
P1: Parser<Input, O1, Error>,
SepParser: Parser<Input, Sep, Error>,
P2: Parser<Input, O2, Error>,
{
trace("separated_pair", move |input: &mut Input| {
let o1 = first.parse_next(input)?;
let _ = sep.parse_next(input)?;
second.parse_next(input).map(|o2| (o1, o2))
})
}
/// Sequence three parsers, only returning the output of the second.
///
/// See also [`seq`] to generalize this across any number of fields.
///
/// # Example
///
/// ```rust
/// # use winnow::{error::ErrMode, error::Needed};
/// # use winnow::error::Needed::Size;
/// # use winnow::prelude::*;
/// use winnow::combinator::delimited;
///
/// fn parser<'i>(input: &mut &'i str) -> ModalResult<&'i str> {
/// delimited("(", "abc", ")").parse_next(input)
/// }
///
/// assert_eq!(parser.parse_peek("(abc)"), Ok(("", "abc")));
/// assert_eq!(parser.parse_peek("(abc)def"), Ok(("def", "abc")));
/// assert!(parser.parse_peek("").is_err());
/// assert!(parser.parse_peek("123").is_err());
/// ```
#[doc(alias = "between")]
#[doc(alias = "padded")]
pub fn delimited<
Input,
Ignored1,
Output,
Ignored2,
Error,
IgnoredParser1,
ParseNext,
IgnoredParser2,
>(
mut ignored1: IgnoredParser1,
mut parser: ParseNext,
mut ignored2: IgnoredParser2,
) -> impl Parser<Input, Output, Error>
where
Input: Stream,
Error: ParserError<Input>,
IgnoredParser1: Parser<Input, Ignored1, Error>,
ParseNext: Parser<Input, Output, Error>,
IgnoredParser2: Parser<Input, Ignored2, Error>,
{
trace("delimited", move |input: &mut Input| {
let _ = ignored1.parse_next(input)?;
let o2 = parser.parse_next(input)?;
ignored2.parse_next(input).map(|_| o2)
})
}
File diff suppressed because it is too large. Load diff