if guard empty condition message

This commit is contained in:
Folkert 2021-02-20 16:30:56 +01:00
parent bcf87f5df6
commit 576b7974e8
6 changed files with 122 additions and 23 deletions

View file

@ -2,8 +2,8 @@ use crate::ast::{
AssignedField, Attempting, CommentOrNewline, Def, Expr, Pattern, Spaceable, TypeAnnotation, AssignedField, Attempting, CommentOrNewline, Def, Expr, Pattern, Spaceable, TypeAnnotation,
}; };
use crate::blankspace::{ use crate::blankspace::{
line_comment, space0, space0_after, space0_around, space0_before, space0_before_e, space1, line_comment, space0, space0_after, space0_around, space0_around_e, space0_before,
space1_around, space1_before, spaces_exactly, space0_before_e, space1, space1_around, space1_before, spaces_exactly,
}; };
use crate::ident::{global_tag_or_ident, ident, lowercase_ident, Ident}; use crate::ident::{global_tag_or_ident, ident, lowercase_ident, Ident};
use crate::keyword; use crate::keyword;
@ -11,10 +11,10 @@ use crate::number_literal::number_literal;
use crate::parser::{ use crate::parser::{
self, allocated, and_then_with_indent_level, ascii_char, ascii_string, attempt, backtrackable, self, allocated, and_then_with_indent_level, ascii_char, ascii_string, attempt, backtrackable,
fail, map, newline_char, not, not_followed_by, optional, sep_by1, specialize, specialize_ref, fail, map, newline_char, not, not_followed_by, optional, sep_by1, specialize, specialize_ref,
then, unexpected, unexpected_eof, word2, EExpr, Either, ParseResult, Parser, State, then, unexpected, unexpected_eof, word1, word2, EExpr, Either, ParseResult, Parser, State,
SyntaxError, When, SyntaxError, When,
}; };
use crate::pattern::{loc_closure_param, loc_pattern}; use crate::pattern::loc_closure_param;
use crate::type_annotation; use crate::type_annotation;
use bumpalo::collections::string::String; use bumpalo::collections::string::String;
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
@ -1150,19 +1150,43 @@ mod when {
min_indent: u16, min_indent: u16,
) -> impl Parser<'a, (Vec<'a, Located<Pattern<'a>>>, Option<Located<Expr<'a>>>), SyntaxError<'a>> ) -> impl Parser<'a, (Vec<'a, Located<Pattern<'a>>>, Option<Located<Expr<'a>>>), SyntaxError<'a>>
{ {
specialize(
|e, r, c| SyntaxError::Expr(EExpr::When(e, r, c)),
branch_alternatives_help(min_indent),
)
}
fn branch_alternatives_help<'a>(
min_indent: u16,
) -> impl Parser<'a, (Vec<'a, Located<Pattern<'a>>>, Option<Located<Expr<'a>>>), When<'a>> {
and!( and!(
sep_by1( sep_by1(
ascii_char(b'|'), word1(b'|', When::Bar),
space0_around(loc_pattern(min_indent), min_indent), space0_around_e(
specialize(When::Pattern, crate::pattern::loc_pattern_help(min_indent)),
min_indent,
When::Space,
When::IndentPattern
), ),
optional(skip_first!( ),
parser::keyword(keyword::IF, min_indent), one_of![
map!(
skip_first!(
parser::keyword_e(keyword::IF, When::IfToken),
// TODO we should require space before the expression but not after // TODO we should require space before the expression but not after
space1_around( space0_around_e(
loc!(move |arena, state| parse_expr(min_indent, arena, state)), loc!(specialize_ref(When::IfGuard, move |arena, state| {
min_indent parse_expr(min_indent, arena, state)
})),
min_indent,
When::Space,
When::IndentIfGuard,
) )
)) ),
Some
),
|_, s| Ok((NoProgress, None, s))
]
) )
} }

View file

