diff --git a/builtins/Bool.roc b/builtins/Bool.roc index da7b02eab0..c69d6ae2ed 100644 --- a/builtins/Bool.roc +++ b/builtins/Bool.roc @@ -28,5 +28,5 @@ eq : val, val -> Bool ## ## This is the same as the #=/= operator. notEq : val, val -> Bool -notEq = \left right -> +notEq = \left, right -> not (equal left right) diff --git a/builtins/Float.roc b/builtins/Float.roc index ba6e9b72d1..10289bac9d 100644 --- a/builtins/Float.roc +++ b/builtins/Float.roc @@ -109,7 +109,7 @@ round = \num -> ## >>> Float.pi ## >>> |> Float.div 2.0 #div : Float, Float -> Result Float DivByZero -div = \numerator denominator -> +div = \numerator, denominator -> case numerator when 0.0 -> 0.0 # TODO return Result! _ -> denominator diff --git a/src/fmt/expr.rs b/src/fmt/expr.rs index 7ae73ab879..82886ebef9 100644 --- a/src/fmt/expr.rs +++ b/src/fmt/expr.rs @@ -102,12 +102,24 @@ pub fn fmt_expr<'a>( indent }; - for loc_pattern in loc_patterns.iter() { - fmt_pattern(buf, &loc_pattern.value, indent, true); + let mut any_args_printed = false; - if !arguments_are_multiline { - buf.push(' '); + for loc_pattern in loc_patterns.iter() { + if any_args_printed { + buf.push(','); + + if !arguments_are_multiline { + buf.push(' '); + } + } else { + any_args_printed = true; } + + fmt_pattern(buf, &loc_pattern.value, indent, true); + } + + if !arguments_are_multiline { + buf.push(' '); } buf.push_str("->"); diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 728d421804..02a9f95b7e 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -13,14 +13,13 @@ pub mod type_annotation; use crate::operator::{BinOp, CalledVia, UnaryOp}; use crate::parse::ast::{AssignedField, Attempting, Def, Expr, MaybeQualified, Pattern, Spaceable}; use crate::parse::blankspace::{ - space0, space0_after, space0_around, space0_before, space1, space1_after, space1_around, - space1_before, + space0, space0_after, space0_around, space0_before, space1, space1_around, space1_before, }; use crate::parse::ident::{global_tag_or_ident, ident, lowercase_ident, Ident}; use crate::parse::number_literal::number_literal; use crate::parse::parser::{ - allocated, char, not, not_followed_by, optional, string, then, unexpected, unexpected_eof, - Either, Fail, FailReason, ParseResult, Parser, State, + allocated, char, not, not_followed_by, optional, sep_by1, string, then, unexpected, + unexpected_eof, Either, Fail, FailReason, ParseResult, Parser, State, }; use crate::region::{Located, Region}; use bumpalo::collections::Vec; @@ -409,7 +408,6 @@ fn equals_for_def<'a>() -> impl Parser<'a, ()> { /// A definition, consisting of one of these: /// -/// * A custom type definition using (`:=`) /// * A type alias using `:` /// * A pattern followed by '=' and then an expression /// * A type annotation @@ -653,13 +651,11 @@ fn closure<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> { // Parse the params attempt!( Attempting::ClosureParams, - // Note: because this is parse1_after, you *must* have - // a space before the "->" in a closure declaration. - // - // We could make this significantly more complicated in - // order to support e.g. (\x-> 5) with no space before - // the "->" but that does not seem worthwhile. - one_or_more!(space1_after(loc_closure_param(min_indent), min_indent)) + // Params are comma-separated + sep_by1( + char(','), + space0_around(loc_closure_param(min_indent), min_indent) + ) ), skip_first!( // Parse the -> which separates params from body diff --git a/src/parse/parser.rs b/src/parse/parser.rs index 46221955fc..f4bfc7f3cb 100644 --- a/src/parse/parser.rs +++ b/src/parse/parser.rs @@ -462,6 +462,60 @@ where } } +/// Parse one or more values separated by a delimiter (e.g. a comma) whose +/// values are discarded +pub fn sep_by1<'a, P, D, Val>(delimiter: D, parser: P) -> impl Parser<'a, Vec<'a, Val>> +where + D: Parser<'a, ()>, + P: Parser<'a, Val>, +{ + move |arena, state: State<'a>| { + let original_attempting = state.attempting; + + match parser.parse(arena, state) { + Ok((first_output, next_state)) => { + let mut state = next_state; + let mut buf = Vec::with_capacity_in(1, arena); + + buf.push(first_output); + + loop { + match delimiter.parse(arena, state) { + Ok(((), next_state)) => { + // If the delimiter passed, check the element parser. + match parser.parse(arena, next_state) { + Ok((next_output, next_state)) => { + state = next_state; + buf.push(next_output); + } + Err((fail, state)) => { + // If the delimiter parsed, but the following + // element did not, that's a fatal error. + return Err(( + Fail { + attempting: original_attempting, + ..fail + }, + state, + )); + } + } + } + Err((_, old_state)) => return Ok((buf, old_state)), + } + } + } + Err((fail, new_state)) => Err(( + Fail { + attempting: original_attempting, + ..fail + }, + new_state, + )), + } + } +} + pub fn satisfies<'a, P, A, F>(parser: P, predicate: F) -> impl Parser<'a, A> where P: Parser<'a, A>, diff --git a/tests/test_canonicalize.rs b/tests/test_canonicalize.rs index bd2bd01a24..1539251f3e 100644 --- a/tests/test_canonicalize.rs +++ b/tests/test_canonicalize.rs @@ -205,7 +205,7 @@ mod test_canonicalize { // This function will get passed in as a pointer. let src = indoc!( r#" - apply = \f x -> f x + apply = \f, x -> f x identity = \a -> a diff --git a/tests/test_format.rs b/tests/test_format.rs index 8590f0a43c..5aafb9d933 100644 --- a/tests/test_format.rs +++ b/tests/test_format.rs @@ -134,7 +134,7 @@ mod test_format { fn func_def() { expr_formats_same(indoc!( r#" - f = \x y -> + f = \x, y -> x f 4 @@ -147,7 +147,7 @@ mod test_format { expr_formats_to( indoc!( r#" - f = \x y -> + f = \x, y -> y = 4 z = 8 x @@ -156,7 +156,7 @@ mod test_format { ), indoc!( r#" - f = \x y -> + f = \x, y -> y = 4 z = 8 @@ -169,7 +169,7 @@ mod test_format { expr_formats_same(indoc!( r#" - f = \x y -> + f = \x, y -> a = 3 b = 6 @@ -289,7 +289,7 @@ mod test_format { fn multi_arg_closure() { expr_formats_same(indoc!( r#" - \a b c -> a b c + \a, b, c -> a b c "# )); } @@ -467,7 +467,7 @@ mod test_format { // fn record_field_destructuring() { // expr_formats_same(indoc!( // r#" - // case foo of + // case foo when // { x: 5 } -> 42 // "# // )); @@ -519,7 +519,7 @@ mod test_format { expr_formats_same(indoc!( r#" - identity = \a + identity = \a, b -> a @@ -529,8 +529,8 @@ mod test_format { expr_formats_same(indoc!( r#" - identity = \a - b + identity = \a, + b, # it's c!! c -> a diff --git a/tests/test_infer.rs b/tests/test_infer.rs index 8d6d7bd073..2ae4017e66 100644 --- a/tests/test_infer.rs +++ b/tests/test_infer.rs @@ -305,7 +305,7 @@ mod test_infer { infer_eq( indoc!( r#" - \_ _ -> 42 + \_, _ -> 42 "# ), "*, * -> Int", @@ -317,7 +317,7 @@ mod test_infer { infer_eq( indoc!( r#" - \_ _ _ -> "test!" + \_, _, _ -> "test!" "# ), "*, *, * -> Str", @@ -373,7 +373,7 @@ mod test_infer { infer_eq( indoc!( r#" - func = \_ _ -> 42 + func = \_, _ -> 42 func "# @@ -387,7 +387,7 @@ mod test_infer { infer_eq( indoc!( r#" - f = \_ _ _ -> "test!" + f = \_, _, _ -> "test!" f "# @@ -401,7 +401,7 @@ mod test_infer { infer_eq( indoc!( r#" - a = \_ _ _ -> "test!" + a = \_, _, _ -> "test!" b = a @@ -545,7 +545,7 @@ mod test_infer { infer_eq( indoc!( r#" - always = \a b -> a + always = \a, b -> a 1 |> always "foo" "# @@ -610,7 +610,7 @@ mod test_infer { infer_eq( indoc!( r#" - apply = \f x -> f x + apply = \f, x -> f x identity = \a -> a apply identity 5 @@ -625,7 +625,7 @@ mod test_infer { infer_eq( indoc!( r#" - \f x -> f x + \f, x -> f x "# ), "(a -> b), a -> b", @@ -654,7 +654,7 @@ mod test_infer { infer_eq( indoc!( r#" - \f -> (\a b -> f b a), + \f -> (\a, b -> f b a), "# ), "(a, b -> c) -> (b, a -> c)", diff --git a/tests/test_parse.rs b/tests/test_parse.rs index 9fd492fbe6..3784c39956 100644 --- a/tests/test_parse.rs +++ b/tests/test_parse.rs @@ -989,13 +989,13 @@ mod test_parse { fn two_arg_closure() { let arena = Bump::new(); let arg1 = Located::new(0, 0, 1, 2, Identifier("a")); - let arg2 = Located::new(0, 0, 3, 4, Identifier("b")); + let arg2 = Located::new(0, 0, 4, 5, Identifier("b")); let patterns = bumpalo::vec![in &arena; arg1, arg2]; let expected = Closure( arena.alloc(patterns), - arena.alloc(Located::new(0, 0, 8, 10, Int("42"))), + arena.alloc(Located::new(0, 0, 9, 11, Int("42"))), ); - let actual = parse_with(&arena, "\\a b -> 42"); + let actual = parse_with(&arena, "\\a, b -> 42"); assert_eq!(Ok(expected), actual); } @@ -1004,14 +1004,14 @@ mod test_parse { fn three_arg_closure() { let arena = Bump::new(); let arg1 = Located::new(0, 0, 1, 2, Identifier("a")); - let arg2 = Located::new(0, 0, 3, 4, Identifier("b")); - let arg3 = Located::new(0, 0, 5, 6, Identifier("c")); + let arg2 = Located::new(0, 0, 4, 5, Identifier("b")); + let arg3 = Located::new(0, 0, 7, 8, Identifier("c")); let patterns = bumpalo::vec![in &arena; arg1, arg2, arg3]; let expected = Closure( arena.alloc(patterns), - arena.alloc(Located::new(0, 0, 10, 12, Int("42"))), + arena.alloc(Located::new(0, 0, 12, 14, Int("42"))), ); - let actual = parse_with(&arena, "\\a b c -> 42"); + let actual = parse_with(&arena, "\\a, b, c -> 42"); assert_eq!(Ok(expected), actual); } @@ -1020,13 +1020,13 @@ mod test_parse { fn closure_with_underscores() { let arena = Bump::new(); let underscore1 = Located::new(0, 0, 1, 2, Underscore); - let underscore2 = Located::new(0, 0, 3, 4, Underscore); + let underscore2 = Located::new(0, 0, 4, 5, Underscore); let patterns = bumpalo::vec![in &arena; underscore1, underscore2]; let expected = Closure( arena.alloc(patterns), - arena.alloc(Located::new(0, 0, 8, 10, Int("42"))), + arena.alloc(Located::new(0, 0, 9, 11, Int("42"))), ); - let actual = parse_with(&arena, "\\_ _ -> 42"); + let actual = parse_with(&arena, "\\_, _ -> 42"); assert_eq!(Ok(expected), actual); } @@ -1259,21 +1259,21 @@ mod test_parse { let args = bumpalo::vec![in &arena; Located::new(1,1,7,8, Identifier("x")), - Located::new(1,1,9,10, Underscore) + Located::new(1,1,10,11, Underscore) ]; - let body = Located::new(1, 1, 14, 16, Int("42")); + let body = Located::new(1, 1, 15, 17, Int("42")); let closure = Expr::Closure(&args, &body); let def = Def::Body( arena.alloc(Located::new(1, 1, 0, 3, Identifier("foo"))), - arena.alloc(Located::new(1, 1, 6, 16, closure)), + arena.alloc(Located::new(1, 1, 6, 17, closure)), ); let loc_def = &*arena.alloc(Located::new( 1, 1, 0, - 16, + 17, Def::SpaceBefore(arena.alloc(def), newline.into_bump_slice()), )); @@ -1287,7 +1287,7 @@ mod test_parse { indoc!( r#" foo : Int, Float -> Bool - foo = \x _ -> 42 + foo = \x, _ -> 42 42 "# diff --git a/tests/test_uniqueness_infer.rs b/tests/test_uniqueness_infer.rs index 29c47bf40b..677d0f15af 100644 --- a/tests/test_uniqueness_infer.rs +++ b/tests/test_uniqueness_infer.rs @@ -322,7 +322,7 @@ mod test_infer_uniq { infer_eq( indoc!( r#" - \_ _ -> 42 + \_, _ -> 42 "# ), "Attr.Attr * (*, * -> Attr.Attr * Int)", @@ -334,7 +334,7 @@ mod test_infer_uniq { infer_eq( indoc!( r#" - \_ _ _ -> "test!" + \_, _, _ -> "test!" "# ), "Attr.Attr * (*, *, * -> Attr.Attr * Str)", @@ -390,7 +390,7 @@ mod test_infer_uniq { infer_eq( indoc!( r#" - func = \_ _ -> 42 + func = \_, _ -> 42 func "# @@ -404,7 +404,7 @@ mod test_infer_uniq { infer_eq( indoc!( r#" - f = \_ _ _ -> "test!" + f = \_, _, _ -> "test!" f "# @@ -418,7 +418,7 @@ mod test_infer_uniq { infer_eq( indoc!( r#" - a = \_ _ _ -> "test!" + a = \_, _, _ -> "test!" b = a @@ -575,7 +575,7 @@ mod test_infer_uniq { infer_eq( indoc!( r#" - always = \a b -> a + always = \a, b -> a 1 |> always "foo" "# @@ -640,7 +640,7 @@ mod test_infer_uniq { infer_eq( indoc!( r#" - apply = \f x -> f x + apply = \f, x -> f x identity = \a -> a apply identity 5 @@ -655,7 +655,7 @@ mod test_infer_uniq { infer_eq( indoc!( r#" - \f x -> f x + \f, x -> f x "# ), "Attr.Attr * (Attr.Attr * (a -> b), a -> b)", @@ -684,7 +684,7 @@ mod test_infer_uniq { infer_eq( indoc!( r#" - \f -> (\a b -> f b a), + \f -> (\a, b -> f b a), "# ), "Attr.Attr * (Attr.Attr * (a, b -> c) -> Attr.Attr * (b, a -> c))",