Merge pull request #4591 from roc-lang/tuple-accessor

Tuple accessors after tuples/records
This commit is contained in:
Richard Feldman 2022-11-24 22:34:27 -05:00 committed by GitHub
commit f52ad5cbba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 108 additions and 63 deletions

View file

@ -535,8 +535,8 @@ pub enum TypeAnnotation<'a> {
Tuple { Tuple {
fields: Collection<'a, Loc<TypeAnnotation<'a>>>, fields: Collection<'a, Loc<TypeAnnotation<'a>>>,
/// The row type variable in an open record, e.g. the `r` in `{ name: Str }r`. /// The row type variable in an open tuple, e.g. the `r` in `( Str, Str )r`.
/// This is None if it's a closed record annotation like `{ name: Str }`. /// This is None if it's a closed tuple annotation like `( Str, Str )`.
ext: Option<&'a Loc<TypeAnnotation<'a>>>, ext: Option<&'a Loc<TypeAnnotation<'a>>>,
}, },

View file

@ -6,7 +6,7 @@ use crate::blankspace::{
space0_after_e, space0_around_e_no_after_indent_check, space0_around_ee, space0_before_e, space0_after_e, space0_around_e_no_after_indent_check, space0_around_ee, space0_before_e,
space0_before_optional_after, space0_e, space0_before_optional_after, space0_e,
}; };
use crate::ident::{lowercase_ident, parse_ident, Ident}; use crate::ident::{integer_ident, lowercase_ident, parse_ident, Accessor, Ident};
use crate::keyword; use crate::keyword;
use crate::parser::{ use crate::parser::{
self, backtrackable, increment_min_indent, line_min_indent, optional, reset_min_indent, self, backtrackable, increment_min_indent, line_min_indent, optional, reset_min_indent,
@ -124,13 +124,9 @@ fn loc_expr_in_parens_etc_help<'a>() -> impl Parser<'a, Loc<Expr<'a>>, EExpr<'a>
map_with_arena!( map_with_arena!(
loc!(and!( loc!(and!(
specialize(EExpr::InParens, loc_expr_in_parens_help()), specialize(EExpr::InParens, loc_expr_in_parens_help()),
one_of![record_field_access_chain(), |a, s, _m| Ok(( record_field_access_chain()
NoProgress,
Vec::new_in(a),
s
))]
)), )),
move |arena: &'a Bump, value: Loc<(Loc<Expr<'a>>, Vec<'a, &'a str>)>| { move |arena: &'a Bump, value: Loc<(Loc<Expr<'a>>, Vec<'a, Accessor<'a>>)>| {
let Loc { let Loc {
mut region, mut region,
value: (loc_expr, field_accesses), value: (loc_expr, field_accesses),
@ -143,12 +139,7 @@ fn loc_expr_in_parens_etc_help<'a>() -> impl Parser<'a, Loc<Expr<'a>>, EExpr<'a>
if field_accesses.is_empty() { if field_accesses.is_empty() {
region = loc_expr.region; region = loc_expr.region;
} else { } else {
for field in field_accesses { value = apply_expr_access_chain(arena, value, field_accesses);
// Wrap the previous answer in the new one, so we end up
// with a nested Expr. That way, `foo.bar.baz` gets represented
// in the AST as if it had been written (foo.bar).baz all along.
value = Expr::RecordAccess(arena.alloc(value), field);
}
} }
Loc::at(region, value) Loc::at(region, value)
@ -156,39 +147,17 @@ fn loc_expr_in_parens_etc_help<'a>() -> impl Parser<'a, Loc<Expr<'a>>, EExpr<'a>
) )
} }
fn record_field_access_chain<'a>() -> impl Parser<'a, Vec<'a, &'a str>, EExpr<'a>> { fn record_field_access_chain<'a>() -> impl Parser<'a, Vec<'a, Accessor<'a>>, EExpr<'a>> {
|arena, state: State<'a>, min_indent| match record_field_access().parse( zero_or_more!(skip_first!(
arena,
state.clone(),
min_indent,
) {
Ok((_, initial, state)) => {
let mut accesses = Vec::with_capacity_in(1, arena);
accesses.push(initial);
let mut loop_state = state;
loop {
match record_field_access().parse(arena, loop_state.clone(), min_indent) {
Ok((_, next, state)) => {
accesses.push(next);
loop_state = state;
}
Err((MadeProgress, fail)) => return Err((MadeProgress, fail)),
Err((NoProgress, _)) => return Ok((MadeProgress, accesses, loop_state)),
}
}
}
Err((MadeProgress, fail)) => Err((MadeProgress, fail)),
Err((NoProgress, _)) => Err((NoProgress, EExpr::Access(state.pos()))),
}
}
fn record_field_access<'a>() -> impl Parser<'a, &'a str, EExpr<'a>> {
skip_first!(
word1(b'.', EExpr::Access), word1(b'.', EExpr::Access),
specialize(|_, pos| EExpr::Access(pos), lowercase_ident()) specialize(
) |_, pos| EExpr::Access(pos),
one_of!(
map!(lowercase_ident(), Accessor::RecordField),
map!(integer_ident(), Accessor::TupleIndex),
)
)
))
} }
/// In some contexts we want to parse the `_` as an expression, so it can then be turned into a /// In some contexts we want to parse the `_` as an expression, so it can then be turned into a
@ -2621,13 +2590,13 @@ fn record_literal_help<'a>() -> impl Parser<'a, Expr<'a>, EExpr<'a>> {
and!( and!(
loc!(specialize(EExpr::Record, record_help())), loc!(specialize(EExpr::Record, record_help())),
// there can be field access, e.g. `{ x : 4 }.x` // there can be field access, e.g. `{ x : 4 }.x`
optional(record_field_access_chain()) record_field_access_chain()
), ),
move |arena, state, _, (loc_record, accesses)| { move |arena, state, _, (loc_record, accessors)| {
let (opt_update, loc_assigned_fields_with_comments) = loc_record.value; let (opt_update, loc_assigned_fields_with_comments) = loc_record.value;
// This is a record literal, not a destructure. // This is a record literal, not a destructure.
let mut value = match opt_update { let value = match opt_update {
Some(update) => Expr::RecordUpdate { Some(update) => Expr::RecordUpdate {
update: &*arena.alloc(update), update: &*arena.alloc(update),
fields: Collection::with_items_and_comments( fields: Collection::with_items_and_comments(
@ -2643,20 +2612,26 @@ fn record_literal_help<'a>() -> impl Parser<'a, Expr<'a>, EExpr<'a>> {
)), )),
}; };
if let Some(fields) = accesses { let value = apply_expr_access_chain(arena, value, accessors);
for field in fields {
// Wrap the previous answer in the new one, so we end up
// with a nested Expr. That way, `foo.bar.baz` gets represented
// in the AST as if it had been written (foo.bar).baz all along.
value = Expr::RecordAccess(arena.alloc(value), field);
}
}
Ok((MadeProgress, value, state)) Ok((MadeProgress, value, state))
}, },
) )
} }
fn apply_expr_access_chain<'a>(
arena: &'a Bump,
value: Expr<'a>,
accessors: Vec<'a, Accessor<'a>>,
) -> Expr<'a> {
accessors
.into_iter()
.fold(value, |value, accessor| match accessor {
Accessor::RecordField(field) => Expr::RecordAccess(arena.alloc(value), field),
Accessor::TupleIndex(field) => Expr::TupleAccess(arena.alloc(value), field),
})
}
fn string_literal_help<'a>() -> impl Parser<'a, Expr<'a>, EString<'a>> { fn string_literal_help<'a>() -> impl Parser<'a, Expr<'a>, EString<'a>> {
map!(crate::string_literal::parse(), Expr::Str) map!(crate::string_literal::parse(), Expr::Str)
} }

