Skip to content

Commit 21bb953

Browse files
committed
Updated changelog, bumped version
1 parent c21c722 commit 21bb953

12 files changed

+133
-64
lines changed

CHANGELOG.md

+43
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,49 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515

1616
### Fixed
1717

18+
# [0.7.0] - 2021-12-16
19+
20+
### Added
21+
22+
- A new [tutorial](tutorial.md) to help new users
23+
24+
- `select` macro, a wrapper over `filter_map` that makes extracting data from specific tokens easy
25+
- `choice` parser, a better alternative to long `or` chains (which sometimes have poor compilation performance)
26+
- `todo` parser, that panics when used (but not when created) (akin to Rust's `todo!` macro, but for parsers)
27+
- `keyword` parser, that parses *exact* identifiers
28+
29+
- `from_str` combinator to allow converting a pattern to a value inline, using `std::str::FromStr`
30+
- `unwrapped` combinator, to automatically unwrap an output value inline
31+
- `rewind` combinator, that allows reverting the input stream on success. It's most useful when requiring that a
32+
pattern is followed by some terminating pattern without the first parser greedily consuming it
33+
- `map_err_with_span` combinator, to allow fetching the span of the input that was parsed by a parser before an error
34+
was encountered
35+
36+
- `or_else` combinator, to allow processing and potentially recovering from a parser error
37+
- `SeparatedBy::at_most` to require that a separated pattern appear at most a specific number of times
38+
- `SeparatedBy::exactly` to require that a separated pattern be repeated exactly a specific number of times
39+
- `Repeated::exactly` to require that a pattern be repeated exactly a specific number of times
40+
41+
- More trait implementations for various things, making the crate more useful
42+
43+
### Changed
44+
45+
- Made `just`, `one_of`, and `none_of` significant more useful. They can now accept strings, arrays, slices, vectors,
46+
sets, or just single tokens as before
47+
- Added the return type of each parser to its documentation
48+
- More explicit documentation of parser behaviour
49+
- More doc examples
50+
- Deprecated `seq` (`just` has been generalised and can now be used to parse specific input sequences)
51+
- Sealed the `Character` trait so that future changes are not breaking
52+
- Sealed the `Chain` trait and made it more powerful
53+
- Moved trait constraints on `Parser` to where clauses for improved readability
54+
55+
### Fixed
56+
57+
- Fixed a subtle bug that allowed `separated_by` to parse an extra trailing separator when it shouldn't
58+
- Filled a 'hole' in the `Error` trait's API that conflated a lack of expected tokens with expectation of end of input
59+
- Made recursive parsers use weak reference-counting to avoid memory leaks
60+
1861
# [0.6.0] - 2021-11-22
1962

2063
### Added

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "chumsky"
3-
version = "0.6.0"
3+
version = "0.7.0"
44
description = "A parser library for humans with powerful error recovery"
55
authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>"]
66
repository = "https://github.com/zesterer/chumsky"

examples/brainfuck.rs

+11-9
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,20 @@ enum Instr {
2222

2323
fn parser() -> impl Parser<char, Vec<Instr>, Error = Simple<char>> {
2424
use Instr::*;
25-
recursive(|bf| choice((
26-
just('<').to(Left),
27-
just('>').to(Right),
28-
just('+').to(Incr),
29-
just('-').to(Decr),
30-
just(',').to(Read),
31-
just('.').to(Write),
32-
))
25+
recursive(|bf| {
26+
choice((
27+
just('<').to(Left),
28+
just('>').to(Right),
29+
just('+').to(Incr),
30+
just('-').to(Decr),
31+
just(',').to(Read),
32+
just('.').to(Write),
33+
))
3334
.or(bf.delimited_by('[', ']').map(Loop))
3435
.recover_with(nested_delimiters('[', ']', [], |_| Invalid))
3536
.recover_with(skip_then_retry_until([']']))
36-
.repeated())
37+
.repeated()
38+
})
3739
.then_ignore(end())
3840
}
3941

examples/json.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ fn main() {
116116
e.expected()
117117
.map(|expected| match expected {
118118
Some(expected) => expected.to_string(),
119-
None => "end of input".to_string()
119+
None => "end of input".to_string(),
120120
})
121121
.collect::<Vec<_>>()
122122
.join(", ")

examples/nano_rust.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -614,7 +614,7 @@ fn main() {
614614
e.expected()
615615
.map(|expected| match expected {
616616
Some(expected) => expected.to_string(),
617-
None => "end of input".to_string()
617+
None => "end of input".to_string(),
618618
})
619619
.collect::<Vec<_>>()
620620
.join(", ")

src/chain.rs

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
mod private {
32
pub trait Sealed<T> {}
43

src/combinator.rs

+20-18
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ impl<I: Clone, O, A: Parser<I, O, Error = E>, B: Parser<I, O, Error = E>, E: Err
3131
let a_state = stream.save();
3232

3333
// If the first parser succeeded and produced no secondary errors, don't bother trying the second parser
34+
// TODO: Perhaps we should *alwaus* take this route, even if recoverable errors did occur? Seems like an
35+
// inconsistent application of PEG rules...
3436
if a_res.0.is_empty() {
3537
if let (a_errors, Ok(a_out)) = a_res {
3638
return (a_errors, Ok(a_out));
@@ -428,7 +430,7 @@ impl<A, B, U> SeparatedBy<A, B, U> {
428430
///
429431
/// ```
430432
/// # use chumsky::prelude::*;
431-
/// let r#enum = seq::<_, _, Simple<char>>("enum".chars())
433+
/// let r#enum = text::keyword::<_, _, Simple<char>>("enum")
432434
/// .padded()
433435
/// .ignore_then(text::ident()
434436
/// .padded()
@@ -987,8 +989,9 @@ impl<I: Clone, O, A: Parser<I, O, Error = E>, F: Fn(E) -> E, E: Error<I>> Parser
987989
let (errors, res) = debugger.invoke(&self.0, stream);
988990
let mapper = |e: Located<I, E>| e.map(&self.1);
989991
(
990-
errors,//errors.into_iter().map(mapper).collect(),
991-
res/*.map(|(out, alt)| (out, alt.map(mapper)))*/.map_err(mapper),
992+
errors, //errors.into_iter().map(mapper).collect(),
993+
res /*.map(|(out, alt)| (out, alt.map(mapper)))*/
994+
.map_err(mapper),
992995
)
993996
}
994997

@@ -1025,14 +1028,14 @@ impl<I: Clone, O, A: Parser<I, O, Error = E>, F: Fn(E, E::Span) -> E, E: Error<I
10251028
let mapper = |e: Located<I, E>| {
10261029
let at = e.at;
10271030
e.map(|e| {
1028-
let span = stream.attempt(|stream| { stream.revert(at); (false, stream.span_since(start)) });
1031+
let span = stream.attempt(|stream| {
1032+
stream.revert(at);
1033+
(false, stream.span_since(start))
1034+
});
10291035
(self.1)(e, span)
10301036
})
10311037
};
1032-
(
1033-
errors,
1034-
res.map_err(mapper),
1035-
)
1038+
(errors, res.map_err(mapper))
10361039
}
10371040

10381041
#[inline]
@@ -1103,13 +1106,8 @@ impl<
11031106
#[derive(Copy, Clone)]
11041107
pub struct OrElse<A, F>(pub(crate) A, pub(crate) F);
11051108

1106-
impl<
1107-
I: Clone,
1108-
O,
1109-
A: Parser<I, O, Error = E>,
1110-
F: Fn(E) -> Result<O, E>,
1111-
E: Error<I>,
1112-
> Parser<I, O> for OrElse<A, F>
1109+
impl<I: Clone, O, A: Parser<I, O, Error = E>, F: Fn(E) -> Result<O, E>, E: Error<I>> Parser<I, O>
1110+
for OrElse<A, F>
11131111
{
11141112
type Error = E;
11151113

@@ -1125,7 +1123,11 @@ impl<
11251123
let res = match res {
11261124
Ok(out) => Ok(out),
11271125
Err(err) => match (&self.1)(err.error) {
1128-
Err(e) => Err(Located { at: err.at, error: e, phantom: PhantomData }),
1126+
Err(e) => Err(Located {
1127+
at: err.at,
1128+
error: e,
1129+
phantom: PhantomData,
1130+
}),
11291131
Ok(out) => Ok((out, None)),
11301132
},
11311133
};
@@ -1167,8 +1169,8 @@ impl<I: Clone, O, A: Parser<I, O, Error = E>, L: Into<E::Label> + Clone, E: Erro
11671169
/* TODO: Not this? */
11681170
/*if e.at > pre_state
11691171
{*/
1170-
// Only add the label if we committed to this pattern somewhat
1171-
e.map(|e| e.with_label(self.1.clone().into()))
1172+
// Only add the label if we committed to this pattern somewhat
1173+
e.map(|e| e.with_label(self.1.clone().into()))
11721174
/*} else {
11731175
e
11741176
}*/

src/error.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ impl<I: fmt::Display + Hash, S: Span> fmt::Display for Simple<I, S> {
296296
}
297297

298298
match self.expected.len() {
299-
0 => {},//write!(f, " but end of input was expected")?,
299+
0 => {} //write!(f, " but end of input was expected")?,
300300
1 => write!(
301301
f,
302302
" but {} was expected",

src/lib.rs

+14-8
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ pub mod prelude {
6060
pub use super::{
6161
error::{Error as _, Simple},
6262
primitive::{
63-
any, empty, end, filter, filter_map, just, none_of, one_of, seq, take_until, todo, choice,
63+
any, choice, empty, end, filter, filter_map, just, none_of, one_of, seq, take_until,
64+
todo,
6465
},
6566
recovery::{nested_delimiters, skip_then_retry_until, skip_until},
6667
recursive::{recursive, Recursive},
@@ -346,6 +347,10 @@ pub trait Parser<I: Clone, O> {
346347

347348
/// Map the primary error of this parser to a result. If the result is [`Ok`], the parser succeeds with that value.
348349
///
350+
/// Note that even if the function returns an [`Ok`], the input stream will still be 'stuck' at the input following
351+
/// the input that triggered the error. You'll need to follow uses of this combinator with a parser that resets
352+
/// the input stream to a known-good state (for example, [`take_until`]).
353+
///
349354
/// The output type of this parser is `U`, the [`Ok`] type of the result.
350355
fn or_else<F>(self, f: F) -> OrElse<Self, F>
351356
where
@@ -879,13 +884,14 @@ pub trait Parser<I: Clone, O> {
879884
/// Apply a fallback recovery strategy to this parser should it fail.
880885
///
881886
/// There is no silver bullet for error recovery, so this function allows you to specify one of several different
882-
/// strategies at the location of your choice.
883-
///
884-
/// Note that for implementation reasons, adding an error recovery strategy can cause a parser to 'over-commit',
885-
/// missing potentially valid alternative parse routes (*TODO: document this and explain why and when it happens*).
886-
/// Rest assured that this case is generally quite rare and only happens for very loose, almost-ambiguous syntax.
887-
/// If you run into cases that you believe should parse but do not, try removing or moving recovery strategies to
888-
/// fix the problem.
887+
/// strategies at the location of your choice. Prefer an error recovery strategy that more precisely mirrors valid
888+
/// syntax where possible to make error recovery more reliable.
889+
///
890+
/// Because chumsky is a [PEG](https://en.m.wikipedia.org/wiki/Parsing_expression_grammar) parser, which always
891+
/// take the first successful parsing route through a grammar, recovering from an error may cause the parser to
892+
/// erroneously miss alternative valid routes through the grammar that do not generate recoverable errors. If you
893+
/// run into cases where valid syntax fails to parse without errors, this might be happening: consider removing
894+
/// error recovery or switching to a more specific error recovery strategy.
889895
///
890896
/// The output type of this parser is `O`, the same as the original parser.
891897
///

src/primitive.rs

+35-20
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,27 @@ pub fn end<E>() -> End<E> {
120120
End(PhantomData)
121121
}
122122

123+
mod private {
124+
pub trait Sealed<T> {}
125+
126+
impl<T> Sealed<T> for T {}
127+
impl Sealed<char> for String {}
128+
impl<'a> Sealed<char> for &'a str {}
129+
impl<'a, T> Sealed<T> for &'a [T] {}
130+
impl<T, const N: usize> Sealed<T> for [T; N] {}
131+
impl<'a, T, const N: usize> Sealed<T> for &'a [T; N] {}
132+
impl<T> Sealed<T> for Vec<T> {}
133+
impl<T> Sealed<T> for std::collections::LinkedList<T> {}
134+
impl<T> Sealed<T> for std::collections::VecDeque<T> {}
135+
impl<T> Sealed<T> for std::collections::HashSet<T> {}
136+
impl<T> Sealed<T> for std::collections::BTreeSet<T> {}
137+
impl<T> Sealed<T> for std::collections::BinaryHeap<T> {}
138+
}
139+
123140
/// A utility trait to abstract over linear container-like things.
124141
///
125142
/// This trait is likely to change in future versions of the crate, so avoid implementing it yourself.
126-
pub trait Container<T> {
143+
pub trait Container<T>: private::Sealed<T> {
127144
/// An iterator over the items within this container, by value.
128145
type Iter: Iterator<Item = T>;
129146
/// Iterate over the elements of the container (using internal iteration because GATs are unstable).
@@ -375,15 +392,13 @@ impl<I: Clone + PartialEq, C: Container<I>, E: Error<I>> Parser<I, I> for OneOf<
375392
(_, _, Some(tok)) if self.0.get_iter().any(|not| not == tok) => {
376393
(Vec::new(), Ok((tok, None)))
377394
}
378-
(at, span, found) => {
379-
(
380-
Vec::new(),
381-
Err(Located::at(
382-
at,
383-
E::expected_input_found(span, self.0.get_iter().map(Some), found),
384-
)),
385-
)
386-
}
395+
(at, span, found) => (
396+
Vec::new(),
397+
Err(Located::at(
398+
at,
399+
E::expected_input_found(span, self.0.get_iter().map(Some), found),
400+
)),
401+
),
387402
}
388403
}
389404

@@ -475,15 +490,13 @@ impl<I: Clone + PartialEq, C: Container<I>, E: Error<I>> Parser<I, I> for NoneOf
475490
(_, _, Some(tok)) if self.0.get_iter().all(|not| not != tok) => {
476491
(Vec::new(), Ok((tok, None)))
477492
}
478-
(at, span, found) => {
479-
(
480-
Vec::new(),
481-
Err(Located::at(
482-
at,
483-
E::expected_input_found(span, Vec::new(), found),
484-
)),
485-
)
486-
}
493+
(at, span, found) => (
494+
Vec::new(),
495+
Err(Located::at(
496+
at,
497+
E::expected_input_found(span, Vec::new(), found),
498+
)),
499+
),
487500
}
488501
}
489502

@@ -836,7 +849,9 @@ pub struct Choice<T, E>(pub(crate) T, pub(crate) PhantomData<E>);
836849

837850
impl<T: Copy, E> Copy for Choice<T, E> {}
838851
impl<T: Clone, E> Clone for Choice<T, E> {
839-
fn clone(&self) -> Self { Self(self.0.clone(), PhantomData) }
852+
fn clone(&self) -> Self {
853+
Self(self.0.clone(), PhantomData)
854+
}
840855
}
841856

842857
macro_rules! impl_for_tuple {

src/recovery.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,11 @@ impl<I: Clone + PartialEq, O, F: Fn(E::Span) -> O, E: Error<I>, const N: usize>
193193
),
194194
None => Located::at(
195195
at,
196-
P::Error::expected_input_found(span, Some(Some(self.1.clone())), None),
196+
P::Error::expected_input_found(
197+
span,
198+
Some(Some(self.1.clone())),
199+
None,
200+
),
197201
),
198202
});
199203
}

src/recursive.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,7 @@ type OnceParser<'a, I, O, E> = OnceCell<Box<dyn Parser<I, O, Error = E> + 'a>>;
3030
/// [definition](Recursive::define).
3131
///
3232
/// Prefer to use [`recursive()`], which exists as a convenient wrapper around both operations, if possible.
33-
pub struct Recursive<'a, I, O, E: Error<I>>(
34-
RecursiveInner<OnceParser<'a, I, O, E>>,
35-
);
33+
pub struct Recursive<'a, I, O, E: Error<I>>(RecursiveInner<OnceParser<'a, I, O, E>>);
3634

3735
impl<'a, I: Clone, O, E: Error<I>> Recursive<'a, I, O, E> {
3836
fn cell(&self) -> Rc<OnceParser<'a, I, O, E>> {

0 commit comments

Comments
 (0)