Implement initial tuple syntax

This commit is contained in:
Joshua Warner 2022-11-05 17:39:13 -04:00
parent 9abed3472a
commit 1753c9cf5b
No known key found for this signature in database
GPG key ID: 89AD497003F93FDD
11 changed files with 152 additions and 52 deletions

View file

@ -596,6 +596,9 @@ pub fn canonicalize_expr<'a>(
}
}
}
ast::Expr::Tuple(_fields) => {
todo!("canonicalize tuple");
}
ast::Expr::RecordUpdate {
fields,
update: loc_update,

View file

@ -163,7 +163,9 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc<Expr<'a>>) -> &'a Loc
}
})),
}),
Tuple(_fields) => {
todo!("desugar_expr: Tuple");
}
RecordUpdate { fields, update } => {
// NOTE the `update` field is always a `Var { .. }`, we only desugar it to get rid of
// any spaces before/after

View file

@ -106,6 +106,7 @@ impl<'a> Formattable for Expr<'a> {
}
Record(fields) => fields.iter().any(|loc_field| loc_field.is_multiline()),
Tuple(fields) => fields.iter().any(|loc_field| loc_field.is_multiline()),
RecordUpdate { fields, .. } => fields.iter().any(|loc_field| loc_field.is_multiline()),
}
}
@ -323,6 +324,9 @@ impl<'a> Formattable for Expr<'a> {
Record(fields) => {
fmt_record(buf, None, *fields, indent);
}
Tuple(_fields) => {
todo!("format tuple");
}
RecordUpdate { update, fields } => {
fmt_record(buf, Some(*update), *fields, indent);
}

View file

@ -648,6 +648,7 @@ impl<'a> RemoveSpaces<'a> for Expr<'a> {
fields: fields.remove_spaces(arena),
},
Expr::Record(a) => Expr::Record(a.remove_spaces(arena)),
Expr::Tuple(a) => Expr::Tuple(a.remove_spaces(arena)),
Expr::Var { module_name, ident } => Expr::Var { module_name, ident },
Expr::Underscore(a) => Expr::Underscore(a),
Expr::Tag(a) => Expr::Tag(a),

View file

