From ebaed27193ba61b1c814bf3356c55689bbd95671 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Fri, 4 Oct 2019 11:46:37 +0300 Subject: [PATCH] Parse `if` and args w/ idents, format w/ parens --- src/parse/ast.rs | 51 ++++++++++++++++++++------ src/parse/mod.rs | 85 +++++++++++++++++++++++++++++++++++++++----- src/parse/parser.rs | 20 +++++++++++ tests/test_format.rs | 19 +++++++++- tests/test_parse.rs | 29 +++++++++++++++ 5 files changed, 184 insertions(+), 20 deletions(-) diff --git a/src/parse/ast.rs b/src/parse/ast.rs index c19565a4a6..8f518d6b5d 100644 --- a/src/parse/ast.rs +++ b/src/parse/ast.rs @@ -343,7 +343,12 @@ impl<'a> Expr<'a> { } } -pub fn format<'a>(arena: &'a Bump, expr: &'a Expr<'a>, indent: u16) -> String<'a> { +pub fn format<'a>( + arena: &'a Bump, + expr: &'a Expr<'a>, + indent: u16, + apply_needs_parens: bool, +) -> String<'a> { use self::Expr::*; let mut buf = String::new_in(arena); @@ -351,10 +356,10 @@ pub fn format<'a>(arena: &'a Bump, expr: &'a Expr<'a>, indent: u16) -> String<'a match expr { SpaceBefore(sub_expr, spaces) => { buf.push_str(&format_spaces(arena, spaces.iter(), indent)); - buf.push_str(&format(arena, sub_expr, indent)); + buf.push_str(&format(arena, sub_expr, indent, apply_needs_parens)); } SpaceAfter(sub_expr, spaces) => { - buf.push_str(&format(arena, sub_expr, indent)); + buf.push_str(&format(arena, sub_expr, indent, apply_needs_parens)); buf.push_str(&format_spaces(arena, spaces.iter(), indent)); } @@ -372,12 +377,20 @@ pub fn format<'a>(arena: &'a Bump, expr: &'a Expr<'a>, indent: u16) -> String<'a buf.push_str(name); } Apply((loc_expr, loc_args)) => { - buf.push_str(&format(arena, &loc_expr.value, indent)); + if apply_needs_parens { + buf.push('('); + } + + buf.push_str(&format(arena, &loc_expr.value, indent, true)); for loc_arg in loc_args { buf.push(' '); - buf.push_str(&format(arena, &loc_arg.value, indent)); + buf.push_str(&format(arena, &loc_arg.value, indent, true)); + } + + if apply_needs_parens { + buf.push(')'); } } BlockStr(lines) => { @@ -424,7 +437,7 @@ pub fn format<'a>(arena: &'a Bump, expr: &'a Expr<'a>, indent: u16) -> String<'a buf.push_str("-> "); - buf.push_str(&format(arena, &loc_ret.value, indent)); + buf.push_str(&format(arena, &loc_ret.value, indent, false)); } Defs((defs, ret)) => { // The first def is actually at the end of the list, because @@ -445,7 +458,15 @@ pub fn format<'a>(arena: &'a Bump, expr: &'a Expr<'a>, indent: u16) -> String<'a buf.push_str(&format_def(arena, def, indent)); } - buf.push_str(&format(arena, &ret.value, indent)); + buf.push_str(&format(arena, &ret.value, indent, false)); + } + If((loc_condition, loc_then, loc_else)) => { + buf.push_str("if "); + buf.push_str(&format(arena, &loc_condition.value, indent, false)); + buf.push_str(" then "); + buf.push_str(&format(arena, &loc_then.value, indent, false)); + buf.push_str(" else "); + buf.push_str(&format(arena, &loc_else.value, indent, false)); } other => panic!("TODO implement Display for AST variant {:?}", other), } @@ -463,7 +484,7 @@ pub fn format_def<'a>(arena: &'a Bump, def: &'a Def<'a>, indent: u16) -> String< BodyOnly(loc_pattern, loc_expr) => { buf.push_str(&format_pattern(arena, &loc_pattern.value, indent, true)); buf.push_str(" = "); - buf.push_str(&format(arena, &loc_expr.value, indent)); + buf.push_str(&format(arena, &loc_expr.value, indent, false)); } AnnotatedBody(_loc_annotation, _loc_pattern, _loc_expr) => { panic!("TODO have format_def support AnnotationOnly") @@ -539,10 +560,20 @@ fn format_pattern<'a>( // Space SpaceBefore(sub_pattern, spaces) => { buf.push_str(&format_spaces(arena, spaces.iter(), indent)); - buf.push_str(&format_pattern(arena, sub_pattern, indent, true)); + buf.push_str(&format_pattern( + arena, + sub_pattern, + indent, + apply_needs_parens, + )); } SpaceAfter(sub_pattern, spaces) => { - buf.push_str(&format_pattern(arena, sub_pattern, indent, true)); + buf.push_str(&format_pattern( + arena, + sub_pattern, + indent, + apply_needs_parens, + )); buf.push_str(&format_spaces(arena, spaces.iter(), indent)); } diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 8b14fad3f0..7d0232a3f7 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -26,12 +26,12 @@ use bumpalo::Bump; use operator::Operator; use parse::ast::{Attempting, Def, Expr, Pattern, Spaceable}; use parse::blankspace::{ - space0, space0_after, space0_around, space0_before, space1, space1_before, + space0, space0_after, space0_around, space0_before, space1, space1_around, space1_before, }; use parse::ident::{ident, Ident, MaybeQualified}; use parse::number_literal::number_literal; use parse::parser::{ - and, attempt, between, char, either, loc, map, map_with_arena, not_followed_by, one_of16, + and, attempt, between, char, either, loc, map, map_with_arena, not, not_followed_by, one_of16, one_of2, one_of4, one_of5, one_of9, one_or_more, optional, sep_by0, skip_first, skip_second, string, then, unexpected, unexpected_eof, zero_or_more, Either, Fail, FailReason, ParseResult, Parser, State, @@ -438,9 +438,43 @@ fn parse_def_expr<'a>( } fn loc_function_arg<'a>(min_indent: u16) -> impl Parser<'a, Located>> { - // Don't parse operators, because they have a higher precedence than function application. - // If we encounter one, we're done parsing function args! - move |arena, state| loc_parse_expr_body_without_operators(min_indent, arena, state) + skip_first( + // If this is a reserved keyword ("if", "then", "case, "when"), then + // it is not a function argument! + not(reserved_keyword()), + // Don't parse operators, because they have a higher precedence than function application. + // If we encounter one, we're done parsing function args! + move |arena, state| loc_parse_function_arg(min_indent, arena, state), + ) +} + +fn loc_parse_function_arg<'a>( + min_indent: u16, + arena: &'a Bump, + state: State<'a>, +) -> ParseResult<'a, Located>> { + one_of9( + loc_parenthetical_expr(min_indent), + loc(string_literal()), + loc(number_literal()), + loc(closure(min_indent)), + loc(record_literal(min_indent)), + loc(list_literal(min_indent)), + loc(case_expr(min_indent)), + loc(if_expr(min_indent)), + loc(ident_without_apply()), + ) + .parse(arena, state) +} + +fn reserved_keyword<'a>() -> impl Parser<'a, ()> { + one_of5( + string(keyword::IF), + string(keyword::THEN), + string(keyword::ELSE), + string(keyword::CASE), + string(keyword::WHEN), + ) } fn closure<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> { @@ -548,10 +582,37 @@ pub fn case_expr<'a>(_min_indent: u16) -> impl Parser<'a, Expr<'a>> { }) } -pub fn if_expr<'a>(_min_indent: u16) -> impl Parser<'a, Expr<'a>> { - map(string(keyword::IF), |_| { - panic!("TODO implement IF"); - }) +pub fn if_expr<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> { + map_with_arena( + and( + skip_first( + string(keyword::IF), + space1_around( + loc(move |arena, state| parse_expr(min_indent, arena, state)), + min_indent, + ), + ), + and( + skip_first( + string(keyword::THEN), + space1_around( + loc(move |arena, state| parse_expr(min_indent, arena, state)), + min_indent, + ), + ), + skip_first( + string(keyword::ELSE), + space1_before( + loc(move |arena, state| parse_expr(min_indent, arena, state)), + min_indent, + ), + ), + ), + ), + |arena, (condition, (then_branch, else_branch))| { + Expr::If(arena.alloc((condition, then_branch, else_branch))) + }, + ) } pub fn loc_function_args<'a>(min_indent: u16) -> impl Parser<'a, Vec<'a, Located>>> { @@ -623,6 +684,12 @@ pub fn ident_etc<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> { ) } +pub fn ident_without_apply<'a>() -> impl Parser<'a, Expr<'a>> { + then(loc(ident()), move |_arena, state, loc_ident| { + Ok((ident_to_expr(loc_ident.value), state)) + }) +} + pub fn equals_with_indent<'a>() -> impl Parser<'a, u16> { move |_arena, state: State<'a>| { let mut iter = state.input.chars(); diff --git a/src/parse/parser.rs b/src/parse/parser.rs index e1aea11570..76ef1c1c80 100644 --- a/src/parse/parser.rs +++ b/src/parse/parser.rs @@ -200,6 +200,26 @@ where } } +pub fn not<'a, P, Val>(parser: P) -> impl Parser<'a, ()> +where + P: Parser<'a, Val>, +{ + move |arena, state: State<'a>| { + let original_state = state.clone(); + + match parser.parse(arena, state) { + Ok((_, _)) => Err(( + Fail { + reason: FailReason::ConditionFailed, + attempting: original_state.attempting, + }, + original_state, + )), + Err((_, _)) => Ok(((), original_state)), + } + } +} + pub fn lookahead<'a, Peek, P, PeekVal, Val>(peek: Peek, parser: P) -> impl Parser<'a, Val> where Peek: Parser<'a, PeekVal>, diff --git a/tests/test_format.rs b/tests/test_format.rs index 4e1ec4d50e..ff783ed074 100644 --- a/tests/test_format.rs +++ b/tests/test_format.rs @@ -29,7 +29,7 @@ mod test_format { let expected = expected.trim_end(); match parse_with(&arena, input) { - Ok(actual) => assert_eq!(format(&arena, &actual, 0), expected), + Ok(actual) => assert_eq!(format(&arena, &actual, 0, false), expected), Err(error) => panic!("Unexpected parse failure when parsing this for formatting:\n\n{:?}\n\nParse error was:\n\n{:?}\n\n", input, error) } } @@ -178,6 +178,23 @@ mod test_format { assert_formats_same("{}"); } + // IF + + #[test] + fn single_line_if() { + assert_formats_same(indoc!( + r#" + if foo bar then a b c else d e f + "# + )); + + assert_formats_same(indoc!( + r#" + if foo (a b c) then a b c else d e f + "# + )); + } + // NEWLINES #[test] diff --git a/tests/test_parse.rs b/tests/test_parse.rs index b7f03a28b9..8e78c0a665 100644 --- a/tests/test_parse.rs +++ b/tests/test_parse.rs @@ -578,6 +578,21 @@ mod test_parse { assert_eq!(Ok(expected), actual); } + #[test] + fn apply_three_args() { + let arena = Bump::new(); + let module_parts = Vec::new_in(&arena).into_bump_slice(); + let arg1 = Located::new(0, 0, 2, 3, Var(module_parts, "b")); + let arg2 = Located::new(0, 0, 4, 5, Var(module_parts, "c")); + let arg3 = Located::new(0, 0, 6, 7, Var(module_parts, "d")); + let args = bumpalo::vec![in &arena; arg1, arg2, arg3]; + let tuple = arena.alloc((Located::new(0, 0, 0, 1, Var(module_parts, "a")), args)); + let expected = Expr::Apply(tuple); + let actual = parse_with(&arena, "a b c d"); + + assert_eq!(Ok(expected), actual); + } + #[test] fn parenthetical_apply() { let arena = Bump::new(); @@ -630,6 +645,20 @@ mod test_parse { assert_eq!(Ok(expected), actual); } + #[test] + 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 patterns = bumpalo::vec![in &arena; arg1, arg2, arg3]; + let tuple = arena.alloc((patterns, Located::new(0, 0, 10, 12, Int("42")))); + let expected = Closure(tuple); + let actual = parse_with(&arena, "\\a b c -> 42"); + + assert_eq!(Ok(expected), actual); + } + #[test] fn closure_with_underscores() { let arena = Bump::new();