Skip to content
This repository was archived by the owner on Apr 2, 2026. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 3 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,8 @@ memoization = []
# Allows extending chumsky by writing your own parser implementations.
extension = []

# Make builtin parsers such as `Boxed` use atomic instead of non-atomic internals.
# TODO: Remove or rework this
sync = ["spin"]

# Enable Pratt parsing combinator
pratt = ["unstable"]
pratt = []

# Allow the use of unstable features (aka features where the API is not settled)
unstable = []
Expand All @@ -66,7 +62,7 @@ docsrs = []
# An alias of all features that work with the stable compiler.
# Do not use this feature, its removal is not considered a breaking change and its behaviour may change.
# If you're working on chumsky and you're adding a feature that does not require nightly support, please add it to this list.
_test_stable = ["std", "stacker", "memoization", "extension", "sync"]
_test_stable = ["std", "stacker", "memoization", "extension", "pratt"]

[package.metadata.docs.rs]
all-features = true
Expand All @@ -76,7 +72,7 @@ rustdoc-args = ["--cfg", "docsrs"]
hashbrown = "0.15"
stacker = { version = "0.1", optional = true }
regex-automata = { version = "0.3", default-features = false, optional = true, features = ["alloc", "meta", "perf", "unicode", "nfa", "dfa", "hybrid"] }
spin = { version = "0.9", features = ["once"], default-features = false, optional = true }
spin = { version = "0.9", features = ["once"], default-features = false }
lexical = { version = "6.1.1", default-features = false, features = ["parse-integers", "parse-floats", "format"], optional = true }
either = { version = "1.8.1", optional = true }
serde = { version = "1.0", default-features = false, optional = true, features = ["derive"] }
Expand Down
2 changes: 1 addition & 1 deletion examples/mini_ml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ where
I: BorrowInput<'tokens, Token = Token<'src>, Span = SimpleSpan>,
// Because this function is generic over the input type, we need the caller to tell us how to create a new input,
// `I`, from a nested token tree. This function serves that purpose.
M: Fn(SimpleSpan, &'tokens [Spanned<Token<'src>>]) -> I + Clone + 'src,
M: Fn(SimpleSpan, &'tokens [Spanned<Token<'src>>]) -> I + Clone + Send + Sync + 'src,
{
recursive(|expr| {
let ident = select_ref! { Token::Ident(x) => *x };
Expand Down
2 changes: 1 addition & 1 deletion examples/nested_spans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ enum Token {
fn parser<'src, I, M>(make_input: M) -> impl Parser<'src, I, i64>
where
I: BorrowInput<'src, Token = Token, Span = SimpleSpan>,
M: Fn(SimpleSpan, &'src [(Token, SimpleSpan)]) -> I + Clone + 'src,
M: Fn(SimpleSpan, &'src [(Token, SimpleSpan)]) -> I + Send + Sync + Clone + 'src,
{
recursive(|expr| {
let num = select_ref! { Token::Num(x) => *x };
Expand Down
18 changes: 9 additions & 9 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ pub mod prelude {
use crate::input::InputOwn;
use alloc::{
boxed::Box,
rc::{self, Rc},
string::String,
sync::{Arc, Weak},
vec,
vec::Vec,
};
Expand Down Expand Up @@ -157,9 +157,10 @@ impl<T> Unpin for EmptyPhantom<T> {}
impl<T> core::panic::UnwindSafe for EmptyPhantom<T> {}
impl<T> core::panic::RefUnwindSafe for EmptyPhantom<T> {}

pub(crate) type DynParser<'src, 'b, I, O, E> = dyn Parser<'src, I, O, E> + 'b;
pub(crate) type DynParser<'src, 'b, I, O, E> = dyn Parser<'src, I, O, E> + Send + Sync + 'b;
#[cfg(feature = "pratt")]
pub(crate) type DynOperator<'src, 'b, I, O, E> = dyn pratt::Operator<'src, I, O, E> + 'b;
pub(crate) type DynOperator<'src, 'b, I, O, E> =
dyn pratt::Operator<'src, I, O, E> + Send + Sync + 'b;

/// Labels corresponding to a variety of patterns.
#[derive(Clone, Debug, PartialEq)]
Expand Down Expand Up @@ -2201,10 +2202,10 @@ pub trait Parser<'src, I: Input<'src>, O, E: ParserExtra<'src, I> = extra::Defau
///
fn boxed<'b>(self) -> Boxed<'src, 'b, I, O, E>
where
Self: Sized + 'b,
Self: Sized + Send + Sync + 'b,
{
Boxed {
inner: Rc::new(self),
inner: Arc::new(self),
}
}

Expand Down Expand Up @@ -2795,12 +2796,11 @@ where

/// See [`Parser::boxed`].
///
/// Due to current implementation details, the inner value is not, in fact, a [`Box`], but is an [`Rc`] to facilitate
/// efficient cloning. This is likely to change in the future. Unlike [`Box`], [`Rc`] has no size guarantees: although
/// Due to current implementation details, the inner value is not, in fact, a [`Box`], but is an [`Arc`] to facilitate
/// efficient cloning. This is likely to change in the future. Unlike [`Box`], [`Arc`] has no size guarantees: although
/// it is *currently* the same size as a raw pointer.
// TODO: Don't use an Rc (why?)
pub struct Boxed<'src, 'b, I: Input<'src>, O, E: ParserExtra<'src, I> = extra::Default> {
inner: Rc<DynParser<'src, 'b, I, O, E>>,
inner: Arc<DynParser<'src, 'b, I, O, E>>,
}

impl<'src, I: Input<'src>, O, E: ParserExtra<'src, I>> Clone for Boxed<'src, '_, I, O, E> {
Expand Down
6 changes: 3 additions & 3 deletions src/pratt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,9 +205,9 @@ where
/// Box this operator, allowing it to be used via dynamic dispatch.
fn boxed<'a>(self) -> Boxed<'src, 'a, I, O, E>
where
Self: Sized + 'a,
Self: Sized + Send + Sync + 'a,
{
Boxed(Rc::new(self))
Boxed(Arc::new(self))
}

#[doc(hidden)]
Expand Down Expand Up @@ -317,7 +317,7 @@ where
}

/// A boxed pratt parser operator. See [`Operator`].
pub struct Boxed<'src, 'a, I, O, E = extra::Default>(Rc<DynOperator<'src, 'a, I, O, E>>);
pub struct Boxed<'src, 'a, I, O, E = extra::Default>(Arc<DynOperator<'src, 'a, I, O, E>>);

impl<I, O, E> Clone for Boxed<'_, '_, I, O, E> {
fn clone(&self) -> Self {
Expand Down
4 changes: 2 additions & 2 deletions src/recovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,10 +239,10 @@ pub fn nested_delimiters<'src, 'parse, I, O, E, F, const N: usize>(
) -> impl Parser<'src, I, O, E> + Clone + 'parse
where
I: ValueInput<'src>,
I::Token: PartialEq + Clone,
I::Token: PartialEq + Clone + Send + Sync,
E: extra::ParserExtra<'src, I> + 'parse,
'src: 'parse,
F: Fn(I::Span) -> O + Clone + 'parse,
F: Fn(I::Span) -> O + Clone + Send + Sync + 'parse,
{
// TODO: Does this actually work? TESTS!
#[allow(clippy::tuple_array_conversions)]
Expand Down
55 changes: 11 additions & 44 deletions src/recursive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,35 +10,10 @@

use super::*;

struct OnceCell<T>(core::cell::Cell<Option<T>>);
impl<T> OnceCell<T> {
pub fn new() -> Self {
Self(core::cell::Cell::new(None))
}
pub fn set(&self, x: T) -> Result<(), ()> {
// SAFETY: Function is not reentrant so we have exclusive access to the inner data
unsafe {
let vacant = (*self.0.as_ptr()).is_none();
if vacant {
self.0.as_ptr().write(Some(x));
Ok(())
} else {
Err(())
}
}
}
#[inline]
pub fn get(&self) -> Option<&T> {
// SAFETY: We ensure that we never insert twice (so the inner `T` always lives as long as us, if it exists) and
// neither function is possibly reentrant so there's no way we can invalidate mut xor shared aliasing
unsafe { (*self.0.as_ptr()).as_ref() }
}
}

// TODO: Ensure that this doesn't produce leaks
enum RecursiveInner<T: ?Sized> {
Owned(Rc<T>),
Unowned(rc::Weak<T>),
Owned(Arc<T>),
Unowned(Weak<T>),
}

/// Type for recursive parsers that are defined through a call to `recursive`, and as such
Expand All @@ -48,7 +23,7 @@ pub type Direct<'src, 'b, I, O, Extra> = DynParser<'src, 'b, I, O, Extra>;
/// Type for recursive parsers that are defined through a call to [`Recursive::declare`], and as
/// such require an additional layer of allocation.
pub struct Indirect<'src, 'b, I: Input<'src>, O, Extra: ParserExtra<'src, I>> {
inner: OnceCell<Box<DynParser<'src, 'b, I, O, Extra>>>,
inner: spin::Once<Box<DynParser<'src, 'b, I, O, Extra>>>,
}

/// A parser that can be defined in terms of itself by separating its [declaration](Recursive::declare) from its
Expand Down Expand Up @@ -100,29 +75,21 @@ impl<'src, 'b, I: Input<'src>, O, E: ParserExtra<'src, I>> Recursive<Indirect<'s
/// ```
pub fn declare() -> Self {
Recursive {
inner: RecursiveInner::Owned(Rc::new(Indirect {
inner: OnceCell::new(),
inner: RecursiveInner::Owned(Arc::new(Indirect {
inner: spin::Once::new(),
})),
}
}

/// Defines the parser after declaring it, allowing it to be used for parsing.
// INFO: Clone bound not actually needed, but good to be safe for future compat
#[track_caller]
pub fn define<P: Parser<'src, I, O, E> + Clone + 'b>(&mut self, parser: P) {
let location = *Location::caller();
self.parser()
.inner
.set(Box::new(parser))
.unwrap_or_else(|_| {
panic!("recursive parsers can only be defined once, trying to redefine it at {location}")
});
pub fn define<P: Parser<'src, I, O, E> + Send + Sync + 'b>(&mut self, parser: P) {
self.parser().inner.call_once(|| Box::new(parser));
}
}

impl<P: ?Sized> Recursive<P> {
#[inline]
fn parser(&self) -> Rc<P> {
fn parser(&self) -> Arc<P> {
match &self.inner {
RecursiveInner::Owned(x) => x.clone(),
RecursiveInner::Unowned(x) => x
Expand Down Expand Up @@ -243,11 +210,11 @@ pub fn recursive<'src, 'b, I, O, E, A, F>(f: F) -> Recursive<Direct<'src, 'b, I,
where
I: Input<'src>,
E: ParserExtra<'src, I>,
A: Parser<'src, I, O, E> + Clone + 'b,
A: Parser<'src, I, O, E> + Send + Sync + 'b,
F: FnOnce(Recursive<Direct<'src, 'b, I, O, E>>) -> A,
{
let rc = Rc::new_cyclic(|rc| {
let rc: rc::Weak<DynParser<'src, 'b, I, O, E>> = rc.clone() as _;
let rc = Arc::new_cyclic(|rc| {
let rc: Weak<DynParser<'src, 'b, I, O, E>> = rc.clone() as _;
let parser = Recursive {
inner: RecursiveInner::Unowned(rc.clone()),
};
Expand Down
34 changes: 20 additions & 14 deletions src/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,8 @@ where
/// // ...including none at all!
/// assert_eq!(whitespace.parse("").into_result(), Ok(()));
/// ```
pub fn whitespace<'src, I, E>() -> Repeated<impl Parser<'src, I, (), E> + Copy, (), I, E>
pub fn whitespace<'src, I, E>(
) -> Repeated<impl Parser<'src, I, (), E> + Copy + Send + Sync, (), I, E>
where
I: StrInput<'src>,
I::Token: Char + 'src,
Expand Down Expand Up @@ -287,7 +288,8 @@ where
/// // ... but not newlines
/// assert!(inline_whitespace.at_least(1).parse("\n\r").has_errors());
/// ```
pub fn inline_whitespace<'src, I, E>() -> Repeated<impl Parser<'src, I, (), E> + Copy, (), I, E>
pub fn inline_whitespace<'src, I, E>(
) -> Repeated<impl Parser<'src, I, (), E> + Copy + Send + Sync, (), I, E>
where
I: StrInput<'src>,
I::Token: Char + 'src,
Expand Down Expand Up @@ -335,7 +337,7 @@ where
/// assert_eq!(newline.parse("\u{2029}").into_result(), Ok(()));
/// ```
#[must_use]
pub fn newline<'src, I, E>() -> impl Parser<'src, I, (), E> + Copy
pub fn newline<'src, I, E>() -> impl Parser<'src, I, (), E> + Copy + Send + Sync
where
I: StrInput<'src>,
I::Token: Char + 'src,
Expand Down Expand Up @@ -398,7 +400,7 @@ where
#[must_use]
pub fn digits<'src, I, E>(
radix: u32,
) -> Repeated<impl Parser<'src, I, <I as Input<'src>>::Token, E> + Copy, I::Token, I, E>
) -> Repeated<impl Parser<'src, I, <I as Input<'src>>::Token, E> + Copy + Send + Sync, I::Token, I, E>
where
I: StrInput<'src>,
I::Token: Char + 'src,
Expand Down Expand Up @@ -446,10 +448,12 @@ where
/// ```
///
#[must_use]
pub fn int<'src, I, E>(radix: u32) -> impl Parser<'src, I, <I as SliceInput<'src>>::Slice, E> + Copy
pub fn int<'src, I, E>(
radix: u32,
) -> impl Parser<'src, I, <I as SliceInput<'src>>::Slice, E> + Copy + Send + Sync
where
I: StrInput<'src>,
I::Token: Char + 'src,
I::Token: Char + Send + Sync + 'src,
E: ParserExtra<'src, I>,
E::Error:
LabelError<'src, I, TextExpected<'src, I>> + LabelError<'src, I, MaybeRef<'src, I::Token>>,
Expand Down Expand Up @@ -486,7 +490,8 @@ pub mod ascii {
/// An identifier is defined as an ASCII alphabetic character or an underscore followed by any number of alphanumeric
/// characters or underscores. The regex pattern for it is `[a-zA-Z_][a-zA-Z0-9_]*`.
#[must_use]
pub fn ident<'src, I, E>() -> impl Parser<'src, I, <I as SliceInput<'src>>::Slice, E> + Copy
pub fn ident<'src, I, E>(
) -> impl Parser<'src, I, <I as SliceInput<'src>>::Slice, E> + Copy + Send + Sync
where
I: StrInput<'src>,
I::Token: Char + 'src,
Expand Down Expand Up @@ -539,12 +544,12 @@ pub mod ascii {
#[track_caller]
pub fn keyword<'src, I, S, E>(
keyword: S,
) -> impl Parser<'src, I, <I as SliceInput<'src>>::Slice, E> + Clone + 'src
) -> impl Parser<'src, I, <I as SliceInput<'src>>::Slice, E> + Clone + Send + Sync + 'src
where
I: StrInput<'src>,
I::Slice: PartialEq,
I::Token: Char + fmt::Debug + 'src,
S: PartialEq<I::Slice> + Clone + 'src,
S: PartialEq<I::Slice> + Clone + Send + Sync + 'src,
E: ParserExtra<'src, I> + 'src,
E::Error: LabelError<'src, I, TextExpected<'src, I>> + LabelError<'src, I, S>,
{
Expand Down Expand Up @@ -981,7 +986,8 @@ pub mod unicode {
///
/// An identifier is defined as per "Default Identifiers" in [Unicode Standard Annex #31](https://www.unicode.org/reports/tr31/).
#[must_use]
pub fn ident<'src, I, E>() -> impl Parser<'src, I, <I as SliceInput<'src>>::Slice, E> + Copy
pub fn ident<'src, I, E>(
) -> impl Parser<'src, I, <I as SliceInput<'src>>::Slice, E> + Copy + Send + Sync
where
I: StrInput<'src>,
I::Token: Char + 'src,
Expand Down Expand Up @@ -1028,12 +1034,12 @@ pub mod unicode {
#[track_caller]
pub fn keyword<'src, I, S, E>(
keyword: S,
) -> impl Parser<'src, I, <I as SliceInput<'src>>::Slice, E> + Clone + 'src
) -> impl Parser<'src, I, <I as SliceInput<'src>>::Slice, E> + Clone + Send + Sync + 'src
where
I: StrInput<'src>,
I::Slice: PartialEq,
I::Token: Char + fmt::Debug + 'src,
S: PartialEq<I::Slice> + Clone + 'src,
S: PartialEq<I::Slice> + Clone + Send + Sync + 'src,
E: ParserExtra<'src, I> + 'src,
E::Error: LabelError<'src, I, TextExpected<'src, I>> + LabelError<'src, I, S>,
{
Expand Down Expand Up @@ -1095,7 +1101,7 @@ mod tests {
fn make_ascii_kw_parser<'src, I>(s: I::Slice) -> impl Parser<'src, I, ()>
where
I: crate::StrInput<'src>,
I::Slice: PartialEq + Clone,
I::Slice: PartialEq + Send + Sync + Clone,
I::Token: crate::Char + fmt::Debug + 'src,
{
text::ascii::keyword(s).ignored()
Expand All @@ -1104,7 +1110,7 @@ mod tests {
fn make_unicode_kw_parser<'src, I>(s: I::Slice) -> impl Parser<'src, I, ()>
where
I: crate::StrInput<'src>,
I::Slice: PartialEq + Clone,
I::Slice: PartialEq + Clone + Send + Sync,
I::Token: crate::Char + fmt::Debug + 'src,
{
text::unicode::keyword(s).ignored()
Expand Down
Loading