mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-27 05:49:08 +00:00
Merge pull request #4591 from roc-lang/tuple-accessor
Tuple accessors after tuples/records
This commit is contained in:
commit
f52ad5cbba
10 changed files with 108 additions and 63 deletions
|
@ -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>>>,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
({ a: 0 }, { b: 1 }).0.a
|
|
@ -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",
|
||||||
|
)
|
|
@ -0,0 +1 @@
|
||||||
|
({a: 0}, {b: 1}).0.a
|
|
@ -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",
|
||||||
|
)
|
|
@ -0,0 +1 @@
|
||||||
|
{ a: (1, 2) }.a.0
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue