Skip to content
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
25 changes: 21 additions & 4 deletions examples/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ fn parser<'a>() -> impl Parser<'a, &'a str, Json, extra::Err<Rich<'a, char>>> {
.then(exp.or_not())
.to_slice()
.map(|s: &str| s.parse().unwrap())
.labelled("number")
.boxed();

let escape = just('\\')
Expand Down Expand Up @@ -68,6 +69,8 @@ fn parser<'a>() -> impl Parser<'a, &'a str, Json, extra::Err<Rich<'a, char>>> {
.to_slice()
.map(ToString::to_string)
.delimited_by(just('"'), just('"'))
.labelled("string")
.as_context()
.boxed();

let array = value
Expand All @@ -86,9 +89,16 @@ fn parser<'a>() -> impl Parser<'a, &'a str, Json, extra::Err<Rich<'a, char>>> {
.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(
Expand All @@ -104,12 +114,14 @@ fn parser<'a>() -> impl Parser<'a, &'a str, Json, extra::Err<Rich<'a, char>>> {
.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),
Expand Down Expand Up @@ -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()
Expand Down
43 changes: 30 additions & 13 deletions examples/mini_ml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
))
Expand Down Expand Up @@ -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((
Expand Down Expand Up @@ -183,8 +186,6 @@ fn parser<'tokens, 'src: 'tokens>() -> impl Parser<
})
.boxed(),
])
.labelled("expression")
.as_context()
})
}

Expand Down Expand Up @@ -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,
),
Expand Down Expand Up @@ -408,7 +409,7 @@ impl<'src> Vm<'src> {
fn failure(
msg: String,
label: (String, SimpleSpan),
extra_labels: impl IntoIterator<Item = (String, SimpleSpan)>,
extra_labels: impl IntoIterator<Item = (String, SimpleSpan, Color)>,
src: &str,
) -> ! {
let fname = "example";
Expand All @@ -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)]))
Expand All @@ -440,8 +441,24 @@ fn parse_failure(err: &Rich<impl fmt::Display>, 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,
)
}
Expand Down
65 changes: 33 additions & 32 deletions examples/nano_rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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('{'),
Expand All @@ -310,7 +313,9 @@ where
(Token::Ctrl('['), Token::Ctrl(']')),
],
|span| (Expr::Error, span),
)));
)))
.labelled("block")
.as_context();

let if_ = recursive(|if_| {
just(Token::If)
Expand Down Expand Up @@ -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| {
Expand Down Expand Up @@ -395,27 +379,39 @@ 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
.separated_by(just(Token::Ctrl(',')))
.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('{'),
Expand Down Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion examples/sample.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"origin": 981339097,
},
"though": ttrue asasjk,
"invalid": "\uDFFF",
"invalid": "hello \uDFFF world",
"activity": "value",
"office": -342325541.1937506,
"noise": false,
Expand Down
60 changes: 53 additions & 7 deletions src/combinator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1445,40 +1445,86 @@ where
}

/// See [`Parser::delimited_by`].
pub struct DelimitedBy<A, B, C, OB, OC> {
pub struct DelimitedBy<A, B, C, OB, OC, D = crate::primitive::Never, OD = Infallible> {
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<A: Copy, B: Copy, C: Copy, OB, OC> Copy for DelimitedBy<A, B, C, OB, OC> {}
impl<A: Clone, B: Clone, C: Clone, OB, OC> Clone for DelimitedBy<A, B, C, OB, OC> {
impl<A: Copy, B: Copy, C: Copy, OB, OC, D: Copy, OD> Copy for DelimitedBy<A, B, C, OB, OC, D, OD> {}
impl<A: Clone, B: Clone, C: Clone, OB, OC, D: Clone, OD> Clone
for DelimitedBy<A, B, C, OB, OC, D, OD>
{
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<A, B, C, OB, OC, D, OD> DelimitedBy<A, B, C, OB, OC, D, OD> {
/// 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<D2, OD2>(self, end: D2) -> DelimitedBy<A, B, C, OB, OC, D2, OD2> {
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<A, B, C, OB, OC>
impl<'src, I, E, A, B, C, OA, OB, OC, D, OD> Parser<'src, I, OA, E>
for DelimitedBy<A, B, C, OB, OC, D, OD>
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<M: Mode>(&self, inp: &mut InputRef<'src, '_, I, E>) -> PResult<M, OA> {
let before_start = inp.cursor();
self.start.go::<Check>(inp)?;
let start_span = inp.span_since(&before_start);
let a = self.parser.go::<M>(inp)?;
self.end.go::<Check>(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::<Check>(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::<Check>(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);
Expand Down
Loading