Fix record descruturing in defs

This commit is contained in:
Richard Feldman 2019-10-01 00:04:18 +03:00
parent 44a4a55c37
commit f96d3c7a02
5 changed files with 177 additions and 45 deletions

View file

@ -291,6 +291,7 @@ pub enum Attempting {
Def, Def,
Expression, Expression,
Module, Module,
Record,
Identifier, Identifier,
} }

View file

@ -28,12 +28,13 @@ use parse::ast::{Attempting, CommentOrNewline, Def, Expr, Pattern, Spaceable};
use parse::blankspace::{ use parse::blankspace::{
space0, space0_after, space0_around, space0_before, space1, space1_before, space0, space0_after, space0_around, space0_before, space1, space1_before,
}; };
use parse::ident::{ident, Ident}; use parse::ident::{ident, Ident, MaybeQualified};
use parse::number_literal::number_literal; use parse::number_literal::number_literal;
use parse::parser::{ use parse::parser::{
and, and_then, attempt, between, char, either, loc, map, map_with_arena, one_of4, one_of5, and, attempt, between, char, either, loc, map, map_with_arena, not_followed_by, one_of2,
one_of9, one_or_more, optional, sep_by0, skip_first, skip_second, string, then, unexpected, one_of4, one_of5, one_of9, one_or_more, optional, sep_by0, skip_first, skip_second, string,
unexpected_eof, zero_or_more, Either, Fail, FailReason, ParseResult, Parser, State, then, unexpected, unexpected_eof, zero_or_more, Either, Fail, FailReason, ParseResult, Parser,
State,
}; };
use parse::string_literal::string_literal; use parse::string_literal::string_literal;
use region::Located; use region::Located;
@ -149,7 +150,7 @@ pub fn loc_parenthetical_expr<'a>(min_indent: u16) -> impl Parser<'a, Located<Ex
// e.g. in `((foo bar) baz.blah)` the `.blah` will be consumed by the `baz` parser // e.g. in `((foo bar) baz.blah)` the `.blah` will be consumed by the `baz` parser
either( either(
one_or_more(skip_first(char('.'), unqualified_ident())), one_or_more(skip_first(char('.'), unqualified_ident())),
and(space0(min_indent), either(char('='), char(':'))), and(space0(min_indent), either(equals_for_def(), char(':'))),
), ),
)), )),
)), )),
@ -181,6 +182,12 @@ pub fn loc_parenthetical_expr<'a>(min_indent: u16) -> impl Parser<'a, Located<Ex
) )
} }
/// The '=' used in a def can't be followed by another '=' (or else it's actually
/// an "==") and also it can't be followed by '>' (or else it's actually an "=>")
fn equals_for_def<'a>() -> impl Parser<'a, ()> {
not_followed_by(char('='), one_of2(char('='), char('>')))
}
/// A definition, consisting of one of these: /// A definition, consisting of one of these:
/// ///
/// * A pattern followed by '=' and then an expression /// * A pattern followed by '=' and then an expression
@ -195,7 +202,7 @@ pub fn def<'a>(min_indent: u16) -> impl Parser<'a, Def<'a>> {
and( and(
skip_second( skip_second(
space0_after(loc_closure_param(min_indent), min_indent), space0_after(loc_closure_param(min_indent), min_indent),
char('='), equals_for_def(),
), ),
space0_before( space0_before(
loc(move |arena, state| parse_expr(indented_more, arena, state)), loc(move |arena, state| parse_expr(indented_more, arena, state)),
@ -288,38 +295,6 @@ fn parse_def_expr<'a>(
} }
} }
fn parse_nested_def_body<'a, S>(
min_indent: u16,
equals_sign_indent: u16,
arena: &'a Bump,
state: State<'a>,
loc_pattern: Located<Pattern<'a>>,
) -> ParseResult<'a, Located<Expr<'a>>> {
let original_indent = state.indent_col;
if original_indent < min_indent {
panic!("TODO this declaration is outdented too far");
// `<` because '=' should be same indent or greater
} else if equals_sign_indent < original_indent {
panic!("TODO the = in this declaration seems outdented");
} else {
then(
loc(move |arena, state| {
parse_expr(original_indent + 1, arena, state)
}),
move |arena, state, loc_expr| {
if state.indent_col != original_indent {
panic!(
"TODO the return expression was indented differently from the original assignment",
);
} else {
Ok((loc_expr, state))
}
},
).parse(arena, state)
}
}
fn loc_function_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<Expr<'a>>> { fn loc_function_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<Expr<'a>>> {
// Don't parse operators, because they have a higher precedence than function application. // Don't parse operators, because they have a higher precedence than function application.
// If we encounter one, we're done parsing function args! // If we encounter one, we're done parsing function args!
@ -490,18 +465,23 @@ pub fn ident_etc<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
Ok((Expr::Apply(arena.alloc((loc_expr, loc_args))), state)) Ok((Expr::Apply(arena.alloc((loc_expr, loc_args))), state))
} }
Some(Either::Second((_space_list, Either::First(indent)))) => { Some(Either::Second((spaces_before_equals, Either::First(equals_indent)))) => {
let value: Pattern<'a> = Pattern::from_ident(arena, loc_ident.value); let pattern: Pattern<'a> = Pattern::from_ident(arena, loc_ident.value);
let value = if spaces_before_equals.is_empty() {
pattern
} else {
Pattern::SpaceAfter(arena.alloc(pattern), spaces_before_equals)
};
let region = loc_ident.region; let region = loc_ident.region;
let loc_pattern = Located { region, value }; let loc_pattern = Located { region, value };
let (spaces, state) = space0(min_indent).parse(arena, state)?; let (spaces_after_equals, state) = space0(min_indent).parse(arena, state)?;
let (parsed_expr, state) = let (parsed_expr, state) =
parse_def_expr(min_indent, indent, arena, state, loc_pattern)?; parse_def_expr(min_indent, equals_indent, arena, state, loc_pattern)?;
let answer = if spaces.is_empty() { let answer = if spaces_after_equals.is_empty() {
parsed_expr parsed_expr
} else { } else {
Expr::SpaceBefore(arena.alloc(parsed_expr), spaces) Expr::SpaceBefore(arena.alloc(parsed_expr), spaces_after_equals)
}; };
Ok((answer, state)) Ok((answer, state))
@ -528,6 +508,7 @@ pub fn equals_with_indent<'a>() -> impl Parser<'a, u16> {
Some(ch) if ch == '=' => { Some(ch) if ch == '=' => {
match iter.peekable().peek() { match iter.peekable().peek() {
// The '=' must not be followed by another `=` or `>` // The '=' must not be followed by another `=` or `>`
// (See equals_for_def() for explanation)
Some(next_ch) if next_ch != &'=' && next_ch != &'>' => { Some(next_ch) if next_ch != &'=' && next_ch != &'>' => {
Ok((state.indent_col, state.advance_without_indenting(1)?)) Ok((state.indent_col, state.advance_without_indenting(1)?))
} }
@ -599,7 +580,65 @@ pub fn record_literal<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
); );
let fields = collection(char('{'), loc(field), char(','), char('}'), min_indent); let fields = collection(char('{'), loc(field), char(','), char('}'), min_indent);
attempt(Attempting::List, map(fields, Expr::Record)) then(
and(
attempt(Attempting::Record, loc(fields)),
optional(and(space0(min_indent), equals_with_indent())),
),
move |arena, state, (loc_field_exprs, opt_def)| match opt_def {
None => Ok((Expr::Record(loc_field_exprs.value), state)),
Some((spaces_before_equals, equals_indent)) => {
let region = loc_field_exprs.region;
let field_exprs = loc_field_exprs.value;
let mut loc_patterns = Vec::with_capacity_in(field_exprs.len(), arena);
for loc_field_expr in field_exprs {
let region = loc_field_expr.region;
// If this is a record destructure, these should all be
// unqualified Var expressions!
let value = match loc_field_expr.value {
Expr::Var(module_parts, value) => {
if module_parts.is_empty() {
Pattern::Identifier(value)
} else {
Pattern::QualifiedIdentifier(MaybeQualified {
module_parts,
value,
})
}
}
_ => {
panic!("TODO handle malformed record destructure.");
// Malformed("???"),
}
};
loc_patterns.push(Located { region, value });
}
let pattern = Pattern::RecordDestructure(loc_patterns);
let value = if spaces_before_equals.is_empty() {
pattern
} else {
Pattern::SpaceAfter(arena.alloc(pattern), spaces_before_equals)
};
let loc_pattern = Located { region, value };
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_pattern)?;
let answer = if spaces_after_equals.is_empty() {
parsed_expr
} else {
Expr::SpaceBefore(arena.alloc(parsed_expr), spaces_after_equals)
};
Ok((answer, state))
}
},
)
} }
/// This is mainly for matching variants in closure params, e.g. \Foo -> ... /// This is mainly for matching variants in closure params, e.g. \Foo -> ...

View file

@ -162,6 +162,42 @@ where
} }
} }
pub fn not_followed_by<'a, P, ByParser, By, Val>(parser: P, by: ByParser) -> impl Parser<'a, Val>
where
ByParser: Parser<'a, By>,
P: Parser<'a, Val>,
{
move |arena, state: State<'a>| {
parser.parse(arena, state).and_then(|(answer, state)| {
let original_state = state.clone();
match by.parse(arena, state) {
Ok((_, state)) => Err((
Fail {
attempting: state.attempting,
reason: FailReason::ConditionFailed,
},
original_state,
)),
Err(_) => Ok((answer, original_state)),
}
})
}
}
pub fn lookahead<'a, Peek, P, PeekVal, Val>(peek: Peek, parser: P) -> impl Parser<'a, Val>
where
Peek: Parser<'a, PeekVal>,
P: Parser<'a, Val>,
{
move |arena, state: State<'a>| {
let original_state = state.clone();
peek.parse(arena, state)
.and_then(|_| parser.parse(arena, original_state))
}
}
pub fn and_then<'a, P1, P2, F, Before, After>(parser: P1, transform: F) -> impl Parser<'a, After> pub fn and_then<'a, P1, P2, F, Before, After>(parser: P1, transform: F) -> impl Parser<'a, After>
where where
P1: Parser<'a, Before>, P1: Parser<'a, Before>,

