Implement parsing for tuple accessor functions (.1, .2, etc)

Step 2 of N toward implementing #4465
This commit is contained in:
Joshua Warner 2022-11-08 19:32:14 -05:00
parent 07efb32173
commit f4ce4bf983
No known key found for this signature in database
GPG key ID: 89AD497003F93FDD
19 changed files with 144 additions and 55 deletions

View file

@ -278,7 +278,7 @@ pub fn expr_to_expr2<'a>(
}
}
Access(record_expr, field) => {
RecordAccess(record_expr, field) => {
// TODO
let region = ZERO;
let (record_expr_id, output) = to_expr_id(env, scope, record_expr, region);
@ -295,7 +295,7 @@ pub fn expr_to_expr2<'a>(
)
}
AccessorFunction(field) => (
RecordAccessorFunction(field) => (
Expr2::Accessor {
function_var: env.var_store.fresh(),
record_var: env.var_store.fresh(),

View file

@ -2236,7 +2236,7 @@ fn canonicalize_pending_body<'a>(
ident: defined_symbol,
..
},
ast::Expr::AccessorFunction(field),
ast::Expr::RecordAccessorFunction(field),
) => {
let (loc_can_expr, can_output) = (
Loc::at(

View file

@ -921,7 +921,7 @@ pub fn canonicalize_expr<'a>(
(expr, output)
}
ast::Expr::Access(record_expr, field) => {
ast::Expr::RecordAccess(record_expr, field) => {
let (loc_expr, output) = canonicalize_expr(env, var_store, scope, region, record_expr);
(
@ -935,7 +935,7 @@ pub fn canonicalize_expr<'a>(
output,
)
}
ast::Expr::AccessorFunction(field) => (
ast::Expr::RecordAccessorFunction(field) => (
Accessor(AccessorData {
name: scope.gen_unique_symbol(),
function_var: var_store.fresh(),
@ -947,6 +947,8 @@ pub fn canonicalize_expr<'a>(
}),
Output::default(),
),
ast::Expr::TupleAccess(_record_expr, _field) => todo!("handle TupleAccess"),
ast::Expr::TupleAccessorFunction(_) => todo!("handle TupleAccessorFunction"),
ast::Expr::Tag(tag) => {
let variant_var = var_store.fresh();
let ext_var = var_store.fresh();
@ -2068,7 +2070,7 @@ fn flatten_str_literal<'a>(
pub fn is_valid_interpolation(expr: &ast::Expr<'_>) -> bool {
match expr {
ast::Expr::Var { .. } => true,
ast::Expr::Access(sub_expr, _) => is_valid_interpolation(sub_expr),
ast::Expr::RecordAccess(sub_expr, _) => is_valid_interpolation(sub_expr),
_ => false,
}
}

View file

@ -120,7 +120,8 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc<Expr<'a>>) -> &'a Loc
| NonBase10Int { .. }
| Str(_)
| SingleQuote(_)
| AccessorFunction(_)
| RecordAccessorFunction(_)
| TupleAccessorFunction(_)
| Var { .. }
| Underscore { .. }
| MalformedIdent(_, _)
@ -129,13 +130,14 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc<Expr<'a>>) -> &'a Loc
| Tag(_)
| OpaqueRef(_) => loc_expr,
Access(sub_expr, paths) => {
TupleAccess(_sub_expr, _paths) => todo!("Handle TupleAccess"),
RecordAccess(sub_expr, paths) => {
let region = loc_expr.region;
let loc_sub_expr = Loc {
region,
value: **sub_expr,
};
let value = Access(&desugar_expr(arena, arena.alloc(loc_sub_expr)).value, paths);
let value = RecordAccess(&desugar_expr(arena, arena.alloc(loc_sub_expr)).value, paths);
arena.alloc(Loc { region, value })
}

View file

@ -34,8 +34,10 @@ impl<'a> Formattable for Expr<'a> {
| Num(..)
| NonBase10Int { .. }
| SingleQuote(_)
| Access(_, _)
| AccessorFunction(_)
| RecordAccess(_, _)
| RecordAccessorFunction(_)
| TupleAccess(_, _)
| TupleAccessorFunction(_)
| Var { .. }
| Underscore { .. }
| MalformedIdent(_, _)
@ -399,12 +401,22 @@ impl<'a> Formattable for Expr<'a> {
sub_expr.format_with_options(buf, Parens::InApply, newlines, indent);
}
AccessorFunction(key) => {
RecordAccessorFunction(key) => {
buf.indent(indent);
buf.push('.');
buf.push_str(key);
}
Access(expr, key) => {
RecordAccess(expr, key) => {
expr.format_with_options(buf, Parens::InApply, Newlines::Yes, indent);
buf.push('.');
buf.push_str(key);
}
TupleAccessorFunction(key) => {
buf.indent(indent);
buf.push('.');
buf.push_str(key);
}
TupleAccess(expr, key) => {
expr.format_with_options(buf, Parens::InApply, Newlines::Yes, indent);
buf.push('.');
buf.push_str(key);

View file

@ -640,8 +640,10 @@ impl<'a> RemoveSpaces<'a> for Expr<'a> {
is_negative,
},
Expr::Str(a) => Expr::Str(a.remove_spaces(arena)),
Expr::Access(a, b) => Expr::Access(arena.alloc(a.remove_spaces(arena)), b),
Expr::AccessorFunction(a) => Expr::AccessorFunction(a),
Expr::RecordAccess(a, b) => Expr::RecordAccess(arena.alloc(a.remove_spaces(arena)), b),
Expr::RecordAccessorFunction(a) => Expr::RecordAccessorFunction(a),
Expr::TupleAccess(a, b) => Expr::TupleAccess(arena.alloc(a.remove_spaces(arena)), b),
Expr::TupleAccessorFunction(a) => Expr::TupleAccessorFunction(a),
Expr::List(a) => Expr::List(a.remove_spaces(arena)),
Expr::RecordUpdate { update, fields } => Expr::RecordUpdate {
update: arena.alloc(update.remove_spaces(arena)),

View file

@ -163,13 +163,19 @@ pub enum Expr<'a> {
// String Literals
Str(StrLiteral<'a>), // string without escapes in it
/// Look up exactly one field on a record, e.g. (expr).foo.
Access(&'a Expr<'a>, &'a str),
/// e.g. `.foo`
AccessorFunction(&'a str),
/// eg 'b'
SingleQuote(&'a str),
/// Look up exactly one field on a record, e.g. `x.foo`.
RecordAccess(&'a Expr<'a>, &'a str),
/// e.g. `.foo`
RecordAccessorFunction(&'a str),
/// Look up exactly one field on a tuple, e.g. `(x, y).1`.
TupleAccess(&'a Expr<'a>, &'a str),
/// e.g. `.1`
TupleAccessorFunction(&'a str),
// Collection Literals
List(Collection<'a, &'a Loc<Expr<'a>>>),
@ -731,7 +737,8 @@ impl<'a> Pattern<'a> {
Pattern::Malformed(buf.into_bump_str())
}
}
Ident::AccessorFunction(string) => Pattern::Malformed(string),
Ident::RecordAccessorFunction(string) => Pattern::Malformed(string),
Ident::TupleAccessorFunction(string) => Pattern::Malformed(string),
Ident::Malformed(string, _problem) => Pattern::Malformed(string),
}
}

View file

@ -147,7 +147,7 @@ fn loc_expr_in_parens_etc_help<'a>() -> impl Parser<'a, Loc<Expr<'a>>, EExpr<'a>
// 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);
value = Expr::RecordAccess(arena.alloc(value), field);
}
}
@ -1868,8 +1868,10 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
is_negative: *is_negative,
}),
// These would not have parsed as patterns
Expr::AccessorFunction(_)
| Expr::Access(_, _)
Expr::RecordAccessorFunction(_)
| Expr::RecordAccess(_, _)
| Expr::TupleAccessorFunction(_)
| Expr::TupleAccess(_, _)
| Expr::List { .. }
| Expr::Closure(_, _)
| Expr::Backpassing(_, _, _)
@ -2417,12 +2419,13 @@ fn ident_to_expr<'a>(arena: &'a Bump, src: Ident<'a>) -> Expr<'a> {
// 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.
answer = Expr::Access(arena.alloc(answer), field);
answer = Expr::RecordAccess(arena.alloc(answer), field);
}
answer
}
Ident::AccessorFunction(string) => Expr::AccessorFunction(string),
Ident::RecordAccessorFunction(string) => Expr::RecordAccessorFunction(string),
Ident::TupleAccessorFunction(string) => Expr::TupleAccessorFunction(string),
Ident::Malformed(string, problem) => Expr::MalformedIdent(string, problem),
}
}
@ -2595,7 +2598,7 @@ fn record_literal_help<'a>() -> impl Parser<'a, Expr<'a>, EExpr<'a>> {
// 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);
value = Expr::RecordAccess(arena.alloc(value), field);
}
}

View file

@ -44,7 +44,9 @@ pub enum Ident<'a> {
parts: &'a [&'a str],
},
/// .foo { foo: 42 }
AccessorFunction(&'a str),
RecordAccessorFunction(&'a str),
/// .1 (1, 2, 3)
TupleAccessorFunction(&'a str),
/// .Foo or foo. or something like foo.Bar
Malformed(&'a str, BadIdent),
}
@ -69,7 +71,8 @@ impl<'a> Ident<'a> {
len - 1
}
AccessorFunction(string) => string.len(),
RecordAccessorFunction(string) => string.len(),
TupleAccessorFunction(string) => string.len(),
Malformed(string, _) => string.len(),
}
}
@ -134,10 +137,7 @@ pub fn uppercase_ident<'a>() -> impl Parser<'a, &'a str, ()> {
}
pub fn unqualified_ident<'a>() -> impl Parser<'a, &'a str, ()> {
move |_, state: State<'a>, _min_indent: u32| match chomp_part(
|c| c.is_alphabetic(),
state.bytes(),
) {
move |_, state: State<'a>, _min_indent: u32| match chomp_anycase_part(state.bytes()) {
Err(progress) => Err((progress, (), state)),
Ok(ident) => {
if crate::keyword::KEYWORDS.iter().any(|kw| &ident == kw) {
@ -234,18 +234,35 @@ pub enum BadIdent {
BadOpaqueRef(Position),
}
fn is_alnum(ch: char) -> bool {
ch.is_alphabetic() || ch.is_ascii_digit()
}
fn chomp_lowercase_part(buffer: &[u8]) -> Result<&str, Progress> {
chomp_part(|c: char| c.is_lowercase(), buffer)
chomp_part(char::is_lowercase, is_alnum, buffer)
}
fn chomp_uppercase_part(buffer: &[u8]) -> Result<&str, Progress> {
chomp_part(|c: char| c.is_uppercase(), buffer)
chomp_part(char::is_uppercase, is_alnum, buffer)
}
fn chomp_anycase_part(buffer: &[u8]) -> Result<&str, Progress> {
chomp_part(char::is_alphabetic, is_alnum, buffer)
}
fn chomp_integer_part(buffer: &[u8]) -> Result<&str, Progress> {
chomp_part(
|ch| char::is_ascii_digit(&ch),
|ch| char::is_ascii_digit(&ch),
buffer,
)
}
#[inline(always)]
fn chomp_part<F>(leading_is_good: F, buffer: &[u8]) -> Result<&str, Progress>
fn chomp_part<F, G>(leading_is_good: F, rest_is_good: G, buffer: &[u8]) -> Result<&str, Progress>
where
F: Fn(char) -> bool,
G: Fn(char) -> bool,
{
use encode_unicode::CharExt;
@ -260,7 +277,7 @@ where
}
while let Ok((ch, width)) = char::from_utf8_slice_start(&buffer[chomped..]) {
if ch.is_alphabetic() || ch.is_ascii_digit() {
if rest_is_good(ch) {
chomped += width;
} else {
// we're done
@ -277,8 +294,13 @@ where
}
}
/// a `.foo` accessor function
fn chomp_accessor(buffer: &[u8], pos: Position) -> Result<&str, BadIdent> {
pub enum Accessor<'a> {
RecordField(&'a str),
TupleIndex(&'a str),
}
/// a `.foo` or `.1` accessor function
fn chomp_accessor(buffer: &[u8], pos: Position) -> Result<Accessor, BadIdent> {
// assumes the leading `.` has been chomped already
use encode_unicode::CharExt;
@ -289,12 +311,25 @@ fn chomp_accessor(buffer: &[u8], pos: Position) -> Result<&str, BadIdent> {
if let Ok(('.', _)) = char::from_utf8_slice_start(&buffer[chomped..]) {
Err(BadIdent::WeirdAccessor(pos))
} else {
Ok(name)
Ok(Accessor::RecordField(name))
}
}
Err(_) => {
// we've already made progress with the initial `.`
Err(BadIdent::StrayDot(pos.bump_column(1)))
match chomp_integer_part(buffer) {
Ok(name) => {
let chomped = name.len();
if let Ok(('.', _)) = char::from_utf8_slice_start(&buffer[chomped..]) {
Err(BadIdent::WeirdAccessor(pos))
} else {
Ok(Accessor::TupleIndex(name))
}
}
Err(_) => {
// we've already made progress with the initial `.`
Err(BadIdent::StrayDot(pos.bump_column(1)))
}
}
}
}
}
@ -335,10 +370,13 @@ fn chomp_identifier_chain<'a>(
match char::from_utf8_slice_start(&buffer[chomped..]) {
Ok((ch, width)) => match ch {
'.' => match chomp_accessor(&buffer[1..], pos) {
Ok(accessor) => {
Ok(Accessor::RecordField(accessor)) => {
let bytes_parsed = 1 + accessor.len();
return Ok((bytes_parsed as u32, Ident::AccessorFunction(accessor)));
return Ok((bytes_parsed as u32, Ident::RecordAccessorFunction(accessor)));
}
Ok(Accessor::TupleIndex(accessor)) => {
let bytes_parsed = 1 + accessor.len();
return Ok((bytes_parsed as u32, Ident::TupleAccessorFunction(accessor)));
}
Err(fail) => return Err((1, fail)),
},

View file

@ -340,7 +340,7 @@ fn loc_ident_pattern_help<'a>(
))
}
}
Ident::AccessorFunction(string) => Ok((
Ident::RecordAccessorFunction(string) | Ident::TupleAccessorFunction(string) => Ok((
MadeProgress,
Loc {
region: loc_ident.region,

View file

@ -1,4 +1,4 @@
Access(
RecordAccess(
Var {
module_name: "",
ident: "rec",

View file

@ -1,6 +1,6 @@
Access(
Access(
Access(
RecordAccess(
RecordAccess(
RecordAccess(
Var {
module_name: "",
ident: "rec",

View file

@ -1,4 +1,4 @@
Access(
RecordAccess(
ParensAround(
Var {
module_name: "",

View file

@ -1,4 +1,4 @@
Access(
RecordAccess(
ParensAround(
Var {
module_name: "One.Two",

View file

@ -1,6 +1,6 @@
Access(
Access(
Access(
RecordAccess(
RecordAccess(
RecordAccess(
Var {
module_name: "One.Two",
ident: "rec",

View file

@ -0,0 +1,21 @@
Apply(
@0-2 TupleAccessorFunction(
"1",
),
[
@3-12 Tuple(
[
@4-5 Num(
"1",
),
@7-8 Num(
"2",
),
@10-11 Num(
"3",
),
],
),
],
Space,
)

View file

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

View file

@ -1,5 +1,5 @@
UnaryOp(
@1-11 Access(
@1-11 RecordAccess(
Var {
module_name: "",
ident: "rec1",

View file

@ -279,6 +279,7 @@ mod test_parse {
pass/underscore_backpassing.expr,
pass/underscore_in_assignment_pattern.expr,
pass/var_else.expr,
pass/tuple_accessor_function.expr,
pass/var_if.expr,
pass/var_is.expr,
pass/var_minus_two.expr,