@ -390,6 +390,10 @@ pub enum When<'a> {
Is(Row, Col), Is(Row, Col),
Pattern(EPattern<'a>, Row, Col), Pattern(EPattern<'a>, Row, Col),
Arrow(Row, Col), Arrow(Row, Col),
Bar(Row, Col),
IfToken(Row, Col),
// TODO make EExpr
IfGuard(&'a SyntaxError<'a>, Row, Col),
Condition(&'a EExpr<'a>, Row, Col), Condition(&'a EExpr<'a>, Row, Col),
Branch(&'a EExpr<'a>, Row, Col), Branch(&'a EExpr<'a>, Row, Col),
Syntax(&'a SyntaxError<'a>, Row, Col), Syntax(&'a SyntaxError<'a>, Row, Col),
@ -399,6 +403,7 @@ pub enum When<'a> {
IndentPattern(Row, Col), IndentPattern(Row, Col),
IndentArrow(Row, Col), IndentArrow(Row, Col),
IndentBranch(Row, Col), IndentBranch(Row, Col),
IndentIfGuard(Row, Col),
PatternAlignment(u16, Row, Col), PatternAlignment(u16, Row, Col),
} }
@ -967,16 +972,17 @@ pub fn keyword<'a>(
} }
} }
pub fn keyword_e<'a, E>(keyword: &'static str, if_error: E) -> impl Parser<'a, (), E> pub fn keyword_e<'a, ToError, E>(keyword: &'static str, if_error: ToError) -> impl Parser<'a, (), E>
where where
E: 'a + Clone, ToError: Fn(Row, Col) -> E,
E: 'a,
{ {
move |arena, state: State<'a>| { move |arena, state: State<'a>| {
let initial_state = state.clone(); let initial_state = state.clone();
// first parse the keyword characters // first parse the keyword characters
let (_, _, after_keyword_state) = ascii_string(keyword) let (_, _, after_keyword_state) = ascii_string(keyword)
.parse(arena, state) .parse(arena, state)
.map_err(|(_, _, state)| (NoProgress, if_error.clone(), state))?; .map_err(|(_, _, state)| (NoProgress, if_error(state.line, state.column), state))?;
// then we must have at least one space character // then we must have at least one space character
// TODO this is potentially wasteful if there are a lot of spaces // TODO this is potentially wasteful if there are a lot of spaces
@ -990,7 +996,11 @@ where
// this is not a keyword, maybe it's `whence` or `iffy` // this is not a keyword, maybe it's `whence` or `iffy`
// anyway, make no progress and return the initial state // anyway, make no progress and return the initial state
// so we can try something else // so we can try something else
Err((NoProgress, if_error.clone(), initial_state)) Err((
NoProgress,
if_error(initial_state.line, initial_state.column),
initial_state,
))
} }
} }
} }

View file

