Parse builders without backtracking from records

This commit is contained in:
Agustin Zubiaga 2023-05-13 20:05:42 -03:00
parent c3bcb88c5b
commit f94004d137
5 changed files with 293 additions and 99 deletions

View file

@ -11,8 +11,8 @@ use crate::keyword;
use crate::parser::{
self, backtrackable, increment_min_indent, line_min_indent, optional, reset_min_indent,
sep_by1, sep_by1_e, set_min_indent, specialize, specialize_ref, then, word1, word1_indent,
word2, EClosure, EExpect, EExpr, EIf, EInParens, EList, ENumber, EPattern, ERecord,
ERecordBuilder, EString, EType, EWhen, Either, ParseResult, Parser,
word2, EClosure, EExpect, EExpr, EIf, EInParens, EList, ENumber, EPattern, ERecord, EString,
EType, EWhen, Either, ParseResult, Parser,
};
use crate::pattern::{closure_param, loc_has_parser};
use crate::state::State;
@ -168,8 +168,7 @@ fn loc_term_or_underscore_or_conditional<'a>(
loc!(specialize(EExpr::Closure, closure_help(options))),
loc!(crash_kw()),
loc!(underscore_expression()),
loc!(backtrackable(record_literal_help())),
loc!(specialize(EExpr::RecordBuilder, record_builder())),
loc!(record_literal_help()),
loc!(specialize(EExpr::List, list_literal_help())),
loc!(map_with_arena!(
assign_or_destructure_identifier(),
@ -189,8 +188,7 @@ fn loc_term_or_underscore<'a>(
loc!(specialize(EExpr::Number, positive_number_literal_help())),
loc!(specialize(EExpr::Closure, closure_help(options))),
loc!(underscore_expression()),
loc!(backtrackable(record_literal_help())),
loc!(specialize(EExpr::RecordBuilder, record_builder())),
loc!(record_literal_help()),
loc!(specialize(EExpr::List, list_literal_help())),
loc!(map_with_arena!(
assign_or_destructure_identifier(),
@ -205,8 +203,7 @@ fn loc_term<'a>(options: ExprParseOptions) -> impl Parser<'a, Loc<Expr<'a>>, EEx
loc!(specialize(EExpr::Str, string_like_literal_help())),
loc!(specialize(EExpr::Number, positive_number_literal_help())),
loc!(specialize(EExpr::Closure, closure_help(options))),
loc!(backtrackable(record_literal_help())),
loc!(specialize(EExpr::RecordBuilder, record_builder())),
loc!(record_literal_help()),
loc!(specialize(EExpr::List, list_literal_help())),
loc!(map_with_arena!(
assign_or_destructure_identifier(),
@ -2535,8 +2532,111 @@ fn list_literal_help<'a>() -> impl Parser<'a, Expr<'a>, EList<'a>> {
.trace("list_literal")
}
pub fn record_value_field<'a>() -> impl Parser<'a, AssignedField<'a, Expr<'a>>, ERecord<'a>> {
use AssignedField::*;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum RecordField<'a> {
RequiredValue(Loc<&'a str>, &'a [CommentOrNewline<'a>], &'a Loc<Expr<'a>>),
OptionalValue(Loc<&'a str>, &'a [CommentOrNewline<'a>], &'a Loc<Expr<'a>>),
LabelOnly(Loc<&'a str>),
SpaceBefore(&'a RecordField<'a>, &'a [CommentOrNewline<'a>]),
SpaceAfter(&'a RecordField<'a>, &'a [CommentOrNewline<'a>]),
ApplyValue(Loc<&'a str>, &'a [CommentOrNewline<'a>], &'a Loc<Expr<'a>>),
}
impl<'a> RecordField<'a> {
fn is_apply_value(&self) -> bool {
let mut current = self;
loop {
match current {
RecordField::ApplyValue(_, _, _) => break true,
RecordField::SpaceBefore(field, _) | RecordField::SpaceAfter(field, _) => {
current = *field;
}
_ => break false,
}
}
}
pub fn to_assigned_field(&self, arena: &'a Bump) -> Result<AssignedField<'a, Expr<'a>>, ()> {
use AssignedField::*;
match self {
RecordField::RequiredValue(loc_label, spaces, loc_expr) => {
Ok(RequiredValue(*loc_label, spaces, loc_expr))
}
RecordField::OptionalValue(loc_label, spaces, loc_expr) => {
Ok(OptionalValue(*loc_label, spaces, loc_expr))
}
RecordField::LabelOnly(loc_label) => Ok(LabelOnly(*loc_label)),
RecordField::ApplyValue(_, _, _) => Err(()),
RecordField::SpaceBefore(field, spaces) => {
let assigned_field = field.to_assigned_field(arena)?;
Ok(SpaceBefore(arena.alloc(assigned_field), spaces))
}
RecordField::SpaceAfter(field, spaces) => {
let assigned_field = field.to_assigned_field(arena)?;
Ok(SpaceAfter(arena.alloc(assigned_field), spaces))
}
}
}
fn to_builder_field(&self, arena: &'a Bump) -> Result<RecordBuilderField<'a>, ()> {
use RecordBuilderField::*;
match self {
RecordField::RequiredValue(loc_label, spaces, loc_expr) => {
Ok(Value(*loc_label, spaces, loc_expr))
}
RecordField::OptionalValue(_, _, _) => Err(()),
RecordField::LabelOnly(loc_label) => Ok(LabelOnly(*loc_label)),
RecordField::ApplyValue(loc_label, spaces, loc_expr) => {
Ok(ApplyValue(*loc_label, spaces, loc_expr))
}
RecordField::SpaceBefore(field, spaces) => {
let builder_field = field.to_builder_field(arena)?;
Ok(SpaceBefore(arena.alloc(builder_field), spaces))
}
RecordField::SpaceAfter(field, spaces) => {
let builder_field = field.to_builder_field(arena)?;
Ok(SpaceAfter(arena.alloc(builder_field), spaces))
}
}
}
}
impl<'a> Spaceable<'a> for RecordField<'a> {
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
RecordField::SpaceBefore(self, spaces)
}
fn after(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
RecordField::SpaceAfter(self, spaces)
}
}
pub fn record_field<'a>() -> impl Parser<'a, RecordField<'a>, ERecord<'a>> {
use RecordField::*;
enum AssignKind {
Required,
Optional,
Apply,
}
use AssignKind::*;
map_with_arena!(
and!(
@ -2544,9 +2644,10 @@ pub fn record_value_field<'a>() -> impl Parser<'a, AssignedField<'a, Expr<'a>>,
and!(
spaces(),
optional(and!(
either!(
word1(b':', ERecord::Colon),
word1(b'?', ERecord::QuestionMark)
one_of!(
map!(word1(b':', ERecord::Colon), |_| Required),
map!(word1(b'?', ERecord::QuestionMark), |_| Optional),
map!(word2(b'<', b'-', ERecord::Arrow), |_| Apply),
),
spaces_before(specialize_ref(ERecord::Expr, loc_expr(false)))
))
@ -2554,13 +2655,11 @@ pub fn record_value_field<'a>() -> impl Parser<'a, AssignedField<'a, Expr<'a>>,
),
|arena: &'a bumpalo::Bump, (loc_label, (spaces, opt_loc_val))| {
match opt_loc_val {
Some((Either::First(_), loc_val)) => {
RequiredValue(loc_label, spaces, arena.alloc(loc_val))
}
Some((Required, loc_val)) => RequiredValue(loc_label, spaces, arena.alloc(loc_val)),
Some((Either::Second(_), loc_val)) => {
OptionalValue(loc_label, spaces, arena.alloc(loc_val))
}
Some((Optional, loc_val)) => OptionalValue(loc_label, spaces, arena.alloc(loc_val)),
Some((Apply, loc_val)) => ApplyValue(loc_label, spaces, arena.alloc(loc_val)),
// If no value was provided, record it as a Var.
// Canonicalize will know what to do with a Var later.
@ -2585,7 +2684,7 @@ fn record_updateable_identifier<'a>() -> impl Parser<'a, Expr<'a>, ERecord<'a>>
struct RecordHelp<'a> {
update: Option<Loc<Expr<'a>>>,
fields: Collection<'a, Loc<AssignedField<'a, Expr<'a>>>>,
fields: Collection<'a, Loc<RecordField<'a>>>,
}
fn record_help<'a>() -> impl Parser<'a, RecordHelp<'a>, ERecord<'a>> {
@ -2605,9 +2704,9 @@ fn record_help<'a>() -> impl Parser<'a, RecordHelp<'a>, ERecord<'a>> {
word1(b'&', ERecord::Ampersand)
))),
fields: collection_inner!(
loc!(record_value_field()),
loc!(record_field()),
word1(b',', ERecord::End),
AssignedField::SpaceBefore
RecordField::SpaceBefore
),
})),
word1(b'}', ERecord::End)
@ -2622,74 +2721,74 @@ fn record_literal_help<'a>() -> impl Parser<'a, Expr<'a>, EExpr<'a>> {
record_field_access_chain()
),
move |arena, state, _, (record, accessors)| {
// This is a record literal, not a destructure.
let value = match record.update {
Some(update) => Expr::RecordUpdate {
update: &*arena.alloc(update),
fields: record.fields,
},
None => Expr::Record(record.fields),
let expr_result = match record.update {
Some(update) => record_update_help(arena, update, record.fields),
None => {
let is_record_builder = record
.fields
.iter()
.any(|field| field.value.is_apply_value());
if is_record_builder {
record_builder_help(arena, record.fields)
} else {
let fields = record.fields.map_items(arena, |loc_field| {
loc_field.map(|field| field.to_assigned_field(arena).unwrap())
});
Ok(Expr::Record(fields))
}
}
};
let value = apply_expr_access_chain(arena, value, accessors);
match expr_result {
Ok(expr) => {
let value = apply_expr_access_chain(arena, expr, accessors);
Ok((MadeProgress, value, state))
Ok((MadeProgress, value, state))
}
Err(err) => Err((MadeProgress, err)),
}
},
)
}
fn record_builder<'a>() -> impl Parser<'a, Expr<'a>, ERecordBuilder<'a>> {
map!(
between!(
word1(b'{', ERecordBuilder::Open),
reset_min_indent(collection_inner!(
loc!(record_builder_field()),
word1(b',', ERecordBuilder::End),
RecordBuilderField::SpaceBefore
),),
word1(b'}', ERecordBuilder::End)
),
Expr::RecordBuilder
)
fn record_update_help<'a>(
arena: &'a Bump,
update: Loc<Expr<'a>>,
fields: Collection<'a, Loc<RecordField<'a>>>,
) -> Result<Expr<'a>, EExpr<'a>> {
let result = fields.map_items_result(arena, |loc_field| {
match loc_field.value.to_assigned_field(arena) {
Ok(builder_field) => Ok(Loc {
region: loc_field.region,
value: builder_field,
}),
Err(()) => Err(EExpr::RecordUpdateBuilder(loc_field.region)),
}
});
result.map(|fields| Expr::RecordUpdate {
update: &*arena.alloc(update),
fields,
})
}
pub fn record_builder_field<'a>() -> impl Parser<'a, RecordBuilderField<'a>, ERecordBuilder<'a>> {
use RecordBuilderField::*;
map_with_arena!(
and!(
specialize(|_, pos| ERecordBuilder::Field(pos), loc!(lowercase_ident())),
and!(
spaces(),
optional(and!(
either!(
word1(b':', ERecordBuilder::Colon),
word2(b'<', b'-', ERecordBuilder::Arrow)
),
spaces_before(specialize_ref(ERecordBuilder::Expr, loc_expr(false)))
))
)
),
|arena: &'a bumpalo::Bump, (loc_label, (spaces, opt_loc_val))| {
match opt_loc_val {
Some((Either::First(_), loc_val)) => Value(loc_label, spaces, arena.alloc(loc_val)),
Some((Either::Second(_), loc_val)) => {
ApplyValue(loc_label, spaces, arena.alloc(loc_val))
}
// If no value was provided, record it as a Var.
// Canonicalize will know what to do with a Var later.
None => {
if !spaces.is_empty() {
SpaceAfter(arena.alloc(LabelOnly(loc_label)), spaces)
} else {
LabelOnly(loc_label)
}
}
}
fn record_builder_help<'a>(
arena: &'a Bump,
fields: Collection<'a, Loc<RecordField<'a>>>,
) -> Result<Expr<'a>, EExpr<'a>> {
let result = fields.map_items_result(arena, |loc_field| {
match loc_field.value.to_builder_field(arena) {
Ok(builder_field) => Ok(Loc {
region: loc_field.region,
value: builder_field,
}),
Err(()) => Err(EExpr::OptionalValueInRecordBuilder(loc_field.region)),
}
)
});
result.map(Expr::RecordBuilder)
}
fn apply_expr_access_chain<'a>(