mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-04 20:28:02 +00:00
Record Builder parsing
This commit is contained in:
parent
8009b524b4
commit
735721769c
3 changed files with 130 additions and 7 deletions
|
@ -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::*;
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue