Work on error messages
Signed-off-by: Marcel Müller <neikos@neikos.email>
This commit is contained in:
parent
7f7bf5c98d
commit
42698bb219
13 changed files with 333 additions and 160 deletions
|
|
@ -20,6 +20,7 @@ use winnow::error::AddContext;
|
|||
use winnow::error::FromRecoverableError;
|
||||
use winnow::error::ModalError;
|
||||
use winnow::error::ParserError;
|
||||
use winnow::stream::Location;
|
||||
use winnow::stream::Offset;
|
||||
use winnow::stream::Recoverable;
|
||||
use winnow::stream::Stream;
|
||||
|
|
@ -28,6 +29,7 @@ use winnow::token::any;
|
|||
|
||||
use crate::SourceSpan;
|
||||
use crate::errors::AstFailure;
|
||||
use crate::input::NomoInput;
|
||||
use crate::lexer::TemplateToken;
|
||||
use crate::lexer::TokenKind;
|
||||
use crate::lexer::TokenOperator;
|
||||
|
|
@ -50,6 +52,7 @@ pub struct AstError {
|
|||
pub(crate) message: Option<String>,
|
||||
pub(crate) help: Option<String>,
|
||||
pub(crate) span: Option<crate::SourceSpan>,
|
||||
pub(crate) replacement: Option<(SourceSpan, String)>,
|
||||
|
||||
is_fatal: bool,
|
||||
}
|
||||
|
|
@ -60,6 +63,7 @@ impl AstError {
|
|||
message: None,
|
||||
help: None,
|
||||
span: None,
|
||||
replacement: None,
|
||||
|
||||
is_fatal: false,
|
||||
}
|
||||
|
|
@ -74,6 +78,11 @@ impl AstError {
|
|||
self.help = Some(help.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
fn replacement(mut self, span: SourceSpan, replacement: &str) -> Self {
|
||||
self.replacement = Some((span, replacement.to_string()));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ModalError for AstError {
|
||||
|
|
@ -132,6 +141,7 @@ impl AddContext<Input<'_>, AstError> for AstError {
|
|||
) -> Self {
|
||||
self.message = context.message.or(self.message);
|
||||
self.help = context.help.or(self.help);
|
||||
self.replacement = context.replacement.or(self.replacement);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
@ -162,7 +172,7 @@ impl<'input> Parser<Input<'input>, TemplateToken, AstError> for TokenKind {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn parse(input: &[TemplateToken]) -> Result<TemplateAst<'_>, AstFailure> {
|
||||
pub fn parse(source: NomoInput, input: &[TemplateToken]) -> Result<TemplateAst<'_>, AstFailure> {
|
||||
let (_remaining, val, errors) = parse_asts.recoverable_parse(TokenSlice::new(input));
|
||||
|
||||
if errors.is_empty()
|
||||
|
|
@ -170,7 +180,7 @@ pub fn parse(input: &[TemplateToken]) -> Result<TemplateAst<'_>, AstFailure> {
|
|||
{
|
||||
Ok(TemplateAst { root: val })
|
||||
} else {
|
||||
Err(AstFailure::from_errors(errors))
|
||||
Err(AstFailure::from_errors(errors, source))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -309,26 +319,42 @@ fn parse_action<'input>(input: &mut Input<'input>) -> Result<TemplateAstExpr<'in
|
|||
alt((
|
||||
parse_conditional_chain,
|
||||
parse_for_chain,
|
||||
(parse_block(
|
||||
cut_err(not(repeat_till(
|
||||
0..,
|
||||
parse_expression,
|
||||
peek((ws, TokenKind::RightDelim)),
|
||||
)
|
||||
.map(|((), _)| ())))
|
||||
.context(
|
||||
AstError::ctx()
|
||||
.msg("Standlone action block")
|
||||
.help("If you want to output this expression, add a '=' to the block"),
|
||||
)
|
||||
parse_block(parse_unknown_action)
|
||||
.take()
|
||||
.map(TemplateAstExpr::Invalid),
|
||||
)),
|
||||
)),
|
||||
)
|
||||
.parse_next(input)
|
||||
}
|
||||
|
||||
fn parse_unknown_action<'input>(
|
||||
input: &mut Input<'input>,
|
||||
) -> Result<TemplateAstExpr<'input>, AstError> {
|
||||
let expression = peek(parse_expression).parse_next(input);
|
||||
|
||||
let is_expr = match expression {
|
||||
Ok(_) => true,
|
||||
Err(err) if err.is_backtrack() => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if is_expr {
|
||||
return Err(AstError::ctx()
|
||||
.msg("Standlone action block")
|
||||
.help("If you want to output this expression, add a '=' to the block")
|
||||
.cut());
|
||||
}
|
||||
|
||||
let keywords = peek(parse_keyword).parse_next(input);
|
||||
|
||||
let is_keyword = keywords.is_ok();
|
||||
|
||||
if is_keyword {
|
||||
return Err(AstError::ctx().msg("Unexpected keyword").cut());
|
||||
}
|
||||
|
||||
Err(AstError::ctx().cut())
|
||||
}
|
||||
fn parse_for_chain<'input>(input: &mut Input<'input>) -> Result<TemplateAstExpr<'input>, AstError> {
|
||||
trace("for_loop", |input: &mut Input<'input>| {
|
||||
let for_block = parse_for_loop.map(Box::new).parse_next(input)?;
|
||||
|
|
@ -368,26 +394,34 @@ fn parse_for_chain<'input>(input: &mut Input<'input>) -> Result<TemplateAstExpr<
|
|||
fn parse_for_loop<'input>(input: &mut Input<'input>) -> Result<TemplateAstExpr<'input>, AstError> {
|
||||
trace(
|
||||
"for_loop_inner",
|
||||
parse_block(
|
||||
(
|
||||
ws,
|
||||
TokenKind::For,
|
||||
ws,
|
||||
cut_err(TokenKind::Ident.context(AstError::ctx().msg("Expected identifier here"))),
|
||||
ws,
|
||||
cut_err(
|
||||
TokenKind::In.context(AstError::ctx().msg("Missing `in` in `for _ in <expr>`")),
|
||||
parse_block(|input: &mut Input<'input>| {
|
||||
let _ignored = surrounded(ws, TokenKind::For).parse_next(input)?;
|
||||
|
||||
let value_ident =
|
||||
cut_err(TokenKind::Ident.context(AstError::ctx().msg("Missing ident here")))
|
||||
.parse_next(input)?;
|
||||
|
||||
let _ignored = cut_err(
|
||||
preceded(ws, TokenKind::In).context(
|
||||
AstError::ctx()
|
||||
.msg("Missing `in` in `for .. in ` loop")
|
||||
.replacement(
|
||||
SourceSpan {
|
||||
range: input.current_token_start()..(input.current_token_start()),
|
||||
},
|
||||
" in",
|
||||
),
|
||||
),
|
||||
ws,
|
||||
parse_expression.map(Box::new),
|
||||
)
|
||||
.map(|(_, _for, _, value_ident, _, _in, _, value_expression)| {
|
||||
TemplateAstExpr::For {
|
||||
value_ident,
|
||||
value_expression,
|
||||
}
|
||||
}),
|
||||
),
|
||||
.parse_next(input)?;
|
||||
|
||||
let value_expression = parse_expression.map(Box::new).parse_next(input)?;
|
||||
|
||||
Ok(TemplateAstExpr::For {
|
||||
value_ident,
|
||||
value_expression,
|
||||
})
|
||||
}),
|
||||
)
|
||||
.parse_next(input)
|
||||
}
|
||||
|
|
@ -614,22 +648,25 @@ fn parse_operand<'input>(input: &mut Input<'input>) -> Result<TemplateAstExpr<'i
|
|||
.parse_next(input)
|
||||
}
|
||||
|
||||
fn parse_keywords_fail<'input>(
|
||||
input: &mut Input<'input>,
|
||||
) -> Result<TemplateAstExpr<'input>, AstError> {
|
||||
let value = alt((
|
||||
fn parse_keyword<'input>(input: &mut Input<'input>) -> Result<TemplateToken, AstError> {
|
||||
alt((
|
||||
TokenKind::ConditionalIf,
|
||||
TokenKind::ConditionalElse,
|
||||
TokenKind::For,
|
||||
TokenKind::End,
|
||||
TokenKind::In,
|
||||
))
|
||||
.take()
|
||||
.map(TemplateAstExpr::Invalid)
|
||||
.parse_next(input)?;
|
||||
.parse_next(input)
|
||||
}
|
||||
|
||||
cut_err(fail::<_, (), _>.context(AstError::ctx().msg("Found literal, expected expression")))
|
||||
.value(value)
|
||||
.parse_next(input)
|
||||
fn parse_keywords_fail<'input>(
|
||||
input: &mut Input<'input>,
|
||||
) -> Result<TemplateAstExpr<'input>, AstError> {
|
||||
let _value = parse_keyword.parse_next(input)?;
|
||||
|
||||
Err(AstError::ctx()
|
||||
.msg("Found literal, expected expression")
|
||||
.cut())
|
||||
}
|
||||
|
||||
fn parse_literal<'input>(input: &mut Input<'input>) -> Result<TemplateAstExpr<'input>, AstError> {
|
||||
|
|
@ -735,6 +772,7 @@ mod tests {
|
|||
use winnow::combinator::fail;
|
||||
use winnow::stream::TokenSlice;
|
||||
|
||||
use crate::input::NomoInput;
|
||||
use crate::lexer::TokenKind;
|
||||
use crate::parser::AstError;
|
||||
use crate::parser::AstFailure;
|
||||
|
|
@ -744,25 +782,22 @@ mod tests {
|
|||
use crate::parser::parse_block;
|
||||
use crate::parser::parse_end;
|
||||
|
||||
fn panic_pretty<'a>(
|
||||
input: &'_ str,
|
||||
tokens: Result<TemplateAst<'a>, AstFailure>,
|
||||
) -> TemplateAst<'a> {
|
||||
fn panic_pretty<'a>(tokens: Result<TemplateAst<'a>, AstFailure>) -> TemplateAst<'a> {
|
||||
match tokens {
|
||||
Ok(ast) => ast,
|
||||
Err(failure) => {
|
||||
panic!("{}", failure.to_report(input));
|
||||
panic!("{}", failure);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_only_content() {
|
||||
let input = "Hello World";
|
||||
let input = NomoInput::from("Hello World");
|
||||
|
||||
let parsed = crate::lexer::parse(input.into()).unwrap();
|
||||
let parsed = crate::lexer::parse(input.clone()).unwrap();
|
||||
|
||||
let ast = parse(parsed.tokens()).unwrap();
|
||||
let ast = parse(input, parsed.tokens()).unwrap();
|
||||
|
||||
insta::assert_debug_snapshot!(ast, @r#"
|
||||
TemplateAst {
|
||||
|
|
@ -777,11 +812,11 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn check_simple_variable_interpolation() {
|
||||
let input = "Hello {{= world }}";
|
||||
let input = NomoInput::from("Hello {{= world }}");
|
||||
|
||||
let parsed = crate::lexer::parse(input.into()).unwrap();
|
||||
let parsed = crate::lexer::parse(input.clone()).unwrap();
|
||||
|
||||
let ast = parse(parsed.tokens()).unwrap();
|
||||
let ast = parse(input, parsed.tokens()).unwrap();
|
||||
|
||||
insta::assert_debug_snapshot!(ast, @r#"
|
||||
TemplateAst {
|
||||
|
|
@ -805,11 +840,11 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn check_simple_if() {
|
||||
let input = "{{ if foo }} Hiii {{ end }}";
|
||||
let input = NomoInput::from("{{ if foo }} Hiii {{ end }}");
|
||||
|
||||
let parsed = crate::lexer::parse(input.into()).unwrap();
|
||||
let parsed = crate::lexer::parse(input.clone()).unwrap();
|
||||
|
||||
let ast = panic_pretty(input, parse(parsed.tokens()));
|
||||
let ast = panic_pretty(parse(input, parsed.tokens()));
|
||||
|
||||
insta::assert_debug_snapshot!(ast, @r#"
|
||||
TemplateAst {
|
||||
|
|
@ -850,35 +885,39 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn check_invalid_action() {
|
||||
let input = r#"{{ value }}
|
||||
let input = NomoInput::from(
|
||||
r#"{{ value }}
|
||||
{{ value }}
|
||||
{{ value }}
|
||||
{{ value }}
|
||||
{{ value }}"#;
|
||||
{{ value }}"#,
|
||||
);
|
||||
|
||||
let parsed = crate::lexer::parse(input.into()).unwrap();
|
||||
let parsed = crate::lexer::parse(input.clone()).unwrap();
|
||||
|
||||
let ast = parse(parsed.tokens()).unwrap_err();
|
||||
let ast = parse(input, parsed.tokens()).unwrap_err();
|
||||
|
||||
insta::assert_snapshot!(ast.to_report(input));
|
||||
insta::assert_snapshot!(ast);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_nested_simple_if() {
|
||||
let input = r#"{{ if foo }}
|
||||
let input = NomoInput::from(
|
||||
r#"{{ if foo }}
|
||||
{{ if bar }}
|
||||
Hiii
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{= value }}
|
||||
"#;
|
||||
"#,
|
||||
);
|
||||
|
||||
let parsed = crate::lexer::parse(input.into()).unwrap();
|
||||
let parsed = crate::lexer::parse(input.clone()).unwrap();
|
||||
|
||||
insta::assert_debug_snapshot!("simple_if_tokens", parsed);
|
||||
|
||||
let ast = panic_pretty(input, parse(parsed.tokens()));
|
||||
let ast = panic_pretty(parse(input, parsed.tokens()));
|
||||
|
||||
insta::assert_debug_snapshot!("simple_if_ast", ast);
|
||||
}
|
||||
|
|
@ -887,9 +926,9 @@ mod tests {
|
|||
fn check_parsing_block() {
|
||||
use winnow::RecoverableParser;
|
||||
|
||||
let input = "{{ foo }}";
|
||||
let input = NomoInput::from("{{ foo }}");
|
||||
|
||||
let parsed = crate::lexer::parse(input.into()).unwrap();
|
||||
let parsed = crate::lexer::parse(input).unwrap();
|
||||
|
||||
let result = alt((
|
||||
parse_end,
|
||||
|
|
@ -924,6 +963,7 @@ mod tests {
|
|||
range: 2..6,
|
||||
},
|
||||
),
|
||||
replacement: None,
|
||||
is_fatal: false,
|
||||
},
|
||||
],
|
||||
|
|
@ -933,11 +973,11 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn check_empty_if_output() {
|
||||
let input = "{{ if foo }}{{ end }}";
|
||||
let input = NomoInput::from("{{ if foo }}{{ end }}");
|
||||
|
||||
let parsed = crate::lexer::parse(input.into()).unwrap();
|
||||
let parsed = crate::lexer::parse(input.clone()).unwrap();
|
||||
|
||||
let ast = panic_pretty(input, parse(parsed.tokens()));
|
||||
let ast = panic_pretty(parse(input, parsed.tokens()));
|
||||
|
||||
insta::assert_debug_snapshot!(ast, @r#"
|
||||
TemplateAst {
|
||||
|
|
@ -970,99 +1010,101 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn check_if_else() {
|
||||
let input = "{{ if foo }} foo {{ else }} bar {{ end }}";
|
||||
let input = NomoInput::from("{{ if foo }} foo {{ else }} bar {{ end }}");
|
||||
|
||||
let parsed = crate::lexer::parse(input.into()).unwrap();
|
||||
let parsed = crate::lexer::parse(input.clone()).unwrap();
|
||||
|
||||
let ast = panic_pretty(input, parse(parsed.tokens()));
|
||||
let ast = panic_pretty(parse(input, parsed.tokens()));
|
||||
|
||||
insta::assert_debug_snapshot!(ast);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_if_else_if() {
|
||||
let input = "{{ if foo }} foo {{ else if bar }} bar {{ end }}";
|
||||
let input = NomoInput::from("{{ if foo }} foo {{ else if bar }} bar {{ end }}");
|
||||
|
||||
let parsed = crate::lexer::parse(input.into()).unwrap();
|
||||
let parsed = crate::lexer::parse(input.clone()).unwrap();
|
||||
|
||||
let ast = panic_pretty(input, parse(parsed.tokens()));
|
||||
let ast = panic_pretty(parse(input, parsed.tokens()));
|
||||
|
||||
insta::assert_debug_snapshot!(ast);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_trim_whitespace() {
|
||||
let input = "{{ if foo -}} foo {{- else if bar -}} bar {{- end }}";
|
||||
let input = NomoInput::from("{{ if foo -}} foo {{- else if bar -}} bar {{- end }}");
|
||||
|
||||
let parsed = crate::lexer::parse(input.into()).unwrap();
|
||||
let parsed = crate::lexer::parse(input.clone()).unwrap();
|
||||
|
||||
let ast = panic_pretty(input, parse(parsed.tokens()));
|
||||
let ast = panic_pretty(parse(input, parsed.tokens()));
|
||||
|
||||
insta::assert_debug_snapshot!(ast);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_for_loop() {
|
||||
let input = "{{ for value in array }} Hi: {{= value }} {{ else }} No Content :C {{ end }}";
|
||||
let input = NomoInput::from(
|
||||
"{{ for value in array }} Hi: {{= value }} {{ else }} No Content :C {{ end }}",
|
||||
);
|
||||
|
||||
let parsed = crate::lexer::parse(input.into()).unwrap();
|
||||
let parsed = crate::lexer::parse(input.clone()).unwrap();
|
||||
|
||||
let ast = panic_pretty(input, parse(parsed.tokens()));
|
||||
let ast = panic_pretty(parse(input, parsed.tokens()));
|
||||
|
||||
insta::assert_debug_snapshot!(ast);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_math_expression() {
|
||||
let input = "{{= 5 * 3 + 2 / 3 }}";
|
||||
let input = NomoInput::from("{{= 5 * 3 + 2 / 3 }}");
|
||||
|
||||
let parsed = crate::lexer::parse(input.into()).unwrap();
|
||||
let parsed = crate::lexer::parse(input.clone()).unwrap();
|
||||
|
||||
let ast = panic_pretty(input, parse(parsed.tokens()));
|
||||
let ast = panic_pretty(parse(input, parsed.tokens()));
|
||||
|
||||
insta::assert_debug_snapshot!(ast);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_logical_expression() {
|
||||
let input = "{{= true && false || 3 >= 2 && 5 == 2 }}";
|
||||
let input = NomoInput::from("{{= true && false || 3 >= 2 && 5 == 2 }}");
|
||||
|
||||
let parsed = crate::lexer::parse(input.into()).unwrap();
|
||||
let parsed = crate::lexer::parse(input.clone()).unwrap();
|
||||
|
||||
let ast = panic_pretty(input, parse(parsed.tokens()));
|
||||
let ast = panic_pretty(parse(input, parsed.tokens()));
|
||||
|
||||
insta::assert_debug_snapshot!(ast);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_function_call() {
|
||||
let input = "{{= foo(2 * 3, bar(2 + baz)) }}";
|
||||
let input = NomoInput::from("{{= foo(2 * 3, bar(2 + baz)) }}");
|
||||
|
||||
let parsed = crate::lexer::parse(input.into()).unwrap();
|
||||
let parsed = crate::lexer::parse(input.clone()).unwrap();
|
||||
|
||||
let ast = panic_pretty(input, parse(parsed.tokens()));
|
||||
let ast = panic_pretty(parse(input, parsed.tokens()));
|
||||
|
||||
insta::assert_debug_snapshot!(ast);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_conditional_access() {
|
||||
let input = "{{= foo? }}";
|
||||
let input = NomoInput::from("{{= foo? }}");
|
||||
|
||||
let parsed = crate::lexer::parse(input.into()).unwrap();
|
||||
let parsed = crate::lexer::parse(input.clone()).unwrap();
|
||||
|
||||
let ast = panic_pretty(input, parse(parsed.tokens()));
|
||||
let ast = panic_pretty(parse(input, parsed.tokens()));
|
||||
|
||||
insta::assert_debug_snapshot!(ast);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_access_operator() {
|
||||
let input = "{{= foo?.bar }}";
|
||||
let input = NomoInput::from("{{= foo?.bar }}");
|
||||
|
||||
let parsed = crate::lexer::parse(input.into()).unwrap();
|
||||
let parsed = crate::lexer::parse(input.clone()).unwrap();
|
||||
|
||||
let ast = panic_pretty(input, parse(parsed.tokens()));
|
||||
let ast = panic_pretty(parse(input, parsed.tokens()));
|
||||
|
||||
insta::assert_debug_snapshot!(ast);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
source: src/parser/mod.rs
|
||||
expression: ast.to_report(input)
|
||||
expression: ast
|
||||
---
|
||||
[1m[91merror[0m[1m: Standlone action block[0m
|
||||
[1m[94m ╭▸ [0m
|
||||
|
|
@ -8,23 +8,31 @@ expression: ast.to_report(input)
|
|||
[1m[94m│[0m [1m[91m━━━━━[0m
|
||||
[1m[94m│[0m
|
||||
[1m[94m╰ [0m[1mhelp[0m: If you want to output this expression, add a '=' to the block
|
||||
|
||||
[1m[91merror[0m[1m: Standlone action block[0m
|
||||
[1m[94m ╭▸ [0m
|
||||
[1m[94m2[0m [1m[94m│[0m {{ value }}
|
||||
[1m[94m│[0m [1m[91m━━━━━[0m
|
||||
[1m[94m│[0m
|
||||
[1m[94m╰ [0m[1mhelp[0m: If you want to output this expression, add a '=' to the block
|
||||
|
||||
[1m[91merror[0m[1m: Standlone action block[0m
|
||||
[1m[94m ╭▸ [0m
|
||||
[1m[94m3[0m [1m[94m│[0m {{ value }}
|
||||
[1m[94m│[0m [1m[91m━━━━━[0m
|
||||
[1m[94m│[0m
|
||||
[1m[94m╰ [0m[1mhelp[0m: If you want to output this expression, add a '=' to the block
|
||||
|
||||
[1m[91merror[0m[1m: Standlone action block[0m
|
||||
[1m[94m ╭▸ [0m
|
||||
[1m[94m4[0m [1m[94m│[0m {{ value }}
|
||||
[1m[94m│[0m [1m[91m━━━━━[0m
|
||||
[1m[94m│[0m
|
||||
[1m[94m╰ [0m[1mhelp[0m: If you want to output this expression, add a '=' to the block
|
||||
|
||||
[1m[91merror[0m[1m: Standlone action block[0m
|
||||
[1m[94m ╭▸ [0m
|
||||
[1m[94m5[0m [1m[94m│[0m {{ value }}
|
||||
[1m[94m│[0m [1m[91m━━━━━[0m
|
||||
[1m[94m│[0m
|
||||
[1m[94m╰ [0m[1mhelp[0m: If you want to output this expression, add a '=' to the block
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue