mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-01 15:51:12 +00:00
Fix record descruturing in defs
This commit is contained in:
parent
44a4a55c37
commit
f96d3c7a02
5 changed files with 177 additions and 45 deletions
|
@ -291,6 +291,7 @@ pub enum Attempting {
|
||||||
Def,
|
Def,
|
||||||
Expression,
|
Expression,
|
||||||
Module,
|
Module,
|
||||||
|
Record,
|
||||||
Identifier,
|
Identifier,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
129
src/parse/mod.rs
129
src/parse/mod.rs
|
@ -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 -> ...
|
||||||
|
|
|
@ -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>,
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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!
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue