mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-15 08:15:07 +00:00
Parse builders without backtracking from records
This commit is contained in:
parent
c3bcb88c5b
commit
f94004d137
5 changed files with 293 additions and 99 deletions
|
@ -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>(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue