Record Builder parsing

This commit is contained in:
Agustin Zubiaga 2023-05-05 23:26:20 -03:00
parent 8009b524b4
commit 735721769c
3 changed files with 130 additions and 7 deletions

View file

@ -264,6 +264,9 @@ pub enum Expr<'a> {
Tuple(Collection<'a, &'a Loc<Expr<'a>>>),
// Record Builders
RecordBuilder(Collection<'a, Loc<RecordBuilderField<'a>>>),
// The name of a file to be ingested directly into a variable.
IngestedFile(&'a Path, &'a Loc<TypeAnnotation<'a>>),
@ -684,6 +687,25 @@ pub enum AssignedField<'a, Val> {
Malformed(&'a str),
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum RecordBuilderField<'a> {
// A field with a value, e.g. `{ name: "blah" }`
Value(Loc<&'a str>, &'a [CommentOrNewline<'a>], &'a Loc<Expr<'a>>),
// A field with a function we can apply to build part of the record, e.g. `{ name <- apply getName }`
ApplyValue(Loc<&'a str>, &'a [CommentOrNewline<'a>], &'a Loc<Expr<'a>>),
// A label with no value, e.g. `{ name }` (this is sugar for { name: name })
LabelOnly(Loc<&'a str>),
// We preserve this for the formatter; canonicalization ignores it.
SpaceBefore(&'a RecordBuilderField<'a>, &'a [CommentOrNewline<'a>]),
SpaceAfter(&'a RecordBuilderField<'a>, &'a [CommentOrNewline<'a>]),
/// A malformed assigned field, which will code gen to a runtime error
Malformed(&'a str),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CommentOrNewline<'a> {
Newline,
@ -1198,6 +1220,15 @@ impl<'a, Val> Spaceable<'a> for AssignedField<'a, Val> {
}
}
impl<'a> Spaceable<'a> for RecordBuilderField<'a> {
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
RecordBuilderField::SpaceBefore(self, spaces)
}
fn after(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
RecordBuilderField::SpaceAfter(self, spaces)
}
}
impl<'a> Spaceable<'a> for Tag<'a> {
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
Tag::SpaceBefore(self, spaces)
@ -1483,6 +1514,8 @@ impl<'a> Malformed for Expr<'a> {
Record(items) => items.is_malformed(),
Tuple(items) => items.is_malformed(),
RecordBuilder(items) => items.is_malformed(),
Closure(args, body) => args.iter().any(|arg| arg.is_malformed()) || body.is_malformed(),
Defs(defs, body) => defs.is_malformed() || body.is_malformed(),
Backpassing(args, call, body) => args.iter().any(|arg| arg.is_malformed()) || call.is_malformed() || body.is_malformed(),
@ -1575,6 +1608,20 @@ impl<'a, T: Malformed> Malformed for AssignedField<'a, T> {
}
}
impl<'a> Malformed for RecordBuilderField<'a> {
fn is_malformed(&self) -> bool {
match self {
RecordBuilderField::Value(_, _, expr) | RecordBuilderField::ApplyValue(_, _, expr) => {
expr.is_malformed()
}
RecordBuilderField::LabelOnly(_) => false,
RecordBuilderField::SpaceBefore(field, _)
| RecordBuilderField::SpaceAfter(field, _) => field.is_malformed(),
RecordBuilderField::Malformed(_) => true,
}
}
}
impl<'a> Malformed for Pattern<'a> {
fn is_malformed(&self) -> bool {
use Pattern::*;

View file

@ -1,6 +1,6 @@
use crate::ast::{
AssignedField, Collection, CommentOrNewline, Defs, Expr, ExtractSpaces, Has, HasAbilities,
Pattern, Spaceable, Spaces, TypeAnnotation, TypeDef, TypeHeader, ValueDef,
Pattern, RecordBuilderField, Spaceable, Spaces, TypeAnnotation, TypeDef, TypeHeader, ValueDef,
};
use crate::blankspace::{
space0_after_e, space0_around_e_no_after_indent_check, space0_around_ee, space0_before_e,
@ -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, EString,
EType, EWhen, Either, ParseResult, Parser,
word2, EClosure, EExpect, EExpr, EIf, EInParens, EList, ENumber, EPattern, ERecord,
ERecordBuilder, EString, EType, EWhen, Either, ParseResult, Parser,
};
use crate::pattern::{closure_param, loc_has_parser};
use crate::state::State;
@ -168,7 +168,8 @@ fn loc_term_or_underscore_or_conditional<'a>(
loc!(specialize(EExpr::Closure, closure_help(options))),
loc!(crash_kw()),
loc!(underscore_expression()),
loc!(record_literal_help()),
loc!(backtrackable(record_literal_help())),
loc!(specialize(EExpr::RecordBuilder, record_builder())),
loc!(specialize(EExpr::List, list_literal_help())),
loc!(map_with_arena!(
assign_or_destructure_identifier(),
@ -188,7 +189,8 @@ 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!(record_literal_help()),
loc!(backtrackable(record_literal_help())),
loc!(specialize(EExpr::RecordBuilder, record_builder())),
loc!(specialize(EExpr::List, list_literal_help())),
loc!(map_with_arena!(
assign_or_destructure_identifier(),
@ -203,7 +205,8 @@ 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!(record_literal_help()),
loc!(backtrackable(record_literal_help())),
loc!(specialize(EExpr::RecordBuilder, record_builder())),
loc!(specialize(EExpr::List, list_literal_help())),
loc!(map_with_arena!(
assign_or_destructure_identifier(),
@ -1876,7 +1879,10 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
pattern
}
Expr::SpaceBefore(..) | Expr::SpaceAfter(..) | Expr::ParensAround(..) => unreachable!(),
Expr::SpaceBefore(..)
| Expr::SpaceAfter(..)
| Expr::ParensAround(..)
| Expr::RecordBuilder(..) => unreachable!(),
Expr::Record(fields) => {
let patterns = fields.map_items_result(arena, |loc_assigned_field| {
@ -2630,6 +2636,60 @@ fn record_literal_help<'a>() -> impl Parser<'a, Expr<'a>, EExpr<'a>> {
)
}
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
)
}
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 apply_expr_access_chain<'a>(
arena: &'a Bump,
value: Expr<'a>,

View file

@ -97,6 +97,7 @@ impl_space_problem! {
EPattern<'a>,
EProvides<'a>,
ERecord<'a>,
ERecordBuilder<'a>,
ERequires<'a>,
EString<'a>,
EType<'a>,
@ -358,6 +359,7 @@ pub enum EExpr<'a> {
InParens(EInParens<'a>, Position),
Record(ERecord<'a>, Position),
RecordBuilder(ERecordBuilder<'a>, Position),
// SingleQuote errors are folded into the EString
Str(EString<'a>, Position),
@ -418,6 +420,20 @@ pub enum ERecord<'a> {
Space(BadInputError, Position),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ERecordBuilder<'a> {
End(Position),
Open(Position),
Field(Position),
Colon(Position),
Arrow(Position),
Expr(&'a EExpr<'a>, Position),
Space(BadInputError, Position),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EInParens<'a> {
End(Position),