diff --git a/examples/json.rs b/examples/json.rs index 99c4206a..1d228ea7 100644 --- a/examples/json.rs +++ b/examples/json.rs @@ -35,6 +35,7 @@ fn parser<'a>() -> impl Parser<'a, &'a str, Json, extra::Err>> { .then(exp.or_not()) .to_slice() .map(|s: &str| s.parse().unwrap()) + .labelled("number") .boxed(); let escape = just('\\') @@ -68,6 +69,8 @@ fn parser<'a>() -> impl Parser<'a, &'a str, Json, extra::Err>> { .to_slice() .map(ToString::to_string) .delimited_by(just('"'), just('"')) + .labelled("string") + .as_context() .boxed(); let array = value @@ -86,9 +89,16 @@ fn parser<'a>() -> impl Parser<'a, &'a str, Json, extra::Err>> { .recover_with(via_parser(end())) .recover_with(skip_then_retry_until(any().ignored(), end())), ) + .labelled("array") + .as_context() .boxed(); - let member = string.clone().then_ignore(just(':').padded()).then(value); + let member = string + .clone() + .then_ignore(just(':').padded()) + .then(value) + .labelled("object member") + .as_context(); let object = member .clone() .separated_by(just(',').padded().recover_with(skip_then_retry_until( @@ -104,12 +114,14 @@ fn parser<'a>() -> impl Parser<'a, &'a str, Json, extra::Err>> { .recover_with(via_parser(end())) .recover_with(skip_then_retry_until(any().ignored(), end())), ) + .labelled("object") + .as_context() .boxed(); choice(( - just("null").to(Json::Null), - just("true").to(Json::Bool(true)), - just("false").to(Json::Bool(false)), + just("null").to(Json::Null).labelled("null"), + just("true").to(Json::Bool(true)).labelled("boolean"), + just("false").to(Json::Bool(false)).labelled("boolean"), number.map(Json::Num), string.map(Json::Str), array.map(Json::Array), @@ -150,6 +162,11 @@ fn main() { .with_message(e.reason().to_string()) .with_color(Color::Red), ) + .with_labels(e.contexts().next().map(|ctx| { + Label::new(((), ctx.span().into_range())) + .with_message(format!("while parsing this {}", ctx.pattern())) + .with_color(Color::Yellow) + })) .finish() .print(Source::from(&src)) .unwrap() diff --git a/examples/mini_ml.rs b/examples/mini_ml.rs index e318e46d..b82e5265 100644 --- a/examples/mini_ml.rs +++ b/examples/mini_ml.rs @@ -78,7 +78,8 @@ fn lexer<'src>( .repeated() .collect() .delimited_by(just('('), just(')')) - .labelled("token tree") + .with_mismatched_end(one_of("]})")) + .labelled("expression") .as_context() .map(Token::Parens), )) @@ -137,7 +138,9 @@ fn parser<'tokens, 'src: 'tokens>() -> impl Parser< lhs, rhs: Box::new(rhs), then: Box::new(then), - }), + }) + .labelled("expression") + .as_context(), )); choice(( @@ -183,8 +186,6 @@ fn parser<'tokens, 'src: 'tokens>() -> impl Parser< }) .boxed(), ]) - .labelled("expression") - .as_context() }) } @@ -257,8 +258,8 @@ impl Solver<'_> { format!("Type mismatch between {a_info} and {b_info}"), ("mismatch occurred here".to_string(), span), vec![ - (format!("{a_info}"), self.vars[a.0].1), - (format!("{b_info}"), self.vars[b.0].1), + (format!("{a_info}"), self.vars[a.0].1, Color::Yellow), + (format!("{b_info}"), self.vars[b.0].1, Color::Yellow), ], self.src, ), @@ -408,7 +409,7 @@ impl<'src> Vm<'src> { fn failure( msg: String, label: (String, SimpleSpan), - extra_labels: impl IntoIterator, + extra_labels: impl IntoIterator, src: &str, ) -> ! { let fname = "example"; @@ -420,10 +421,10 @@ fn failure( .with_message(label.0) .with_color(Color::Red), ) - .with_labels(extra_labels.into_iter().map(|label2| { - Label::new((fname, label2.1.into_range())) - .with_message(label2.0) - .with_color(Color::Yellow) + .with_labels(extra_labels.into_iter().map(|(pat, span, col)| { + Label::new((fname, span.into_range())) + .with_message(pat) + .with_color(col) })) .finish() .print(sources([(fname, src)])) @@ -440,8 +441,24 @@ fn parse_failure(err: &Rich, src: &str) -> ! { .unwrap_or_else(|| "end of input".to_string()), *err.span(), ), - err.contexts() - .map(|(l, s)| (format!("while parsing this {l}"), *s)), + err.reason() + .unclosed_delimiter() + .map(|ud| { + ( + "this delimiter was never closed".to_string(), + *ud, + Color::Blue, + ) + }) + .into_iter() + .chain(err.contexts().map(|ctx| { + ( + format!("while parsing this {}", ctx.pattern()), + *ctx.span(), + Color::Yellow, + ) + })) + .take(1), src, ) } diff --git a/examples/nano_rust.rs b/examples/nano_rust.rs index d10bd4d5..deb2df03 100644 --- a/examples/nano_rust.rs +++ b/examples/nano_rust.rs @@ -300,7 +300,10 @@ where // Blocks are expressions but delimited with braces let block = expr .clone() + // Empty blocks are permitted + .or(empty().map_with(|(), e| (Expr::Value(Value::Null), e.span()))) .delimited_by(just(Token::Ctrl('{')), just(Token::Ctrl('}'))) + .with_mismatched_end(one_of([Token::Ctrl(']'), Token::Ctrl(')')])) // Attempt to recover anything that looks like a block but contains errors .recover_with(via_parser(nested_delimiters( Token::Ctrl('{'), @@ -310,7 +313,9 @@ where (Token::Ctrl('['), Token::Ctrl(']')), ], |span| (Expr::Error, span), - ))); + ))) + .labelled("block") + .as_context(); let if_ = recursive(|if_| { just(Token::If) @@ -343,30 +348,9 @@ where (Expr::Then(Box::new(a), Box::new(b)), e.span()) }); - let block_recovery = nested_delimiters( - Token::Ctrl('{'), - Token::Ctrl('}'), - [ - (Token::Ctrl('('), Token::Ctrl(')')), - (Token::Ctrl('['), Token::Ctrl(']')), - ], - |span| (Expr::Error, span), - ); - block_chain - .labelled("block") // Expressions, chained by semicolons, are statements .or(inline_expr.clone()) - .recover_with(skip_then_retry_until( - block_recovery.ignored().or(any().ignored()), - one_of([ - Token::Ctrl(';'), - Token::Ctrl('}'), - Token::Ctrl(')'), - Token::Ctrl(']'), - ]) - .ignored(), - )) .foldl_with( just(Token::Ctrl(';')).ignore_then(expr.or_not()).repeated(), |a, b, e| { @@ -395,7 +379,7 @@ fn funcs_parser<'tokens, 'src: 'tokens, I>() -> impl Parser< where I: ValueInput<'tokens, Token = Token<'src>, Span = Span>, { - let ident = select! { Token::Ident(ident) => ident }; + let ident = select! { Token::Ident(ident) => ident }.labelled("identifier"); // Argument lists are just identifiers separated by commas, surrounded by parentheses let args = ident @@ -403,19 +387,31 @@ where .allow_trailing() .collect() .delimited_by(just(Token::Ctrl('(')), just(Token::Ctrl(')'))) - .labelled("function args"); + .with_mismatched_end(one_of([Token::Ctrl(']'), Token::Ctrl('}')])) + .labelled("argument list") + .as_context() + .recover_with(via_parser(nested_delimiters( + Token::Ctrl('('), + Token::Ctrl(')'), + [ + (Token::Ctrl('{'), Token::Ctrl('}')), + (Token::Ctrl('['), Token::Ctrl(']')), + ], + |_| Vec::new(), + ))); let func = just(Token::Fn) - .ignore_then( - ident - .map_with(|name, e| (name, e.span())) - .labelled("function name"), - ) + .ignore_then(ident.map_with(|name, e| (name, e.span()))) .then(args) .map_with(|start, e| (start, e.span())) .then( expr_parser() + // Empty function bodies are permitted + .or(empty().map_with(|(), e| (Expr::Value(Value::Null), e.span()))) .delimited_by(just(Token::Ctrl('{')), just(Token::Ctrl('}'))) + .with_mismatched_end(one_of([Token::Ctrl(']'), Token::Ctrl(')')])) + .labelled("function body") + .as_context() // Attempt to recover anything that looks like a function body but contains errors .recover_with(via_parser(nested_delimiters( Token::Ctrl('{'), @@ -610,9 +606,14 @@ fn main() { .with_message(e.reason().to_string()) .with_color(Color::Red), ) - .with_labels(e.contexts().map(|(label, span)| { - Label::new((filename.clone(), span.into_range())) - .with_message(format!("while parsing this {label}")) + .with_labels(e.reason().unclosed_delimiter().map(|ud| { + Label::new((filename.clone(), ud.into_range())) + .with_message("this delimiter was never closed") + .with_color(Color::Blue) + })) + .with_labels(e.contexts().take(1).map(|ctx| { + Label::new((filename.clone(), ctx.span().into_range())) + .with_message(format!("while parsing this {}", ctx.pattern())) .with_color(Color::Yellow) })) .finish() diff --git a/examples/sample.json b/examples/sample.json index d2ae6310..3655878a 100644 --- a/examples/sample.json +++ b/examples/sample.json @@ -15,7 +15,7 @@ "origin": 981339097, }, "though": ttrue asasjk, - "invalid": "\uDFFF", + "invalid": "hello \uDFFF world", "activity": "value", "office": -342325541.1937506, "noise": false, diff --git a/src/combinator.rs b/src/combinator.rs index ffbe000a..102f3ff8 100644 --- a/src/combinator.rs +++ b/src/combinator.rs @@ -1445,40 +1445,86 @@ where } /// See [`Parser::delimited_by`]. -pub struct DelimitedBy { +pub struct DelimitedBy { pub(crate) parser: A, pub(crate) start: B, pub(crate) end: C, + pub(crate) invalid_end: D, #[allow(dead_code)] - pub(crate) phantom: EmptyPhantom<(OB, OC)>, + pub(crate) phantom: EmptyPhantom<(OB, OC, OD)>, } -impl Copy for DelimitedBy {} -impl Clone for DelimitedBy { +impl Copy for DelimitedBy {} +impl Clone + for DelimitedBy +{ fn clone(&self) -> Self { Self { parser: self.parser.clone(), start: self.start.clone(), end: self.end.clone(), + invalid_end: self.invalid_end.clone(), + phantom: EmptyPhantom::new(), + } + } +} + +impl DelimitedBy { + /// Indicate that when the given mismatched pattern is found, errors should be marked as 'unclosed delimiters'. + /// + /// This can result in improved error messages in certain cases. + pub fn with_mismatched_end(self, end: D2) -> DelimitedBy { + DelimitedBy { + parser: self.parser, + start: self.start, + end: self.end, + invalid_end: end, phantom: EmptyPhantom::new(), } } } -impl<'src, I, E, A, B, C, OA, OB, OC> Parser<'src, I, OA, E> for DelimitedBy +impl<'src, I, E, A, B, C, OA, OB, OC, D, OD> Parser<'src, I, OA, E> + for DelimitedBy where I: Input<'src>, E: ParserExtra<'src, I>, A: Parser<'src, I, OA, E>, B: Parser<'src, I, OB, E>, C: Parser<'src, I, OC, E>, + D: Parser<'src, I, OD, E>, + I::Span: Clone, { #[inline(always)] fn go(&self, inp: &mut InputRef<'src, '_, I, E>) -> PResult { + let before_start = inp.cursor(); self.start.go::(inp)?; + let start_span = inp.span_since(&before_start); let a = self.parser.go::(inp)?; - self.end.go::(inp)?; - Ok(a) + // let before_errors = inp.errors.secondary.len(); + let before_end = inp.save(); + let old_alt = inp.take_alt(); + let scope_span = inp.span_since(&before_start); + let end_res = self.end.go::(inp); + let res = if end_res.is_err() { + let mut new_alt = inp.take_alt().expect("error, but no alt"); + inp.rewind(before_end); + if self.invalid_end.go::(inp).is_ok() { + new_alt + .err + .in_delimited(start_span.clone(), scope_span.clone()); + } + inp.errors.alt = old_alt; + inp.add_alt_err(&new_alt.pos, new_alt.err); + Err(()) + } else { + inp.errors.alt = old_alt; + Ok(a) + }; + // for err in inp.errors.secondary_errors_since(before_errors) { + // err.err.in_delimited(start_span.clone(), scope_span.clone()); + // } + res } go_extra!(OA); diff --git a/src/error.rs b/src/error.rs index 7614c122..28ecf993 100644 --- a/src/error.rs +++ b/src/error.rs @@ -76,6 +76,15 @@ pub use label::LabelError; pub trait Error<'a, I: Input<'a>>: Sized + LabelError<'a, I, DefaultExpected<'a, I::Token>> { + /// Indicates that this error occurred when parsing the final delimiter of a delimited parser failed (see [`Parser::delimited_by`]). + /// + /// The span `start` is the span of the starting delimiter. + /// + /// `scope` is the span from (and including) the starting delimiter, up to the location of the error. + fn in_delimited(&mut self, start: I::Span, scope: I::Span) { + #![allow(unused_variables)] + } + /// Merge two errors that point to the same input together, combining their information. #[inline(always)] fn merge(self, other: Self) -> Self { @@ -280,6 +289,7 @@ where /// An expected pattern for a [`Rich`] error. #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[non_exhaustive] pub enum RichPattern<'a, T> { /// A specific token. Token(MaybeRef<'a, T>), @@ -424,19 +434,22 @@ where /// The reason for a [`Rich`] error. #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub enum RichReason<'a, T> { +#[non_exhaustive] +pub enum RichReason<'a, T, S> { /// An unexpected input was found ExpectedFound { /// The tokens expected expected: Vec>, /// The tokens found found: Option>, + /// If `Some`, a delimiter that started a pattern did not have a matching close delimiter. + unclosed_delimiter: Option, }, /// An error with a custom message Custom(String), } -impl<'a, T> RichReason<'a, T> { +impl<'a, T, S> RichReason<'a, T, S> { /// Return the token that was found by this error reason. `None` implies that the end of input was expected. pub fn found(&self) -> Option<&T> { match self { @@ -445,15 +458,32 @@ impl<'a, T> RichReason<'a, T> { } } + /// If the reason for the error involves a delimiter that was never closed, return the span of that delimiter. + pub fn unclosed_delimiter(&self) -> Option<&S> { + if let Self::ExpectedFound { + unclosed_delimiter, .. + } = self + { + unclosed_delimiter.as_ref() + } else { + None + } + } + /// Convert this reason into an owned version of itself by cloning any borrowed internal tokens, if necessary. - pub fn into_owned<'b>(self) -> RichReason<'b, T> + pub fn into_owned<'b>(self) -> RichReason<'b, T, S> where T: Clone, { match self { - Self::ExpectedFound { found, expected } => RichReason::ExpectedFound { + Self::ExpectedFound { + found, + expected, + unclosed_delimiter, + } => RichReason::ExpectedFound { expected: expected.into_iter().map(RichPattern::into_owned).collect(), found: found.map(MaybeRef::into_owned), + unclosed_delimiter, }, Self::Custom(msg) => RichReason::Custom(msg), } @@ -470,39 +500,52 @@ impl<'a, T> RichReason<'a, T> { /// /// This is useful when you wish to combine errors from multiple compilation passes (lexing and parsing, say) where /// the token type for each pass is different (`char` vs `MyToken`, say). - pub fn map_token U>(self, mut f: F) -> RichReason<'a, U> + pub fn map_token U>(self, mut f: F) -> RichReason<'a, U, S> where T: Clone, { match self { - RichReason::ExpectedFound { expected, found } => RichReason::ExpectedFound { + RichReason::ExpectedFound { + expected, + found, + unclosed_delimiter, + } => RichReason::ExpectedFound { expected: expected .into_iter() .map(|pat| pat.map_token(&mut f)) .collect(), found: found.map(|found| f(found.into_inner()).into()), + unclosed_delimiter, }, RichReason::Custom(msg) => RichReason::Custom(msg), } } - fn inner_fmt( + fn inner_fmt( &self, f: &mut fmt::Formatter<'_>, mut fmt_token: impl FnMut(&T, &mut fmt::Formatter<'_>) -> fmt::Result, mut fmt_span: impl FnMut(&S, &mut fmt::Formatter<'_>) -> fmt::Result, span: Option<&S>, - context: &[(RichPattern<'a, T>, S)], + context: &[RichContext<'a, T, S>], ) -> fmt::Result { match self { - RichReason::ExpectedFound { expected, found } => { - write!(f, "found ")?; + RichReason::ExpectedFound { + expected, + found, + unclosed_delimiter, + } => { + if unclosed_delimiter.is_some() { + write!(f, "found mismatched delimiter ")?; + } else { + write!(f, "found ")?; + } write_token(f, &mut fmt_token, found.as_deref())?; if let Some(span) = span { write!(f, " at ")?; fmt_span(span, f)?; } - write!(f, " expected ")?; + write!(f, ", expected ")?; match &expected[..] { [] => write!(f, "something else")?, [expected] => expected.write(f, &mut fmt_token)?, @@ -524,17 +567,17 @@ impl<'a, T> RichReason<'a, T> { } } } - for (l, s) in context { + for ctx in context { write!(f, " in ")?; - l.write(f, &mut fmt_token)?; + ctx.pattern().write(f, &mut fmt_token)?; write!(f, " at ")?; - fmt_span(s, f)?; + fmt_span(ctx.span(), f)?; } Ok(()) } } -impl RichReason<'_, T> +impl RichReason<'_, T, S> where T: PartialEq, { @@ -548,9 +591,11 @@ where RichReason::ExpectedFound { expected: mut this_expected, found, + unclosed_delimiter: this_unclosed, }, RichReason::ExpectedFound { expected: mut other_expected, + unclosed_delimiter: other_unclosed, .. }, ) => { @@ -566,18 +611,43 @@ where RichReason::ExpectedFound { expected: this_expected, found, + unclosed_delimiter: this_unclosed.or(other_unclosed), } } } } } -impl fmt::Display for RichReason<'_, T> +impl fmt::Display for RichReason<'_, T, S> where T: fmt::Display, + S: fmt::Display, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.inner_fmt(f, T::fmt, |_: &(), _| Ok(()), None, &[]) + self.inner_fmt(f, T::fmt, |_, _| Ok(()), None, &[]) + } +} + +/// Represents a grammatical context in which a [`Rich`] error can appear. +/// +/// For example, in a Rust-like language, errors might appear within nested sets of expressions, blocks, modules, etc. +/// +/// Contexts can provide useful hints to your user about how to locate an error or why the erroneous syntax is not valid at a certain location. +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct RichContext<'a, T, S = SimpleSpan> { + pattern: RichPattern<'a, T>, + span: S, +} + +impl<'a, T, S> RichContext<'a, T, S> { + /// Get the [`RichPattern`] that identifies this context. + pub fn pattern(&self) -> &RichPattern<'a, T> { + &self.pattern + } + + /// Get the span associated with this context. + pub fn span(&self) -> &S { + &self.span } } @@ -605,8 +675,8 @@ where #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Rich<'a, T, S = SimpleSpan> { span: S, - reason: Box>, - context: Vec<(RichPattern<'a, T>, S)>, + reason: Box>, + context: Vec>, } impl Rich<'_, T, S> { @@ -646,12 +716,12 @@ impl<'a, T, S> Rich<'a, T, S> { } /// Get the reason for this error. - pub fn reason(&self) -> &RichReason<'a, T> { + pub fn reason(&self) -> &RichReason<'a, T, S> { &self.reason } /// Take the reason from this error. - pub fn into_reason(self) -> RichReason<'a, T> { + pub fn into_reason(self) -> RichReason<'a, T, S> { *self.reason } @@ -660,12 +730,14 @@ impl<'a, T, S> Rich<'a, T, S> { self.reason.found() } - /// Return an iterator over the labelled contexts of this error, from least general to most. + /// Return an iterator over the labelled contexts of this error, starting from that which appears closest to the error location. /// /// 'Context' here means parser patterns that the parser was in the process of parsing when the error occurred. To /// add labelled contexts, see [`Parser::labelled`]. - pub fn contexts(&self) -> impl Iterator, &S)> { - self.context.iter().map(|(l, s)| (l, s)) + pub fn contexts( + &self, + ) -> impl DoubleEndedIterator> + ExactSizeIterator { + self.context.iter() } /// Convert this error into an owned version of itself by cloning any borrowed internal tokens, if necessary. @@ -678,7 +750,10 @@ impl<'a, T, S> Rich<'a, T, S> { context: self .context .into_iter() - .map(|(p, s)| (p.into_owned(), s)) + .map(|ctx| RichContext { + pattern: ctx.pattern.into_owned(), + ..ctx + }) .collect(), ..self } @@ -706,7 +781,10 @@ impl<'a, T, S> Rich<'a, T, S> { context: self .context .into_iter() - .map(|(p, s)| (p.map_token(&mut f), s)) + .map(|ctx| RichContext { + pattern: ctx.pattern.map_token(&mut f), + span: ctx.span, + }) .collect(), } } @@ -716,13 +794,30 @@ impl<'a, I: Input<'a>> Error<'a, I> for Rich<'a, I::Token, I::Span> where I::Token: PartialEq, { + fn in_delimited(&mut self, start: I::Span, _scope: I::Span) { + if let RichReason::ExpectedFound { + unclosed_delimiter, .. + } = &mut *self.reason + { + if unclosed_delimiter.is_none() { + *unclosed_delimiter = Some(start); + } + } + // >::in_context(self, RichPattern::SomethingElse, scope); + } + #[inline] fn merge(self, other: Self) -> Self { let new_reason = self.reason.flat_merge(*other.reason); Self { span: self.span, reason: Box::new(new_reason), - context: self.context, // TOOD: Merge contexts + // TOOD: Merge context properly + context: if self.context.len() > other.context.len() { + self.context + } else { + other.context + }, } } } @@ -743,6 +838,7 @@ where reason: Box::new(RichReason::ExpectedFound { expected: expected.into_iter().map(|tok| tok.into()).collect(), found, + unclosed_delimiter: None, }), context: Vec::new(), } @@ -756,7 +852,9 @@ where _span: I::Span, ) -> Self { match &mut *self.reason { - RichReason::ExpectedFound { expected, found } => { + RichReason::ExpectedFound { + expected, found, .. + } => { for new_expected in new_expected { let new_expected = new_expected.into(); if !expected[..].contains(&new_expected) { @@ -767,7 +865,6 @@ where } RichReason::Custom(_) => {} } - // TOOD: Merge contexts self } @@ -780,15 +877,21 @@ where ) -> Self { self.span = span; match &mut *self.reason { - RichReason::ExpectedFound { expected, found } => { + RichReason::ExpectedFound { + expected, + found, + unclosed_delimiter, + } => { expected.clear(); expected.extend(new_expected.into_iter().map(|tok| tok.into())); *found = new_found; + *unclosed_delimiter = None; } _ => { *self.reason = RichReason::ExpectedFound { expected: new_expected.into_iter().map(|tok| tok.into()).collect(), found: new_found, + unclosed_delimiter: None, }; } } @@ -800,7 +903,9 @@ where fn label_with(&mut self, label: L) { // Opportunistically attempt to reuse allocations if we can match &mut *self.reason { - RichReason::ExpectedFound { expected, found: _ } => { + RichReason::ExpectedFound { + expected, found: _, .. + } => { expected.clear(); expected.push(label.into()); } @@ -808,6 +913,7 @@ where *self.reason = RichReason::ExpectedFound { expected: vec![label.into()], found: self.reason.take_found(), + unclosed_delimiter: None, }; } } @@ -816,8 +922,11 @@ where #[inline] fn in_context(&mut self, label: L, span: I::Span) { let label = label.into(); - if self.context.iter().all(|(l, _)| l != &label) { - self.context.push((label, span)); + if self.context.iter().all(|ctx| ctx.pattern != label) { + self.context.push(RichContext { + pattern: label, + span, + }); } } } diff --git a/src/lib.rs b/src/lib.rs index e5b3b05c..576b0013 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -100,6 +100,7 @@ use core::{ borrow::Borrow, cell::{Cell, RefCell}, cmp::{Eq, Ord, Ordering}, + convert::Infallible, fmt, hash::Hash, marker::PhantomData, @@ -1333,6 +1334,7 @@ pub trait Parser<'src, I: Input<'src>, O, E: ParserExtra<'src, I> = extra::Defau parser: self, start, end, + invalid_end: crate::primitive::Never, phantom: EmptyPhantom::new(), } } diff --git a/src/primitive.rs b/src/primitive.rs index 67cdb04d..6debca07 100644 --- a/src/primitive.rs +++ b/src/primitive.rs @@ -86,6 +86,32 @@ where go_extra!(()); } +/// See [`never`]. +#[derive(Copy, Clone)] +pub struct Never; + +/// A parser that always fails. +/// +/// The output type of this parser is [`core::convert::Infallible`]. In the future, this may be changed to [`!`]. +pub const fn never() -> Never { + Never +} + +impl<'src, I, E> Parser<'src, I, Infallible, E> for Never +where + I: Input<'src>, + E: ParserExtra<'src, I>, +{ + #[inline] + fn go(&self, inp: &mut InputRef<'src, '_, I, E>) -> PResult { + let span = inp.span_since(&inp.cursor()); + inp.add_alt([DefaultExpected::SomethingElse], None, span); + Err(()) + } + + go_extra!(Infallible); +} + /// Configuration for [`just`], used in [`ConfigParser::configure`] pub struct JustCfg { seq: Option, diff --git a/src/recovery.rs b/src/recovery.rs index ff30caa5..034908b6 100644 --- a/src/recovery.rs +++ b/src/recovery.rs @@ -240,6 +240,7 @@ pub fn nested_delimiters<'src, 'parse, I, O, E, F, const N: usize>( where I: ValueInput<'src>, I::Token: PartialEq + Clone, + I::Span: Clone, E: extra::ParserExtra<'src, I> + 'parse, 'src: 'parse, F: Fn(I::Span) -> O + Clone + 'parse, diff --git a/src/span.rs b/src/span.rs index b4660ec5..5ba308c8 100644 --- a/src/span.rs +++ b/src/span.rs @@ -30,7 +30,7 @@ pub trait Span { /// means that it's perfectly fine for tokens to have non-continuous spans that bear no relation to their actual /// location in the input stream. This is useful for languages with an AST-level macro system that need to /// correctly point to symbols in the macro input when producing errors. - type Offset: Clone; + type Offset: Clone + PartialOrd; /// Create a new span given a context and an offset range. fn new(context: Self::Context, range: Range) -> Self; @@ -151,7 +151,7 @@ where } } -impl Span for SimpleSpan { +impl Span for SimpleSpan { type Context = C; type Offset = T; @@ -191,7 +191,7 @@ impl> Span for (C, S) { } } -impl Span for Range { +impl Span for Range { type Context = (); type Offset = T;