Implement tuple accessors after records/tuples

This commit is contained in:
Joshua Warner 2022-11-24 15:31:56 -08:00
parent a1432d1a14
commit 56470c838d
No known key found for this signature in database
GPG Key ID: 89AD497003F93FDD
9 changed files with 106 additions and 61 deletions

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_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::parser::{
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!(
loc!(and!(
specialize(EExpr::InParens, loc_expr_in_parens_help()),
one_of![record_field_access_chain(), |a, s, _m| Ok((
NoProgress,
Vec::new_in(a),
s
))]
record_field_access_chain()
)),
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 {
mut region,
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() {
region = loc_expr.region;
} else {
for field in 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);
}
value = apply_expr_access_chain(arena, value, field_accesses);
}
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>> {
|arena, state: State<'a>, min_indent| match record_field_access().parse(
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!(
fn record_field_access_chain<'a>() -> impl Parser<'a, Vec<'a, Accessor<'a>>, EExpr<'a>> {
zero_or_more!(skip_first!(
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
@ -2621,13 +2590,13 @@ fn record_literal_help<'a>() -> impl Parser<'a, Expr<'a>, EExpr<'a>> {
and!(
loc!(specialize(EExpr::Record, record_help())),
// 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;
// This is a record literal, not a destructure.
let mut value = match opt_update {
let value = match opt_update {
Some(update) => Expr::RecordUpdate {
update: &*arena.alloc(update),
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 {
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);
}
}
let value = apply_expr_access_chain(arena, value, accessors);
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>> {
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, ()> {
move |arena, state: State<'a>, min_indent: u32| {
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| {
use bumpalo::collections::Vec;
match $parser.parse(arena, state, min_indent) {
match $parser.parse(arena, state.clone(), min_indent) {
Ok((_, first_output, next_state)) => {
let mut state = next_state;
let mut buf = Vec::with_capacity_in(1, arena);
@ -1807,14 +1807,12 @@ macro_rules! one_or_more {
return Ok((MadeProgress, buf, old_state));
}
Err((MadeProgress, fail)) => {
return Err((MadeProgress, fail, old_state));
return Err((MadeProgress, fail));
}
}
}
}
Err((progress, _, new_state)) => {
Err((progress, $to_error(new_state.pos), new_state))
}
Err((progress, _)) => Err((progress, $to_error(state.pos()))),
}
}
};

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_int.expr,
pass/tuple_type.expr,
pass/tuple_access_after_record.expr,
pass/record_access_after_tuple.expr,
pass/tuple_type_ext.expr,
pass/malformed_ident_due_to_underscore.expr,
pass/malformed_pattern_field_access.expr, // See https://github.com/roc-lang/roc/issues/399