@ -180,6 +180,8 @@ pub enum Expr<'a> {
Record(Collection<'a, Loc<AssignedField<'a, Expr<'a>>>>),
Tuple(Collection<'a, Loc<Expr<'a>>>),
// Lookups
Var {
module_name: &'a str, // module_name will only be filled if the original Roc code stated something like `5 + SomeModule.myVar`, module_name will be blank if it was `5 + myVar`

View file

@ -12,7 +12,7 @@ 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, trailing_sep_by0, word1,
word1_indent, word2, EClosure, EExpect, EExpr, EIf, EInParens, EList, ENumber, EPattern,
ERecord, EString, EType, EWhen, Either, ParseResult, Parser,
ERecord, EString, ETuple, EType, EWhen, Either, ParseResult, Parser,
};
use crate::pattern::{loc_closure_param, loc_has_parser};
use crate::state::State;
@ -81,72 +81,79 @@ pub fn expr_help<'a>() -> impl Parser<'a, Expr<'a>, EExpr<'a>> {
}
fn loc_expr_in_parens_help<'a>() -> impl Parser<'a, Loc<Expr<'a>>, EInParens<'a>> {
move |arena, state, min_indent| {
let (_, loc_expr, state) =
loc_expr_in_parens_help_help().parse(arena, state, min_indent)?;
Ok((
MadeProgress,
Loc {
region: loc_expr.region,
value: Expr::ParensAround(arena.alloc(loc_expr.value)),
},
state,
))
}
}
fn loc_expr_in_parens_help_help<'a>() -> impl Parser<'a, Loc<Expr<'a>>, EInParens<'a>> {
between!(
word1(b'(', EInParens::Open),
space0_around_ee(
specialize_ref(EInParens::Expr, loc_expr()),
EInParens::IndentOpen,
then(
loc!(collection_trailing_sep_e!(
word1(b'(', EInParens::Open),
specialize_ref(EInParens::Expr, loc_expr_no_multi_backpassing()),
word1(b',', EInParens::End),
word1(b')', EInParens::End),
EInParens::Open,
EInParens::IndentEnd,
),
word1(b')', EInParens::End)
Expr::SpaceBefore
)),
move |arena, state, _, loc_elements| {
let elements = loc_elements.value;
let region = loc_elements.region;
if elements.len() > 1 {
Ok((
MadeProgress,
Loc::at(region, Expr::List(elements.ptrify_items(arena))),
state,
))
} else if elements.is_empty() {
Err((NoProgress, EInParens::Empty(state.pos()), state))
} else {
// TODO: don't discard comments before/after
// (stored in the Collection)
Ok((
MadeProgress,
Loc::at(
elements.items[0].region,
Expr::ParensAround(&elements.items[0].value),
),
state,
))
}
},
)
.trace("in_parens")
}
fn loc_expr_in_parens_etc_help<'a>() -> impl Parser<'a, Loc<Expr<'a>>, EExpr<'a>> {
move |arena, state: State<'a>, min_indent: u32| {
let parser = loc!(and!(
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
))]
));
let (
_,
Loc {
)),
move |arena: &'a Bump, value: Loc<(Loc<Expr<'a>>, Vec<'a, &'a str>)>| {
let Loc {
mut region,
value: (loc_expr, field_accesses),
},
state,
) = parser.parse(arena, state, min_indent)?;
} = value;
let mut value = loc_expr.value;
let mut value = loc_expr.value;
// if there are field accesses, include the parentheses in the region
// otherwise, don't include the parentheses
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::Access(arena.alloc(value), field);
// if there are field accesses, include the parentheses in the region
// otherwise, don't include the parentheses
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::Access(arena.alloc(value), field);
}
}
Loc::at(region, value)
}
let loc_expr = Loc::at(region, value);
Ok((MadeProgress, loc_expr, state))
}
)
}
fn record_field_access_chain<'a>() -> impl Parser<'a, Vec<'a, &'a str>, EExpr<'a>> {
@ -1845,6 +1852,10 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
Ok(Pattern::RecordDestructure(patterns))
}
Expr::Tuple(_fields) => {
todo!("tuple patterns")
}
&Expr::Float(string) => Ok(Pattern::FloatLiteral(string)),
&Expr::Num(string) => Ok(Pattern::NumLiteral(string)),
Expr::NonBase10Int {
@ -2435,6 +2446,13 @@ fn list_literal_help<'a>() -> impl Parser<'a, Expr<'a>, EList<'a>> {
.trace("list_literal")
}
pub fn tuple_value_field<'a>() -> impl Parser<'a, Loc<Expr<'a>>, ETuple<'a>> {
space0_before_e(
specialize_ref(ETuple::Expr, loc_expr_no_multi_backpassing()),
ETuple::IndentEnd,
)
}
pub fn record_value_field<'a>() -> impl Parser<'a, AssignedField<'a, Expr<'a>>, ERecord<'a>> {
use AssignedField::*;

View file

@ -108,7 +108,8 @@ impl_space_problem! {
EAbility<'a>,
PInParens<'a>,
PRecord<'a>,
PList<'a>
PList<'a>,
ETuple<'a>
}
#[derive(Debug, Clone, PartialEq, Eq)]
@ -360,6 +361,7 @@ pub enum EExpr<'a> {
InParens(EInParens<'a>, Position),
Record(ERecord<'a>, Position),
Tuple(ETuple<'a>, Position),
Str(EString<'a>, Position),
SingleQuote(EString<'a>, Position),
Number(ENumber, Position),
@ -414,10 +416,43 @@ pub enum ERecord<'a> {
IndentEnd(Position),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ETuple<'a> {
// Empty tuples are not allowed
Empty(Position),
// Single element tuples are not allowed
Single(Position),
End(Position),
Open(Position),
Updateable(Position),
Field(Position),
Colon(Position),
QuestionMark(Position),
Bar(Position),
Ampersand(Position),
Expr(&'a EExpr<'a>, Position),
Space(BadInputError, Position),
IndentOpen(Position),
IndentColon(Position),
IndentBar(Position),
IndentAmpersand(Position),
IndentEnd(Position),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EInParens<'a> {
End(Position),
Open(Position),
/// Empty parens, e.g. () is not allowed
Empty(Position),
///
Expr(&'a EExpr<'a>, Position),

View file

@ -0,0 +1,13 @@
List(
[
@1-2 Num(
"1",
),
@4-5 Num(
"2",
),
@7-8 Num(
"3",
),
],
)

View file

@ -0,0 +1 @@
(1, 2, 3)

View file

@ -303,6 +303,7 @@ mod test_parse {
pass/where_clause_on_newline.expr,
pass/zero_float.expr,
pass/zero_int.expr,
pass/basic_tuple.expr,
}
fn snapshot_test(