View file

@ -100,6 +100,17 @@ pub fn lowercase_ident<'a>() -> impl Parser<'a, &'a str, ()> {
} }
} }
/// This is a tuple accessor, e.g. "1" in `.1`
pub fn integer_ident<'a>() -> impl Parser<'a, &'a str, ()> {
move |_, state: State<'a>, _min_indent: u32| match chomp_integer_part(state.bytes()) {
Err(progress) => Err((progress, ())),
Ok(ident) => {
let width = ident.len();
Ok((MadeProgress, ident, state.advance(width)))
}
}
}
pub fn tag_name<'a>() -> impl Parser<'a, &'a str, ()> { pub fn tag_name<'a>() -> impl Parser<'a, &'a str, ()> {
move |arena, state: State<'a>, min_indent: u32| { move |arena, state: State<'a>, min_indent: u32| {
uppercase_ident().parse(arena, state, min_indent) uppercase_ident().parse(arena, state, min_indent)

View file

@ -1789,7 +1789,7 @@ macro_rules! one_or_more {
move |arena, state: State<'a>, min_indent: u32| { move |arena, state: State<'a>, min_indent: u32| {
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
match $parser.parse(arena, state, min_indent) { match $parser.parse(arena, state.clone(), min_indent) {
Ok((_, first_output, next_state)) => { Ok((_, first_output, next_state)) => {
let mut state = next_state; let mut state = next_state;
let mut buf = Vec::with_capacity_in(1, arena); let mut buf = Vec::with_capacity_in(1, arena);
@ -1807,14 +1807,12 @@ macro_rules! one_or_more {
return Ok((MadeProgress, buf, old_state)); return Ok((MadeProgress, buf, old_state));
} }
Err((MadeProgress, fail)) => { Err((MadeProgress, fail)) => {
return Err((MadeProgress, fail, old_state)); return Err((MadeProgress, fail));
} }
} }
} }
} }
Err((progress, _, new_state)) => { Err((progress, _)) => Err((progress, $to_error(state.pos()))),
Err((progress, $to_error(new_state.pos), new_state))
}
} }
} }
}; };

