parser: better messages for (group)

This commit is contained in:
René Kijewski 2025-08-07 20:40:06 +02:00
parent c0d6f0f0fc
commit 98ad5d5b3f
3 changed files with 292 additions and 21 deletions

View File

@ -571,33 +571,86 @@ impl<'a> Expr<'a> {
i: &mut InputStream<'a>,
level: Level<'_>,
) -> ParseResult<'a, WithSpan<'a, Box<Self>>> {
let start = ***i;
let expr = preceded(ws('('), opt(|i: &mut _| Self::parse(i, level, true))).parse_next(i)?;
let Some(expr) = expr else {
let _ = ')'.parse_next(i)?;
return Ok(WithSpan::new(Box::new(Self::Tuple(vec![])), start, i));
};
ws('(').parse_next(i)?;
Self::group_actually(i, level)
}
let comma = ws(opt(peek(','))).parse_next(i)?;
if comma.is_none() {
let _ = ')'.parse_next(i)?;
return Ok(WithSpan::new(Box::new(Self::Group(expr)), start, i));
// `Self::group()` is quite big. Let's only put it on the stack if needed.
#[inline(never)]
fn group_actually(
i: &mut InputStream<'a>,
level: Level<'_>,
) -> ParseResult<'a, WithSpan<'a, Box<Self>>> {
let (expr, span) = cut_err(|i: &mut _| Self::group_actually_inner(i, level))
.with_taken()
.parse_next(i)?;
Ok(WithSpan::new_with_full(expr, span.trim_ascii()))
}
#[inline]
fn group_actually_inner(
i: &mut InputStream<'a>,
level: Level<'_>,
) -> ParseResult<'a, Box<Self>> {
enum GroupResult<'a> {
Tuple(WithSpan<'a, Box<Expr<'a>>>),
Expr(Box<Expr<'a>>),
Err(&'static str),
}
let (expr, comma, closing) = (
opt(|i: &mut _| Self::parse(i, level, true)),
ws(opt(','.take())),
opt(')'),
)
.parse_next(i)?;
let result = match (expr, comma, closing) {
// `(expr,`
(Some(expr), Some(_), None) => GroupResult::Tuple(expr),
// `()`
(None, None, Some(_)) => GroupResult::Expr(Box::new(Self::Tuple(vec![]))),
// `(expr)`
(Some(expr), None, Some(_)) => GroupResult::Expr(Box::new(Self::Group(expr))),
// `(expr,)`
(Some(expr), Some(_), Some(_)) => GroupResult::Expr(Box::new(Self::Tuple(vec![expr]))),
// `(`
(None, None, None) => GroupResult::Err("expected closing `)` or an expression"),
// `(expr`
(Some(_), None, None) => GroupResult::Err("expected `,` or `)`"),
// `(,`
(None, Some(span), _) => return cut_error!("stray comma after opening `(`", span),
};
let expr = match result {
GroupResult::Tuple(expr) => expr,
GroupResult::Expr(expr) => return Ok(expr),
GroupResult::Err(msg) => return cut_error!(msg, ***i),
};
let mut exprs = vec![expr];
repeat(
0..,
preceded(',', ws(|i: &mut _| Self::parse(i, level, true))),
)
.fold(
|| (),
|(), expr| {
exprs.push(expr);
let collect_items = opt(separated(
1..,
|i: &mut _| {
exprs.push(Self::parse(i, level, true)?);
Ok(())
},
ws(','),
)
.parse_next(i)?;
let _ = (ws(opt(',')), ')').parse_next(i)?;
Ok(WithSpan::new(Box::new(Self::Tuple(exprs)), start, i))
.map(|()| ()));
let ((items, comma, close), span) = cut_err((collect_items, ws(opt(',')), opt(')')))
.with_taken()
.parse_next(i)?;
let msg = if items.is_none() {
"expected `)` or an expression"
} else if close.is_some() {
return Ok(Box::new(Self::Tuple(exprs)));
} else if comma.is_some() {
"expected `)` or an expression"
} else {
"expected `,` or `)`"
};
cut_error!(msg, span)
}
fn array(

View File

@ -0,0 +1,75 @@
use askama::Template;
#[derive(Template)]
#[template(source = r#"{% let test = ( %}"#, ext = "html")]
struct TruncatedInput;
#[derive(Template)]
#[template(source = r#"{% let test = (a, %}"#, ext = "html")]
struct TruncatedInput2;
#[derive(Template)]
#[template(source = r#"{% let test = (a, b %}"#, ext = "html")]
struct TruncatedInput3;
#[derive(Template)]
#[template(source = r#"{% let test = (a, b, %}"#, ext = "html")]
struct TruncatedInput4;
#[derive(Template)]
#[template(source = r#"{% let test = (() %}"#, ext = "html")]
struct MoreOpenThanClose;
#[derive(Template)]
#[template(source = r#"{% let test = ((()) %}"#, ext = "html")]
struct MoreOpenThanClose2;
#[derive(Template)]
#[template(source = r#"{% let test = (,) %}"#, ext = "html")]
struct ExtraneousComma;
#[derive(Template)]
#[template(source = r#"{% let test = (,,) %}"#, ext = "html")]
struct ExtraneousComma2;
#[derive(Template)]
#[template(source = r#"{% let test = (a,,) %}"#, ext = "html")]
struct ExtraneousComma3;
#[derive(Template)]
#[template(source = r#"{% let test = (a, b,,) %}"#, ext = "html")]
struct ExtraneousComma4;
#[derive(Template)]
#[template(source = r#"{% let test = (, %}"#, ext = "html")]
struct NoCloseAndExtraneousComma;
#[derive(Template)]
#[template(source = r#"{% let test = (,, %}"#, ext = "html")]
struct NoCloseAndExtraneousComma2;
#[derive(Template)]
#[template(source = r#"{% let test = (a,, %}"#, ext = "html")]
struct NoCloseAndExtraneousComma3;
#[derive(Template)]
#[template(source = r#"{% let test = (a, b,, %}"#, ext = "html")]
struct NoCloseAndExtraneousComma4;
#[derive(Template)]
#[template(source = r#"{% let test = (a b) %}"#, ext = "html")]
struct MissingComma;
#[derive(Template)]
#[template(source = r#"{% let test = (a b c) %}"#, ext = "html")]
struct MissingComma2;
#[derive(Template)]
#[template(source = r#"{% let test = (a b %}"#, ext = "html")]
struct NoCloseAndMissingComma;
#[derive(Template)]
#[template(source = r#"{% let test = (a b c %}"#, ext = "html")]
struct NoCloseAndMissingComma2;
fn main() {}

View File

@ -0,0 +1,143 @@
error: expected closing `)` or an expression
--> <source attribute>:1:16
"%}"
--> tests/ui/groups.rs:4:21
|
4 | #[template(source = r#"{% let test = ( %}"#, ext = "html")]
| ^^^^^^^^^^^^^^^^^^^^^^^
error: expected `)` or an expression
--> <source attribute>:1:18
"%}"
--> tests/ui/groups.rs:8:21
|
8 | #[template(source = r#"{% let test = (a, %}"#, ext = "html")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^
error: expected `,` or `)`
--> <source attribute>:1:18
"b %}"
--> tests/ui/groups.rs:12:21
|
12 | #[template(source = r#"{% let test = (a, b %}"#, ext = "html")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: expected `)` or an expression
--> <source attribute>:1:18
"b, %}"
--> tests/ui/groups.rs:16:21
|
16 | #[template(source = r#"{% let test = (a, b, %}"#, ext = "html")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: expected `,` or `)`
--> <source attribute>:1:18
"%}"
--> tests/ui/groups.rs:20:21
|
20 | #[template(source = r#"{% let test = (() %}"#, ext = "html")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^
error: expected `,` or `)`
--> <source attribute>:1:20
"%}"
--> tests/ui/groups.rs:24:21
|
24 | #[template(source = r#"{% let test = ((()) %}"#, ext = "html")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: stray comma after opening `(`
--> <source attribute>:1:15
",) %}"
--> tests/ui/groups.rs:28:21
|
28 | #[template(source = r#"{% let test = (,) %}"#, ext = "html")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^
error: stray comma after opening `(`
--> <source attribute>:1:15
",,) %}"
--> tests/ui/groups.rs:32:21
|
32 | #[template(source = r#"{% let test = (,,) %}"#, ext = "html")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
error: expected `)` or an expression
--> <source attribute>:1:17
",) %}"
--> tests/ui/groups.rs:36:21
|
36 | #[template(source = r#"{% let test = (a,,) %}"#, ext = "html")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: expected `)` or an expression
--> <source attribute>:1:18
"b,,) %}"
--> tests/ui/groups.rs:40:21
|
40 | #[template(source = r#"{% let test = (a, b,,) %}"#, ext = "html")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: stray comma after opening `(`
--> <source attribute>:1:15
", %}"
--> tests/ui/groups.rs:44:21
|
44 | #[template(source = r#"{% let test = (, %}"#, ext = "html")]
| ^^^^^^^^^^^^^^^^^^^^^^^^
error: stray comma after opening `(`
--> <source attribute>:1:15
",, %}"
--> tests/ui/groups.rs:48:21
|
48 | #[template(source = r#"{% let test = (,, %}"#, ext = "html")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^
error: expected `)` or an expression
--> <source attribute>:1:17
", %}"
--> tests/ui/groups.rs:52:21
|
52 | #[template(source = r#"{% let test = (a,, %}"#, ext = "html")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
error: expected `)` or an expression
--> <source attribute>:1:18
"b,, %}"
--> tests/ui/groups.rs:56:21
|
56 | #[template(source = r#"{% let test = (a, b,, %}"#, ext = "html")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: expected `,` or `)`
--> <source attribute>:1:17
"b) %}"
--> tests/ui/groups.rs:60:21
|
60 | #[template(source = r#"{% let test = (a b) %}"#, ext = "html")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: expected `,` or `)`
--> <source attribute>:1:17
"b c) %}"
--> tests/ui/groups.rs:64:21
|
64 | #[template(source = r#"{% let test = (a b c) %}"#, ext = "html")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: expected `,` or `)`
--> <source attribute>:1:17
"b %}"
--> tests/ui/groups.rs:68:21
|
68 | #[template(source = r#"{% let test = (a b %}"#, ext = "html")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
error: expected `,` or `)`
--> <source attribute>:1:17
"b c %}"
--> tests/ui/groups.rs:72:21
|
72 | #[template(source = r#"{% let test = (a b c %}"#, ext = "html")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^