@ -62,7 +62,9 @@ pub fn loc_pattern<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<'a>>,
) )
} }
fn loc_pattern_help<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<'a>>, EPattern<'a>> { pub fn loc_pattern_help<'a>(
min_indent: u16,
) -> impl Parser<'a, Located<Pattern<'a>>, EPattern<'a>> {
one_of!( one_of!(
specialize(EPattern::PInParens, loc_pattern_in_parens_help(min_indent)), specialize(EPattern::PInParens, loc_pattern_in_parens_help(min_indent)),
loc!(underscore_pattern_help()), loc!(underscore_pattern_help()),

View file

@ -66,7 +66,7 @@ fn term<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, Typ
and!( and!(
skip_second!( skip_second!(
backtrackable(space0_e(min_indent, Type::TSpace, Type::TIndentEnd)), backtrackable(space0_e(min_indent, Type::TSpace, Type::TIndentEnd)),
crate::parser::keyword_e(keyword::AS, Type::TEnd(0, 0)) crate::parser::keyword_e(keyword::AS, Type::TEnd)
), ),
space0_before_e( space0_before_e(
term(min_indent), term(min_indent),

View file

@ -145,6 +145,59 @@ fn to_syntax_report<'a>(
} }
Type(typ) => to_type_report(alloc, filename, &typ, 0, 0), Type(typ) => to_type_report(alloc, filename, &typ, 0, 0),
Pattern(pat) => to_pattern_report(alloc, filename, &pat, 0, 0), Pattern(pat) => to_pattern_report(alloc, filename, &pat, 0, 0),
Expr(expr) => to_expr_report(alloc, filename, &expr, 0, 0),
_ => todo!("unhandled parse error: {:?}", parse_problem),
}
}
fn to_expr_report<'a>(
alloc: &'a RocDocAllocator<'a>,
filename: PathBuf,
parse_problem: &roc_parse::parser::EExpr<'a>,
_start_row: Row,
_start_col: Col,
) -> Report<'a> {
use roc_parse::parser::EExpr;
match parse_problem {
EExpr::When(when, row, col) => to_when_report(alloc, filename, &when, *row, *col),
_ => todo!("unhandled parse error: {:?}", parse_problem),
}
}
fn to_when_report<'a>(
alloc: &'a RocDocAllocator<'a>,
filename: PathBuf,
parse_problem: &roc_parse::parser::When<'a>,
start_row: Row,
start_col: Col,
) -> Report<'a> {
use roc_parse::parser::When;
match *parse_problem {
When::IfGuard(nested, row, col) => match what_is_next(alloc.src_lines, row, col) {
Next::Token("->") => {
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
let region = Region::from_row_col(row, col);
let doc = alloc.stack(vec![
alloc.reflow(
r"I just started parsing an if guard, but there is no guard condition:",
),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("Try adding an expression before the arrow!")
]),
]);
Report {
filename,
doc,
title: "IF GUARD NO CONDITION".to_string(),
}
}
_ => to_syntax_report(alloc, filename, nested, row, col),
},
_ => todo!("unhandled parse error: {:?}", parse_problem), _ => todo!("unhandled parse error: {:?}", parse_problem),
} }
} }
@ -1289,6 +1342,7 @@ enum Next<'a> {
Keyword(&'a str), Keyword(&'a str),
// Operator(&'a str), // Operator(&'a str),
Close(&'a str, char), Close(&'a str, char),
Token(&'a str),
Other(Option<char>), Other(Option<char>),
} }
@ -1299,18 +1353,20 @@ fn what_is_next<'a>(source_lines: &'a [&'a str], row: Row, col: Col) -> Next<'a>
None => Next::Other(None), None => Next::Other(None),
Some(line) => { Some(line) => {
let chars = &line[col_index..]; let chars = &line[col_index..];
let mut it = chars.chars();
match roc_parse::keyword::KEYWORDS match roc_parse::keyword::KEYWORDS
.iter() .iter()
.find(|keyword| starts_with_keyword(chars, keyword)) .find(|keyword| starts_with_keyword(chars, keyword))
{ {
Some(keyword) => Next::Keyword(keyword), Some(keyword) => Next::Keyword(keyword),
None => match chars.chars().next() { None => match it.next() {
None => Next::Other(None), None => Next::Other(None),
Some(c) => match c { Some(c) => match c {
')' => Next::Close("parenthesis", ')'), ')' => Next::Close("parenthesis", ')'),
']' => Next::Close("square bracket", ']'), ']' => Next::Close("square bracket", ']'),
'}' => Next::Close("curly brace", '}'), '}' => Next::Close("curly brace", '}'),
'-' if it.next() == Some('>') => Next::Token("->"),
// _ if is_symbol(c) => todo!("it's an operator"), // _ if is_symbol(c) => todo!("it's an operator"),
_ => Next::Other(Some(c)), _ => Next::Other(Some(c)),
}, },
@ -1331,6 +1387,10 @@ fn starts_with_keyword(rest_of_line: &str, keyword: &str) -> bool {
} }
} }
fn next_is_arrow(rest_of_line: &str) -> bool {
rest_of_line.starts_with("->")
}
fn next_line_starts_with_close_curly(source_lines: &[&str], row: Row) -> Option<(Row, Col)> { fn next_line_starts_with_close_curly(source_lines: &[&str], row: Row) -> Option<(Row, Col)> {
match source_lines.get(row as usize + 1) { match source_lines.get(row as usize + 1) {
None => None, None => None,

View file

@ -4790,12 +4790,15 @@ mod test_reporting {
), ),
indoc!( indoc!(
r#" r#"
PARSE PROBLEM IF GUARD NO CONDITION
Unexpected token : I just started parsing an if guard, but there is no guard condition:
1 when Just 4 is
2 Just if -> 2 Just if ->
^ ^
Try adding an expression before the arrow!
"# "#
), ),
) )