View file

@ -126,6 +126,17 @@ mod test_format {
)); ));
} }
#[test]
fn record_destructuring() {
assert_formats_same(indoc!(
r#"# comment to reset indentation
{ x, y } = 5
42
"#
));
}
// RECORD LITERALS // RECORD LITERALS
#[test] #[test]

View file

@ -724,6 +724,51 @@ mod test_parse {
); );
} }
#[test]
fn record_destructure_def() {
let arena = Bump::new();
let newlines = bumpalo::vec![in &arena; Newline, Newline];
let newline = bumpalo::vec![in &arena; Newline];
let fields = bumpalo::vec![in &arena;
Located::new(1, 1, 2, 3, Identifier("x")),
Located::new(1, 1, 5, 6, Identifier("y"))
];
let def1 = Def::BodyOnly(
Located::new(1, 1, 0, 8, RecordDestructure(fields)),
arena.alloc(Located::new(1, 1, 11, 12, Int("5"))),
);
let def2 = Def::BodyOnly(
Located::new(2, 2, 0, 1, Identifier("y")),
arena.alloc(Located::new(2, 2, 4, 5, Int("6"))),
);
// NOTE: The first def always gets reordered to the end (because it
// gets added by .push(), since that's more efficient and since
// canonicalization is going to re-sort these all anyway.)
let defs = bumpalo::vec![in &arena;
(newline.into_bump_slice(), def2),
(Vec::new_in(&arena).into_bump_slice(), def1)
];
let ret = Expr::SpaceBefore(arena.alloc(Int("42")), newlines.into_bump_slice());
let loc_ret = Located::new(4, 4, 0, 2, ret);
let reset_indentation = bumpalo::vec![in &arena; LineComment(" reset indentation")];
let expected = Expr::SpaceBefore(
arena.alloc(Defs(arena.alloc((defs, loc_ret)))),
reset_indentation.into_bump_slice(),
);
assert_parses_to(
indoc!(
r#"# reset indentation
{ x, y } = 5
y = 6
42
"#
),
expected,
);
}
// TODO test hex/oct/binary parsing // TODO test hex/oct/binary parsing
// //
// TODO test for \t \r and \n in string literals *outside* unicode escape sequence! // TODO test for \t \r and \n in string literals *outside* unicode escape sequence!