View file

@ -0,0 +1 @@
({ a: 0 }, { b: 1 }).0.a

View file

@ -0,0 +1,32 @@
RecordAccess(
TupleAccess(
Tuple(
[
@1-7 Record(
[
@2-6 RequiredValue(
@2-3 "a",
[],
@5-6 Num(
"0",
),
),
],
),
@9-15 Record(
[
@10-14 RequiredValue(
@10-11 "b",
[],
@13-14 Num(
"1",
),
),
],
),
],
),
"0",
),
"a",
)

View file

@ -0,0 +1 @@
({a: 0}, {b: 1}).0.a

View file

@ -0,0 +1,24 @@
TupleAccess(
RecordAccess(
Record(
[
@2-11 RequiredValue(
@2-3 "a",
[],
@5-11 Tuple(
[
@6-7 Num(
"1",
),
@9-10 Num(
"2",
),
],
),
),
],
),
"a",
),
"0",
)

View file

@ -0,0 +1 @@
{ a: (1, 2) }.a.0

View file

@ -188,6 +188,8 @@ mod test_parse {
pass/lowest_float.expr, pass/lowest_float.expr,
pass/lowest_int.expr, pass/lowest_int.expr,
pass/tuple_type.expr, pass/tuple_type.expr,
pass/tuple_access_after_record.expr,
pass/record_access_after_tuple.expr,
pass/tuple_type_ext.expr, pass/tuple_type_ext.expr,
pass/malformed_ident_due_to_underscore.expr, pass/malformed_ident_due_to_underscore.expr,
pass/malformed_pattern_field_access.expr, // See https://github.com/roc-lang/roc/issues/399 pass/malformed_pattern_field_access.expr, // See https://github.com/roc-lang/roc/issues/399