diff --git a/src/can/mod.rs b/src/can/mod.rs index d01ccdd36a..8c55e998d9 100644 --- a/src/can/mod.rs +++ b/src/can/mod.rs @@ -994,7 +994,7 @@ fn add_idents_from_pattern<'a>( &QualifiedIdentifier(_name) => { panic!("TODO implement QualifiedIdentifier pattern."); } - &Apply(_) => { + &Apply(_, _) => { panic!("TODO implement Apply pattern."); // &AppliedVariant(_, ref opt_loc_args) => match opt_loc_args { // &None => (), @@ -1033,7 +1033,7 @@ fn remove_idents(pattern: &ast::Pattern, idents: &mut ImMap { panic!("TODO implement QualifiedIdentifier pattern in remove_idents."); } - Apply(_) => { + Apply(_, _) => { panic!("TODO implement Apply pattern in remove_idents."); // AppliedVariant(_, Some(loc_args)) => { // for loc_arg in loc_args { diff --git a/src/parse/ast.rs b/src/parse/ast.rs index 99e7bec69a..bdca804561 100644 --- a/src/parse/ast.rs +++ b/src/parse/ast.rs @@ -106,7 +106,7 @@ pub enum Pattern<'a> { // Variant, optionally qualified Variant(&'a [&'a str], &'a str), - Apply(&'a (Loc<&'a Pattern<'a>>, [Loc>])), + Apply(&'a Loc>, &'a [Loc>]), /// This is Loc rather than Loc so we can record comments /// around the destructured names, e.g. { x ### x does stuff ###, y } /// In practice, these patterns will always be Identifier @@ -388,7 +388,7 @@ pub fn format<'a>(arena: &'a Bump, expr: &'a Expr<'a>, indent: u16) -> String<'a buf.push('\\'); for loc_pattern in loc_patterns { - buf.push_str(&format_pattern(arena, &loc_pattern.value, indent)); + buf.push_str(&format_pattern(arena, &loc_pattern.value, indent, true)); buf.push(' '); } @@ -432,7 +432,7 @@ pub fn format_def<'a>(arena: &'a Bump, def: &'a Def<'a>, indent: u16) -> String< match def { Def::AnnotationOnly(_region) => panic!("TODO have format_def support AnnotationOnly"), BodyOnly(loc_pattern, loc_expr) => { - buf.push_str(&format_pattern(arena, &loc_pattern.value, indent)); + buf.push_str(&format_pattern(arena, &loc_pattern.value, indent, true)); buf.push_str(" = "); buf.push_str(&format(arena, &loc_expr.value, indent)); } @@ -444,7 +444,12 @@ pub fn format_def<'a>(arena: &'a Bump, def: &'a Def<'a>, indent: u16) -> String< buf } -fn format_pattern<'a>(arena: &'a Bump, pattern: &'a Pattern<'a>, indent: u16) -> String<'a> { +fn format_pattern<'a>( + arena: &'a Bump, + pattern: &'a Pattern<'a>, + indent: u16, + apply_needs_parens: bool, +) -> String<'a> { use self::Pattern::*; let mut buf = String::new_in(arena); @@ -459,12 +464,20 @@ fn format_pattern<'a>(arena: &'a Bump, pattern: &'a Pattern<'a>, indent: u16) -> buf.push_str(name); } - Apply((loc_pattern, loc_arg_patterns)) => { - buf.push_str(&format_pattern(arena, loc_pattern.value, indent)); + Apply(loc_pattern, loc_arg_patterns) => { + if apply_needs_parens { + buf.push('('); + } - for loc_arg in loc_arg_patterns { + buf.push_str(&format_pattern(arena, &loc_pattern.value, indent, true)); + + for loc_arg in loc_arg_patterns.iter() { buf.push(' '); - buf.push_str(&format_pattern(arena, &loc_arg.value, indent)); + buf.push_str(&format_pattern(arena, &loc_arg.value, indent, true)); + } + + if apply_needs_parens { + buf.push(')'); } } RecordDestructure(loc_patterns) => { @@ -479,7 +492,7 @@ fn format_pattern<'a>(arena: &'a Bump, pattern: &'a Pattern<'a>, indent: u16) -> buf.push_str(", "); } - buf.push_str(&format_pattern(arena, &loc_pattern.value, indent)); + buf.push_str(&format_pattern(arena, &loc_pattern.value, indent, true)); } buf.push_str(" }"); @@ -494,10 +507,10 @@ fn format_pattern<'a>(arena: &'a Bump, pattern: &'a Pattern<'a>, indent: u16) -> // Space SpaceBefore(sub_pattern, spaces) => { buf.push_str(&format_spaces(arena, spaces.iter(), indent)); - buf.push_str(&format_pattern(arena, sub_pattern, indent)); + buf.push_str(&format_pattern(arena, sub_pattern, indent, true)); } SpaceAfter(sub_pattern, spaces) => { - buf.push_str(&format_pattern(arena, sub_pattern, indent)); + buf.push_str(&format_pattern(arena, sub_pattern, indent, true)); buf.push_str(&format_spaces(arena, spaces.iter(), indent)); } diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 171b747a19..e5c948d7fc 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -31,8 +31,8 @@ use parse::blankspace::{ 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_of10, - one_of2, one_of4, one_of5, one_or_more, optional, sep_by0, skip_first, skip_second, string, + and, attempt, between, char, either, loc, map, map_with_arena, not_followed_by, 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, }; @@ -69,8 +69,7 @@ fn loc_parse_expr_body_without_operators<'a>( arena: &'a Bump, state: State<'a>, ) -> ParseResult<'a, Located>> { - one_of10( - loc_parenthetical_def(min_indent), + one_of9( loc_parenthetical_expr(min_indent), loc(string_literal()), loc(number_literal()), @@ -126,7 +125,7 @@ fn parse_expr<'a>(min_indent: u16, arena: &'a Bump, state: State<'a>) -> ParseRe } pub fn loc_parenthetical_expr<'a>(min_indent: u16) -> impl Parser<'a, Located>> { - map_with_arena( + then( loc(and( between( char('('), @@ -140,34 +139,121 @@ pub fn loc_parenthetical_expr<'a>(min_indent: u16) -> impl Parser<'a, Located Located { - region: loc_expr_with_extras.region, - value: Expr::Apply(arena.alloc((loc_expr, loc_args))), - }, + Some(Either::First(loc_args)) => Ok(( + Located { + region: loc_expr_with_extras.region, + value: Expr::Apply(arena.alloc((loc_expr, loc_args))), + }, + state, + )), + // '=' after optional spaces + Some(Either::Second(Either::Second((spaces_before_equals, equals_indent)))) => { + let region = loc_expr.region; + + // Re-parse the Expr as a Pattern. + let pattern = match expr_to_pattern(arena, loc_expr.value) { + Ok(valid) => valid, + Err(fail) => return Err((fail, state)), + }; + + // Make sure we don't discard the spaces - might be comments in there! + let value = if spaces_before_equals.is_empty() { + pattern + } else { + Pattern::SpaceAfter(arena.alloc(pattern), spaces_before_equals) + }; + + let loc_first_pattern = Located { + region: region.clone(), + value, + }; + + // Continue parsing the expression as a Def. + let (spaces_after_equals, state) = space0(min_indent).parse(arena, state)?; + let (parsed_expr, state) = + parse_def_expr(min_indent, equals_indent, arena, state, loc_first_pattern)?; + + let value = if spaces_after_equals.is_empty() { + parsed_expr + } else { + Expr::SpaceBefore(arena.alloc(parsed_expr), spaces_after_equals) + }; + + Ok((Located { value, region }, state)) + } // '.' and a record field immediately after ')', no optional spaces - Some(Either::Second(fields)) => Located { - region: loc_expr_with_extras.region, - value: Expr::Field(arena.alloc(loc_expr), fields), - }, - None => loc_expr, + Some(Either::Second(Either::First(fields))) => Ok(( + Located { + region: loc_expr_with_extras.region, + value: Expr::Field(arena.alloc(loc_expr), fields), + }, + state, + )), + None => Ok((loc_expr, state)), } }, ) } +/// If the given Expr would parse the same way as a valid Pattern, convert it. +/// Example: (foo) could be either an Expr::Var("foo") or Pattern::Identifier("foo") +fn expr_to_pattern<'a>(arena: &'a Bump, expr: Expr<'a>) -> Result, Fail> { + match expr { + Expr::Var(module_parts, value) => { + if module_parts.is_empty() { + Ok(Pattern::Identifier(value)) + } else { + Ok(Pattern::QualifiedIdentifier(MaybeQualified { + module_parts, + value, + })) + } + } + Expr::Variant(module_parts, value) => Ok(Pattern::Variant(module_parts, value)), + Expr::Apply((loc_val, loc_args)) => { + let region = loc_val.region.clone(); + let value = expr_to_pattern(arena, loc_val.value.clone())?; + let val_pattern = arena.alloc(Located { region, value }); + + let mut arg_patterns = Vec::with_capacity_in(loc_args.len(), arena); + + for loc_arg in loc_args { + let region = loc_arg.region.clone(); + let value = expr_to_pattern(arena, loc_arg.value.clone())?; + + arg_patterns.push(Located { region, value }); + } + + let pattern = Pattern::Apply(val_pattern, arg_patterns.into_bump_slice()); + + Ok(pattern) + } + + _ => panic!("TODO handle expr_to_pattern for {:?}", expr), + } +} + /// A def beginning with a parenthetical pattern, for example: /// /// (UserId userId) = ...