mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-30 15:21:12 +00:00
if guard empty condition message
This commit is contained in:
parent
bcf87f5df6
commit
576b7974e8
6 changed files with 122 additions and 23 deletions
|
@ -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!(
|
one_of![
|
||||||
parser::keyword(keyword::IF, min_indent),
|
map!(
|
||||||
// TODO we should require space before the expression but not after
|
skip_first!(
|
||||||
space1_around(
|
parser::keyword_e(keyword::IF, When::IfToken),
|
||||||
loc!(move |arena, state| parse_expr(min_indent, arena, state)),
|
// TODO we should require space before the expression but not after
|
||||||
min_indent
|
space0_around_e(
|
||||||
)
|
loc!(specialize_ref(When::IfGuard, move |arena, state| {
|
||||||
))
|
parse_expr(min_indent, arena, state)
|
||||||
|
})),
|
||||||
|
min_indent,
|
||||||
|
When::Space,
|
||||||
|
When::IndentIfGuard,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Some
|
||||||
|
),
|
||||||
|
|_, s| Ok((NoProgress, None, s))
|
||||||
|
]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()),
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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!
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue