Merge pull request #1056 from rtfeldman/parse-operators

Parse operators
This commit is contained in:
Richard Feldman 2021-03-07 23:19:49 -05:00 committed by GitHub
commit c2525d2407
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 529 additions and 241 deletions

View file

@ -313,27 +313,68 @@ fn expr_in_parens_then_access<'a>(
} }
} }
fn loc_parse_expr_body_without_operators_help<'a>( fn in_parens_region_fix<'a>(min_indent: u16) -> impl Parser<'a, Located<Expr<'a>>, EExpr<'a>> {
// we get the region of the expression inside the parens, but current tests want us to
// include the parentheses in the region
map!(
loc!(loc_expr_in_parens_etc_help(min_indent)),
|loc_loc_expr: Located<Located<Expr<'a>>>| {
let value = loc_loc_expr.value.value;
let region = loc_loc_expr.region;
Located::at(region, value)
}
)
}
fn parse_loc_term<'a>(
min_indent: u16, min_indent: u16,
arena: &'a Bump, arena: &'a Bump,
state: State<'a>, state: State<'a>,
) -> ParseResult<'a, Located<Expr<'a>>, EExpr<'a>> { ) -> ParseResult<'a, Located<Expr<'a>>, EExpr<'a>> {
one_of!( one_of!(
loc_expr_in_parens_etc_help(min_indent), in_parens_region_fix(min_indent),
loc!(specialize(EExpr::Str, string_literal_help())), loc!(specialize(EExpr::Str, string_literal_help())),
loc!(specialize(EExpr::Number, number_literal_help())), loc!(specialize(EExpr::Number, number_literal_help())),
loc!(specialize(EExpr::Lambda, closure_help(min_indent))),
loc!(record_literal_help(min_indent)), loc!(record_literal_help(min_indent)),
loc!(specialize(EExpr::List, list_literal_help(min_indent))), loc!(specialize(EExpr::List, list_literal_help(min_indent))),
loc!(unary_op_help(min_indent)), loc!(ident_etc_help(min_indent))
loc!(specialize(EExpr::When, when::expr_help(min_indent))),
loc!(specialize(EExpr::If, if_expr_help(min_indent))),
loc!(ident_etc_help(min_indent)),
fail_expr_start_e()
) )
.parse(arena, state) .parse(arena, state)
} }
fn loc_possibly_negative_or_negated_term<'a>(
min_indent: u16,
) -> impl Parser<'a, Located<Expr<'a>>, EExpr<'a>> {
one_of![
loc!(map_with_arena!(
// slight complication; a unary minus must be part of the number literal for overflow
// reasons
and!(loc!(unary_negate()), |a, s| parse_loc_term(
min_indent, a, s
)),
|arena: &'a Bump, (loc_op, loc_expr): (Located<_>, _)| {
Expr::UnaryOp(
arena.alloc(loc_expr),
Located::at(loc_op.region, UnaryOp::Negate),
)
}
)),
loc!(map_with_arena!(
and!(loc!(word1(b'!', EExpr::Start)), |a, s| parse_loc_term(
min_indent, a, s
)),
|arena: &'a Bump, (loc_op, loc_expr): (Located<_>, _)| {
Expr::UnaryOp(
arena.alloc(loc_expr),
Located::at(loc_op.region, UnaryOp::Not),
)
}
)),
|arena, state| parse_loc_term(min_indent, arena, state)
]
}
fn fail_expr_start_e<'a, T>() -> impl Parser<'a, T, EExpr<'a>> fn fail_expr_start_e<'a, T>() -> impl Parser<'a, T, EExpr<'a>>
where where
T: 'a, T: 'a,
@ -436,22 +477,23 @@ fn parse_expr_help<'a>(
arena: &'a Bump, arena: &'a Bump,
state: State<'a>, state: State<'a>,
) -> ParseResult<'a, Expr<'a>, EExpr<'a>> { ) -> ParseResult<'a, Expr<'a>, EExpr<'a>> {
let expr_parser = crate::parser::map_with_arena( let (_, loc_expr1, state) = one_of![
and!( loc!(specialize(EExpr::If, if_expr_help(min_indent))),
// First parse the body without operators, then try to parse possible operators after. loc!(specialize(EExpr::When, when::expr_help(min_indent))),
move |arena, state| loc_parse_expr_body_without_operators_help( loc!(specialize(EExpr::Lambda, closure_help(min_indent))),
min_indent, arena, state loc_possibly_negative_or_negated_term(min_indent),
), // |arena, state| loc_term(min_indent, arena, state),
// Parse the operator, with optional spaces before it. fail_expr_start_e()
// ]
// Since spaces can only wrap an Expr, not an BinOp, we have to first .parse(arena, state)?;
// parse the spaces and then attach them retroactively to the expression
// preceding the operator (the one we parsed before considering operators). let initial = state.clone();
optional(and!(
and!( match space0_e(min_indent, EExpr::Space, EExpr::IndentEnd).parse(arena, state) {
space0_e(min_indent, EExpr::Space, EExpr::IndentEnd), Err((_, _, state)) => Ok((MadeProgress, loc_expr1.value, state)),
loc!(binop_help()) Ok((_, spaces_before_op, state)) => {
), let parser = and!(
loc!(operator()),
// The spaces *after* the operator can be attached directly to // The spaces *after* the operator can be attached directly to
// the expression following the operator. // the expression following the operator.
space0_before_e( space0_before_e(
@ -460,27 +502,27 @@ fn parse_expr_help<'a>(
EExpr::Space, EExpr::Space,
EExpr::IndentEnd, EExpr::IndentEnd,
) )
)) );
),
|arena, (loc_expr1, opt_operator)| match opt_operator {
Some(((spaces_before_op, loc_op), loc_expr2)) => {
let loc_expr1 = if spaces_before_op.is_empty() {
loc_expr1
} else {
// Attach the spaces retroactively to the expression preceding the operator.
arena
.alloc(loc_expr1.value)
.with_spaces_after(spaces_before_op, loc_expr1.region)
};
let tuple = arena.alloc((loc_expr1, loc_op, loc_expr2));
Expr::BinOp(tuple) match parser.parse(arena, state) {
Ok((_, (loc_op, loc_expr2), state)) => {
let loc_expr1 = if spaces_before_op.is_empty() {
loc_expr1
} else {
// Attach the spaces retroactively to the expression preceding the operator.
arena
.alloc(loc_expr1.value)
.with_spaces_after(spaces_before_op, loc_expr1.region)
};
let tuple = arena.alloc((loc_expr1, loc_op, loc_expr2));
Ok((MadeProgress, Expr::BinOp(tuple), state))
}
Err((NoProgress, _, _)) => Ok((MadeProgress, loc_expr1.value, initial)),
Err((MadeProgress, fail, state)) => Err((MadeProgress, fail, state)),
} }
None => loc_expr1.value, }
}, }
);
expr_parser.parse(arena, state)
} }
/// If the given Expr would parse the same way as a valid Pattern, convert it. /// If the given Expr would parse the same way as a valid Pattern, convert it.
@ -1681,9 +1723,9 @@ fn loc_function_args_help<'a>(
EExpr::IndentStart, EExpr::IndentStart,
EExpr::Start EExpr::Start
)), )),
one_of!(unary_negate_function_arg_help(min_indent), |a, s| { one_of![unary_negate_function_arg_help(min_indent), |a, s| {
loc_parse_function_arg_help(min_indent, a, s) loc_parse_function_arg_help(min_indent, a, s)
}) }]
), ),
|(spaces, loc_expr): (&'a [_], Located<Expr<'a>>)| { |(spaces, loc_expr): (&'a [_], Located<Expr<'a>>)| {
if spaces.is_empty() { if spaces.is_empty() {
@ -2033,49 +2075,6 @@ fn ident_to_expr<'a>(arena: &'a Bump, src: Ident<'a>) -> Expr<'a> {
} }
} }
fn binop_help<'a>() -> impl Parser<'a, BinOp, EExpr<'a>> {
macro_rules! binop {
($word1:expr, $op:expr) => {
map!(
word1($word1, |row, col| EExpr::BinOp($op, row, col)),
|_| $op
)
};
($word1:expr, $word2:expr, $op:expr) => {
map!(
word2($word1, $word2, |row, col| EExpr::BinOp($op, row, col)),
|_| $op
)
};
}
one_of!(
// Sorted from highest to lowest predicted usage in practice,
// so that successful matches short-circuit as early as possible.
// The only exception to this is that operators which begin
// with other valid operators (e.g. "<=" begins with "<") must
// come before the shorter ones; otherwise, they will never
// be reached because the shorter one will pass and consume!
binop!(b'|', b'>', BinOp::Pizza),
binop!(b'=', b'=', BinOp::Equals),
binop!(b'!', b'=', BinOp::NotEquals),
binop!(b'&', b'&', BinOp::And),
binop!(b'|', b'|', BinOp::Or),
binop!(b'+', BinOp::Plus),
binop!(b'*', BinOp::Star),
binop!(b'-', BinOp::Minus),
binop!(b'/', b'/', BinOp::DoubleSlash),
binop!(b'/', BinOp::Slash),
binop!(b'<', b'=', BinOp::LessThanOrEq),
binop!(b'<', BinOp::LessThan),
binop!(b'>', b'=', BinOp::GreaterThanOrEq),
binop!(b'>', BinOp::GreaterThan),
binop!(b'^', BinOp::Caret),
binop!(b'%', b'%', BinOp::DoublePercent),
binop!(b'%', BinOp::Percent)
)
}
fn list_literal_help<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, List<'a>> { fn list_literal_help<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, List<'a>> {
move |arena, state| { move |arena, state| {
let (_, (parsed_elems, final_comments), state) = collection_trailing_sep_e!( let (_, (parsed_elems, final_comments), state) = collection_trailing_sep_e!(
@ -2365,3 +2364,97 @@ fn number_literal_help<'a>() -> impl Parser<'a, Expr<'a>, Number> {
} }
}) })
} }
const BINOP_CHAR_SET: &[u8] = b"+-/*=.<>:&|^?%!";
use crate::parser::{Col, Row};
fn operator<'a>() -> impl Parser<'a, BinOp, EExpr<'a>> {
|_, state| operator_help(EExpr::Start, EExpr::BadOperator, state)
}
#[inline(always)]
fn operator_help<'a, F, G, E>(
to_expectation: F,
to_error: G,
mut state: State<'a>,
) -> ParseResult<'a, BinOp, E>
where
F: Fn(Row, Col) -> E,
G: Fn(&'a [u8], Row, Col) -> E,
E: 'a,
{
let chomped = chomp_ops(state.bytes);
macro_rules! good {
($op:expr, $width:expr) => {{
state.column += $width;
state.bytes = &state.bytes[$width..];
Ok((MadeProgress, $op, state))
}};
}
macro_rules! bad_made_progress {
($op:expr) => {{
Err((MadeProgress, to_error($op, state.line, state.column), state))
}};
}
match chomped {
0 => Err((NoProgress, to_expectation(state.line, state.column), state)),
1 => {
let op = state.bytes[0];
match op {
b'+' => good!(BinOp::Plus, 1),
b'-' => good!(BinOp::Minus, 1),
b'*' => good!(BinOp::Star, 1),
b'/' => good!(BinOp::Slash, 1),
b'%' => good!(BinOp::Percent, 1),
b'^' => good!(BinOp::Caret, 1),
b'>' => good!(BinOp::GreaterThan, 1),
b'<' => good!(BinOp::LessThan, 1),
b'.' => {
// a `.` makes no progress, so it does not interfere with `.foo` access(or)
Err((NoProgress, to_error(b".", state.line, state.column), state))
}
_ => bad_made_progress!(&state.bytes[0..1]),
}
}
2 => {
let op0 = state.bytes[0];
let op1 = state.bytes[1];
match (op0, op1) {
(b'|', b'>') => good!(BinOp::Pizza, 2),
(b'=', b'=') => good!(BinOp::Equals, 2),
(b'!', b'=') => good!(BinOp::NotEquals, 2),
(b'>', b'=') => good!(BinOp::GreaterThanOrEq, 2),
(b'<', b'=') => good!(BinOp::LessThanOrEq, 2),
(b'&', b'&') => good!(BinOp::And, 2),
(b'|', b'|') => good!(BinOp::Or, 2),
(b'/', b'/') => good!(BinOp::DoubleSlash, 2),
(b'%', b'%') => good!(BinOp::DoublePercent, 2),
(b'-', b'>') => {
// makes no progress, so it does not interfere with `_ if isGood -> ...`
Err((NoProgress, to_error(b"->", state.line, state.column), state))
}
_ => bad_made_progress!(&state.bytes[0..2]),
}
}
_ => bad_made_progress!(&state.bytes[0..chomped]),
}
}
fn chomp_ops(bytes: &[u8]) -> usize {
let mut chomped = 0;
for c in bytes.iter() {
if !BINOP_CHAR_SET.contains(c) {
return chomped;
}
chomped += 1;
}
chomped
}

View file

@ -391,7 +391,7 @@ pub enum EExpr<'a> {
Access(Row, Col), Access(Row, Col),
UnaryNot(Row, Col), UnaryNot(Row, Col),
UnaryNegate(Row, Col), UnaryNegate(Row, Col),
BinOp(roc_module::operator::BinOp, Row, Col), BadOperator(&'a [u8], Row, Col),
Def(&'a SyntaxError<'a>, Row, Col), Def(&'a SyntaxError<'a>, Row, Col),
Type(Type<'a>, Row, Col), Type(Type<'a>, Row, Col),

View file

@ -491,6 +491,29 @@ mod test_parse {
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
#[test]
fn var_minus_two() {
let arena = Bump::new();
let tuple = arena.alloc((
Located::new(
0,
0,
0,
1,
Var {
module_name: "",
ident: "x",
},
),
Located::new(0, 0, 1, 2, Minus),
Located::new(0, 0, 2, 3, Num("2")),
));
let expected = BinOp(tuple);
let actual = parse_expr_with(&arena, "x-2");
assert_eq!(Ok(expected), actual);
}
#[test] #[test]
fn add_with_spaces() { fn add_with_spaces() {
let arena = Bump::new(); let arena = Bump::new();

View file

@ -221,6 +221,82 @@ fn to_expr_report<'a>(
} }
} }
EExpr::BadOperator(op, row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, *row, *col);
let region = Region::from_rows_cols(*row, *col, *row, *col + op.len() as u16);
let suggestion = match *op {
b"|" => vec![
alloc.reflow("Maybe you want "),
alloc.parser_suggestion("||"),
alloc.reflow(" or "),
alloc.parser_suggestion("|>"),
alloc.reflow(" instead?"),
],
b"++" => vec![
alloc.reflow("To concatenate two lists or strings, try using "),
alloc.parser_suggestion("List.concat"),
alloc.reflow(" or "),
alloc.parser_suggestion("Str.concat"),
alloc.reflow(" instead."),
],
b":" => vec![alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("The has-type operator "),
alloc.parser_suggestion(":"),
alloc.reflow(" can only occur in a definition's type signature, like"),
]),
alloc
.vcat(vec![
alloc.text("increment : I64 -> I64"),
alloc.text("increment = \\x -> x + 1"),
])
.indent(4),
])],
b"->" => vec![alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("The arrow "),
alloc.parser_suggestion("->"),
alloc.reflow(" is only used to define cases in a "),
alloc.keyword("when"),
alloc.reflow("."),
]),
alloc
.vcat(vec![
alloc.text("when color is"),
alloc.text("Red -> \"stop!\"").indent(4),
alloc.text("Green -> \"go!\"").indent(4),
])
.indent(4),
])],
b"!" => vec![
alloc.reflow("The boolean negation operator "),
alloc.parser_suggestion("!"),
alloc.reflow(" must occur immediately before an expression, like "),
alloc.parser_suggestion("!(List.isEmpty primes)"),
alloc.reflow(". There cannot be a space between the "),
alloc.parser_suggestion("!"),
alloc.reflow(" and the expression after it."),
],
_ => vec![
alloc.reflow("I have no specific suggestion for this operator, "),
alloc.reflow("see TODO for the full list of operators in Roc."),
],
};
let doc = alloc.stack(vec![
alloc.reflow(r"This looks like an operator, but it's not one I recognize!"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(suggestion),
]);
Report {
filename,
doc,
title: "UNKNOWN OPERATOR".to_string(),
}
}
EExpr::Ident(_row, _col) => unreachable!("another branch would be taken"), EExpr::Ident(_row, _col) => unreachable!("another branch would be taken"),
EExpr::QualifiedTag(row, col) => { EExpr::QualifiedTag(row, col) => {

View file

@ -812,20 +812,20 @@ mod test_reporting {
indoc!( indoc!(
r#" r#"
TYPE MISMATCH TYPE MISMATCH
The 3rd branch of this `if` does not match all the previous branches: The 3rd branch of this `if` does not match all the previous branches:
1 if True then 2 else if False then 2 else "foo" 1 if True then 2 else if False then 2 else "foo"
^^^^^ ^^^^^
The 3rd branch is a string of type: The 3rd branch is a string of type:
Str Str
But all the previous branches have type: But all the previous branches have type:
Num a Num a
I need all branches in an `if` to have the same type! I need all branches in an `if` to have the same type!
"# "#
), ),
@ -3159,12 +3159,12 @@ mod test_reporting {
indoc!( indoc!(
r#" r#"
ARGUMENTS BEFORE EQUALS ARGUMENTS BEFORE EQUALS
I am in the middle of parsing a definition, but I got stuck here: I am in the middle of parsing a definition, but I got stuck here:
1 f x y = x 1 f x y = x
^^^ ^^^
Looks like you are trying to define a function. In roc, functions are Looks like you are trying to define a function. In roc, functions are
always written as a lambda, like increment = \n -> n + 1. always written as a lambda, like increment = \n -> n + 1.
"# "#
@ -4020,12 +4020,12 @@ mod test_reporting {
indoc!( indoc!(
r#" r#"
SYNTAX PROBLEM SYNTAX PROBLEM
I am trying to parse a qualified name here: I am trying to parse a qualified name here:
1 Foo.Bar 1 Foo.Bar
^ ^
This looks like a qualified tag name to me, but tags cannot be This looks like a qualified tag name to me, but tags cannot be
qualified! Maybe you wanted a qualified name, something like qualified! Maybe you wanted a qualified name, something like
Json.Decode.string? Json.Decode.string?
@ -4045,12 +4045,12 @@ mod test_reporting {
indoc!( indoc!(
r#" r#"
SYNTAX PROBLEM SYNTAX PROBLEM
I am trying to parse a qualified name here: I am trying to parse a qualified name here:
1 Foo.Bar. 1 Foo.Bar.
^ ^
I was expecting to see an identifier next, like height. A complete I was expecting to see an identifier next, like height. A complete
qualified name looks something like Json.Decode.string. qualified name looks something like Json.Decode.string.
"# "#
@ -4069,12 +4069,12 @@ mod test_reporting {
indoc!( indoc!(
r#" r#"
SYNTAX PROBLEM SYNTAX PROBLEM
I trying to parse a record field accessor here: I trying to parse a record field accessor here:
1 foo.bar. 1 foo.bar.
^ ^
Something like .name or .height that accesses a value from a record. Something like .name or .height that accesses a value from a record.
"# "#
), ),
@ -4092,12 +4092,12 @@ mod test_reporting {
indoc!( indoc!(
r#" r#"
SYNTAX PROBLEM SYNTAX PROBLEM
I am trying to parse a qualified name here: I am trying to parse a qualified name here:
1 @Foo.Bar 1 @Foo.Bar
^ ^
This looks like a qualified tag name to me, but tags cannot be This looks like a qualified tag name to me, but tags cannot be
qualified! Maybe you wanted a qualified name, something like qualified! Maybe you wanted a qualified name, something like
Json.Decode.string? Json.Decode.string?
@ -4163,32 +4163,6 @@ mod test_reporting {
) )
} }
#[test]
fn invalid_operator() {
// NOTE: VERY BAD ERROR MESSAGE
report_problem_as(
indoc!(
r#"
main =
5 ** 3
"#
),
indoc!(
r#"
MISSING EXPRESSION
I am partway through parsing a definition, but I got stuck here:
1 main =
2 5 ** 3
^
I was expecting to see an expression like 42 or "hello".
"#
),
)
}
#[test] #[test]
fn tag_union_open() { fn tag_union_open() {
report_problem_as( report_problem_as(
@ -4718,12 +4692,12 @@ mod test_reporting {
indoc!( indoc!(
r#" r#"
UNFINISHED TYPE UNFINISHED TYPE
I am partway through parsing a type, but I got stuck here: I am partway through parsing a type, but I got stuck here:
1 f : I64, I64 1 f : I64, I64
^ ^
Note: I may be confused by indentation Note: I may be confused by indentation
"# "#
), ),
@ -4905,13 +4879,13 @@ mod test_reporting {
indoc!( indoc!(
r#" r#"
MISSING EXPRESSION MISSING EXPRESSION
I am partway through parsing a definition, but I got stuck here: I am partway through parsing a definition, but I got stuck here:
1 when Just 4 is 1 when Just 4 is
2 Just 4 | -> 2 Just 4 | ->
^ ^
I was expecting to see an expression like 42 or "hello". I was expecting to see an expression like 42 or "hello".
"# "#
), ),
@ -4966,31 +4940,31 @@ mod test_reporting {
r#" r#"
when 5 is when 5 is
1 -> 2 1 -> 2
_ _
"# "#
), ),
indoc!( indoc!(
r#" r#"
MISSING ARROW MISSING ARROW
I am partway through parsing a `when` expression, but got stuck here: I am partway through parsing a `when` expression, but got stuck here:
2 1 -> 2 2 1 -> 2
3 _ 3 _
^ ^
I was expecting to see an arrow next. I was expecting to see an arrow next.
Note: Sometimes I get confused by indentation, so try to make your `when` Note: Sometimes I get confused by indentation, so try to make your `when`
look something like this: look something like this:
when List.first plants is when List.first plants is
Ok n -> Ok n ->
n n
Err _ -> Err _ ->
200 200
Notice the indentation. All patterns are aligned, and each branch is Notice the indentation. All patterns are aligned, and each branch is
indented a bit more than the corresponding pattern. That is important! indented a bit more than the corresponding pattern. That is important!
"# "#
@ -5009,13 +4983,13 @@ mod test_reporting {
indoc!( indoc!(
r#" r#"
UNFINISHED ARGUMENT LIST UNFINISHED ARGUMENT LIST
I am in the middle of parsing a function argument list, but I got I am in the middle of parsing a function argument list, but I got
stuck at this comma: stuck at this comma:
1 \a,,b -> 1 1 \a,,b -> 1
^ ^
I was expecting an argument pattern before this, so try adding an I was expecting an argument pattern before this, so try adding an
argument before the comma and see if that helps? argument before the comma and see if that helps?
"# "#
@ -5034,13 +5008,13 @@ mod test_reporting {
indoc!( indoc!(
r#" r#"
UNFINISHED ARGUMENT LIST UNFINISHED ARGUMENT LIST
I am in the middle of parsing a function argument list, but I got I am in the middle of parsing a function argument list, but I got
stuck at this comma: stuck at this comma:
1 \,b -> 1 1 \,b -> 1
^ ^
I was expecting an argument pattern before this, so try adding an I was expecting an argument pattern before this, so try adding an
argument before the comma and see if that helps? argument before the comma and see if that helps?
"# "#
@ -5062,23 +5036,23 @@ mod test_reporting {
indoc!( indoc!(
r#" r#"
UNFINISHED WHEN UNFINISHED WHEN
I was partway through parsing a `when` expression, but I got stuck here: I was partway through parsing a `when` expression, but I got stuck here:
3 _ -> 2 3 _ -> 2
^ ^
I suspect this is a pattern that is not indented enough? (by 2 spaces) I suspect this is a pattern that is not indented enough? (by 2 spaces)
Note: Here is an example of a valid `when` expression for reference. Note: Here is an example of a valid `when` expression for reference.
when List.first plants is when List.first plants is
Ok n -> Ok n ->
n n
Err _ -> Err _ ->
200 200
Notice the indentation. All patterns are aligned, and each branch is Notice the indentation. All patterns are aligned, and each branch is
indented a bit more than the corresponding pattern. That is important! indented a bit more than the corresponding pattern. That is important!
"# "#
@ -5093,7 +5067,7 @@ mod test_reporting {
indoc!( indoc!(
r#" r#"
x = x =
if 5 == 5 if 5 == 5
then 2 else 3 then 2 else 3
x x
@ -5102,12 +5076,12 @@ mod test_reporting {
indoc!( indoc!(
r#" r#"
UNFINISHED IF UNFINISHED IF
I was partway through parsing an `if` expression, but I got stuck here: I was partway through parsing an `if` expression, but I got stuck here:
2 if 5 == 5 2 if 5 == 5
^ ^
I was expecting to see the `then` keyword next. I was expecting to see the `then` keyword next.
"# "#
), ),
@ -5126,12 +5100,12 @@ mod test_reporting {
indoc!( indoc!(
r#" r#"
UNFINISHED IF UNFINISHED IF
I was partway through parsing an `if` expression, but I got stuck here: I was partway through parsing an `if` expression, but I got stuck here:
1 if 5 == 5 then 2 1 if 5 == 5 then 2
^ ^
I was expecting to see the `else` keyword next. I was expecting to see the `else` keyword next.
"# "#
), ),
@ -5149,12 +5123,12 @@ mod test_reporting {
indoc!( indoc!(
r#" r#"
UNFINISHED LIST UNFINISHED LIST
I am partway through started parsing a list, but I got stuck here: I am partway through started parsing a list, but I got stuck here:
1 [ 1, 2, , 3 ] 1 [ 1, 2, , 3 ]
^ ^
I was expecting to see a list entry before this comma, so try adding a I was expecting to see a list entry before this comma, so try adding a
list entry and see if that helps? list entry and see if that helps?
"# "#
@ -5167,21 +5141,21 @@ mod test_reporting {
report_problem_as( report_problem_as(
indoc!( indoc!(
r#" r#"
[ 1, 2, [ 1, 2,
"# "#
), ),
indoc!( indoc!(
r#" r#"
UNFINISHED LIST UNFINISHED LIST
I am partway through started parsing a list, but I got stuck here: I am partway through started parsing a list, but I got stuck here:
1 [ 1, 2, 1 [ 1, 2,
^ ^
I was expecting to see a closing square bracket before this, so try I was expecting to see a closing square bracket before this, so try
adding a ] and see if that helps? adding a ] and see if that helps?
Note: When I get stuck like this, it usually means that there is a Note: When I get stuck like this, it usually means that there is a
missing parenthesis or bracket somewhere earlier. It could also be a missing parenthesis or bracket somewhere earlier. It could also be a
stray keyword or operator. stray keyword or operator.
@ -5195,7 +5169,7 @@ mod test_reporting {
report_problem_as( report_problem_as(
indoc!( indoc!(
r#" r#"
x = [ 1, 2, x = [ 1, 2,
] ]
x x
@ -5204,16 +5178,16 @@ mod test_reporting {
indoc!( indoc!(
r#" r#"
UNFINISHED LIST UNFINISHED LIST
I cannot find the end of this list: I cannot find the end of this list:
1 x = [ 1, 2, 1 x = [ 1, 2,
^ ^
You could change it to something like [ 1, 2, 3 ] or even just []. You could change it to something like [ 1, 2, 3 ] or even just [].
Anything where there is an open and a close square bracket, and where Anything where there is an open and a close square bracket, and where
the elements of the list are separated by commas. the elements of the list are separated by commas.
Note: I may be confused by indentation Note: I may be confused by indentation
"# "#
), ),
@ -5231,15 +5205,15 @@ mod test_reporting {
indoc!( indoc!(
r#" r#"
SYNTAX PROBLEM SYNTAX PROBLEM
This float literal contains an invalid digit: This float literal contains an invalid digit:
1 1.1.1 1 1.1.1
^^^^^ ^^^^^
Floating point literals can only contain the digits 0-9, or use Floating point literals can only contain the digits 0-9, or use
scientific notation 10e4 scientific notation 10e4
Tip: Learn more about number literals at TODO Tip: Learn more about number literals at TODO
"# "#
), ),
@ -5253,15 +5227,15 @@ mod test_reporting {
indoc!( indoc!(
r#" r#"
WEIRD CODE POINT WEIRD CODE POINT
I am partway through parsing a unicode code point, but I got stuck I am partway through parsing a unicode code point, but I got stuck
here: here:
1 "abc\u(zzzz)def" 1 "abc\u(zzzz)def"
^ ^
I was expecting a hexadecimal number, like \u(1100) or \u(00FF). I was expecting a hexadecimal number, like \u(1100) or \u(00FF).
Learn more about working with unicode in roc at TODO Learn more about working with unicode in roc at TODO
"# "#
), ),
@ -5275,15 +5249,15 @@ mod test_reporting {
indoc!( indoc!(
r#" r#"
SYNTAX PROBLEM SYNTAX PROBLEM
This string interpolation is invalid: This string interpolation is invalid:
1 "abc\(32)def" 1 "abc\(32)def"
^^ ^^
I was expecting an identifier, like \u(message) or I was expecting an identifier, like \u(message) or
\u(LoremIpsum.text). \u(LoremIpsum.text).
Learn more about string interpolation at TODO Learn more about string interpolation at TODO
"# "#
), ),
@ -5297,12 +5271,12 @@ mod test_reporting {
indoc!( indoc!(
r#" r#"
SYNTAX PROBLEM SYNTAX PROBLEM
This unicode code point is invalid: This unicode code point is invalid:
1 "abc\u(110000)def" 1 "abc\u(110000)def"
^^^^^^ ^^^^^^
Learn more about working with unicode in roc at TODO Learn more about working with unicode in roc at TODO
"# "#
), ),
@ -5316,15 +5290,15 @@ mod test_reporting {
indoc!( indoc!(
r#" r#"
WEIRD ESCAPE WEIRD ESCAPE
I was partway through parsing a string literal, but I got stuck here: I was partway through parsing a string literal, but I got stuck here:
1 "abc\qdef" 1 "abc\qdef"
^^ ^^
This is not an escape sequence I recognize. After a backslash, I am This is not an escape sequence I recognize. After a backslash, I am
looking for one of these: looking for one of these:
- A newline: \n - A newline: \n
- A caret return: \r - A caret return: \r
- A tab: \t - A tab: \t
@ -5344,12 +5318,12 @@ mod test_reporting {
indoc!( indoc!(
r#" r#"
ENDLESS STRING ENDLESS STRING
I cannot find the end of this string: I cannot find the end of this string:
1 "there is no end 1 "there is no end
^ ^
You could change it to something like "to be or not to be" or even You could change it to something like "to be or not to be" or even
just "". just "".
"# "#
@ -5364,12 +5338,12 @@ mod test_reporting {
indoc!( indoc!(
r#" r#"
ENDLESS STRING ENDLESS STRING
I cannot find the end of this block string: I cannot find the end of this block string:
1 """there is no end 1 """there is no end
^ ^
You could change it to something like """to be or not to be""" or even You could change it to something like """to be or not to be""" or even
just """""". just """""".
"# "#
@ -5390,20 +5364,20 @@ mod test_reporting {
indoc!( indoc!(
r#" r#"
TYPE MISMATCH TYPE MISMATCH
This expression is used in an unexpected way: This expression is used in an unexpected way:
3 foo.if 3 foo.if
^^^^^^ ^^^^^^
This `foo` value is a: This `foo` value is a:
{} {}
But you are trying to use it as: But you are trying to use it as:
{ if : a }b { if : a }b
"# "#
), ),
@ -5421,9 +5395,9 @@ mod test_reporting {
indoc!( indoc!(
r#" r#"
SYNTAX PROBLEM SYNTAX PROBLEM
The Num module does not expose a if value: The Num module does not expose a if value:
1 Num.if 1 Num.if
^^^^^^ ^^^^^^
"# "#
@ -5442,12 +5416,12 @@ mod test_reporting {
indoc!( indoc!(
r#" r#"
SYNTAX PROBLEM SYNTAX PROBLEM
I trying to parse a record field accessor here: I trying to parse a record field accessor here:
1 Num.add . 23 1 Num.add . 23
^ ^
Something like .name or .height that accesses a value from a record. Something like .name or .height that accesses a value from a record.
"# "#
), ),
@ -5465,12 +5439,12 @@ mod test_reporting {
indoc!( indoc!(
r#" r#"
SYNTAX PROBLEM SYNTAX PROBLEM
I am trying to parse a private tag here: I am trying to parse a private tag here:
1 Num.add @foo 23 1 Num.add @foo 23
^ ^
But after the `@` symbol I found a lowercase letter. All tag names But after the `@` symbol I found a lowercase letter. All tag names
(global and private) must start with an uppercase letter, like @UUID (global and private) must start with an uppercase letter, like @UUID
or @Secrets. or @Secrets.
@ -5490,12 +5464,12 @@ mod test_reporting {
indoc!( indoc!(
r#" r#"
SYNTAX PROBLEM SYNTAX PROBLEM
I am very confused by this field access: I am very confused by this field access:
1 @UUID.bar 1 @UUID.bar
^^^^^^^^^ ^^^^^^^^^
It looks like a record field access on a private tag. It looks like a record field access on a private tag.
"# "#
), ),
@ -5513,12 +5487,12 @@ mod test_reporting {
indoc!( indoc!(
r#" r#"
SYNTAX PROBLEM SYNTAX PROBLEM
I am very confused by this field access I am very confused by this field access
1 .foo.bar 1 .foo.bar
^^^^^^^^ ^^^^^^^^
It looks like a field access on an accessor. I parse.client.name as It looks like a field access on an accessor. I parse.client.name as
(.client).name. Maybe use an anonymous function like (.client).name. Maybe use an anonymous function like
(\r -> r.client.name) instead? (\r -> r.client.name) instead?
@ -5538,12 +5512,12 @@ mod test_reporting {
indoc!( indoc!(
r#" r#"
SYNTAX PROBLEM SYNTAX PROBLEM
I trying to parse a record field access here: I trying to parse a record field access here:
1 foo.100 1 foo.100
^ ^
So I expect to see a lowercase letter next, like .name or .height. So I expect to see a lowercase letter next, like .name or .height.
"# "#
), ),
@ -5561,12 +5535,12 @@ mod test_reporting {
indoc!( indoc!(
r#" r#"
SYNTAX PROBLEM SYNTAX PROBLEM
I am trying to parse an identifier here: I am trying to parse an identifier here:
1 \the_answer -> 100 1 \the_answer -> 100
^ ^
Underscores are not allowed in identifiers. Use camelCase instead! Underscores are not allowed in identifiers. Use camelCase instead!
"# "#
), ),
@ -5606,4 +5580,126 @@ mod test_reporting {
), ),
) )
} }
#[test]
#[ignore]
fn argument_without_space() {
report_problem_as(
indoc!(
r#"
[ "foo", bar("") ]
"#
),
indoc!(
r#"
"#
),
)
}
#[test]
fn invalid_operator() {
report_problem_as(
indoc!(
r#"
main =
5 ** 3
"#
),
indoc!(
r#"
UNKNOWN OPERATOR
This looks like an operator, but it's not one I recognize!
1 main =
2 5 ** 3
^^
I have no specific suggestion for this operator, see TODO for the full
list of operators in Roc.
"#
),
)
}
#[test]
fn double_plus() {
report_problem_as(
indoc!(
r#"
main =
[] ++ []
"#
),
indoc!(
r#"
UNKNOWN OPERATOR
This looks like an operator, but it's not one I recognize!
1 main =
2 [] ++ []
^^
To concatenate two lists or strings, try using List.concat or
Str.concat instead.
"#
),
)
}
#[test]
fn inline_hastype() {
report_problem_as(
indoc!(
r#"
main =
5 : I64
"#
),
indoc!(
r#"
UNKNOWN OPERATOR
This looks like an operator, but it's not one I recognize!
1 main =
2 5 : I64
^
The has-type operator : can only occur in a definition's type
signature, like
increment : I64 -> I64
increment = \x -> x + 1
"#
),
)
}
#[test]
fn wild_case_arrow() {
// this is still bad, but changing the order and progress of other parsers should improve it
// down the line
report_problem_as(
indoc!(
r#"
main = 5 -> 3
"#
),
indoc!(
r#"
MISSING EXPRESSION
I am partway through parsing a definition, but I got stuck here:
1 main = 5 -> 3
^
I was expecting to see an expression like 42 or "hello".
"#
),
)
}
} }