mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-13 07:16:18 +00:00
Fix parsing of tuple accessors after an identifier - e.g. myIdent.2
This commit is contained in:
parent
53b1525139
commit
31a4eb2bfd
8 changed files with 150 additions and 59 deletions
|
@ -2429,7 +2429,13 @@ fn ident_to_expr<'a>(arena: &'a Bump, src: Ident<'a>) -> Expr<'a> {
|
|||
// The first value in the iterator is the variable name,
|
||||
// e.g. `foo` in `foo.bar.baz`
|
||||
let mut answer = match iter.next() {
|
||||
Some(ident) => Expr::Var { module_name, ident },
|
||||
Some(Accessor::RecordField(ident)) => Expr::Var { module_name, ident },
|
||||
Some(Accessor::TupleIndex(_)) => {
|
||||
// TODO: make this state impossible to represent in Ident::Access,
|
||||
// by splitting out parts[0] into a separate field with a type of `&'a str`,
|
||||
// rather than a `&'a [Accessor<'a>]`.
|
||||
panic!("Parsed an Ident::Access with a first part of a tuple index");
|
||||
}
|
||||
None => {
|
||||
panic!("Parsed an Ident::Access with no parts");
|
||||
}
|
||||
|
@ -2441,7 +2447,14 @@ 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::RecordAccess(arena.alloc(answer), field);
|
||||
match field {
|
||||
Accessor::RecordField(field) => {
|
||||
answer = Expr::RecordAccess(arena.alloc(answer), field);
|
||||
}
|
||||
Accessor::TupleIndex(index) => {
|
||||
answer = Expr::TupleAccess(arena.alloc(answer), index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
answer
|
||||
|
|
|
@ -41,7 +41,7 @@ pub enum Ident<'a> {
|
|||
/// foo or foo.bar or Foo.Bar.baz.qux
|
||||
Access {
|
||||
module_name: &'a str,
|
||||
parts: &'a [&'a str],
|
||||
parts: &'a [Accessor<'a>],
|
||||
},
|
||||
/// .foo { foo: 42 }
|
||||
RecordAccessorFunction(&'a str),
|
||||
|
@ -197,7 +197,7 @@ pub fn parse_ident<'a>(
|
|||
if module_name.is_empty() {
|
||||
if let Some(first) = parts.first() {
|
||||
for keyword in crate::keyword::KEYWORDS.iter() {
|
||||
if first == keyword {
|
||||
if first == &Accessor::RecordField(keyword) {
|
||||
return Err((NoProgress, EExpr::Start(initial.pos())));
|
||||
}
|
||||
}
|
||||
|
@ -339,11 +339,32 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Accessor<'a> {
|
||||
RecordField(&'a str),
|
||||
TupleIndex(&'a str),
|
||||
}
|
||||
|
||||
impl<'a> Accessor<'a> {
|
||||
pub fn len(&self) -> usize {
|
||||
match self {
|
||||
Accessor::RecordField(name) => name.len(),
|
||||
Accessor::TupleIndex(name) => name.len(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() > 0
|
||||
}
|
||||
|
||||
pub fn as_inner(&self) -> &'a str {
|
||||
match self {
|
||||
Accessor::RecordField(name) => name,
|
||||
Accessor::TupleIndex(name) => name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// a `.foo` or `.1` accessor function
|
||||
fn chomp_accessor(buffer: &[u8], pos: Position) -> Result<Accessor, BadIdent> {
|
||||
// assumes the leading `.` has been chomped already
|
||||
|
@ -474,7 +495,7 @@ fn chomp_identifier_chain<'a>(
|
|||
|
||||
if !first_is_uppercase {
|
||||
let first_part = unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) };
|
||||
parts.push(first_part);
|
||||
parts.push(Accessor::RecordField(first_part));
|
||||
}
|
||||
|
||||
match chomp_access_chain(&buffer[chomped..], &mut parts) {
|
||||
|
@ -518,7 +539,7 @@ fn chomp_identifier_chain<'a>(
|
|||
let value = unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) };
|
||||
let ident = Ident::Access {
|
||||
module_name: "",
|
||||
parts: arena.alloc([value]),
|
||||
parts: arena.alloc([Accessor::RecordField(value)]),
|
||||
};
|
||||
Ok((chomped as u32, ident))
|
||||
}
|
||||
|
@ -591,7 +612,7 @@ fn chomp_concrete_type(buffer: &[u8]) -> Result<(&str, &str, usize), Progress> {
|
|||
}
|
||||
}
|
||||
|
||||
fn chomp_access_chain<'a>(buffer: &'a [u8], parts: &mut Vec<'a, &'a str>) -> Result<u32, u32> {
|
||||
fn chomp_access_chain<'a>(buffer: &'a [u8], parts: &mut Vec<'a, Accessor<'a>>) -> Result<u32, u32> {
|
||||
let mut chomped = 0;
|
||||
|
||||
while let Some(b'.') = buffer.get(chomped) {
|
||||
|
@ -603,11 +624,23 @@ fn chomp_access_chain<'a>(buffer: &'a [u8], parts: &mut Vec<'a, &'a str>) -> Res
|
|||
&buffer[chomped + 1..chomped + 1 + name.len()],
|
||||
)
|
||||
};
|
||||
parts.push(value);
|
||||
parts.push(Accessor::RecordField(value));
|
||||
|
||||
chomped += name.len() + 1;
|
||||
}
|
||||
Err(_) => return Err(chomped as u32 + 1),
|
||||
Err(_) => match chomp_integer_part(slice) {
|
||||
Ok(name) => {
|
||||
let value = unsafe {
|
||||
std::str::from_utf8_unchecked(
|
||||
&buffer[chomped + 1..chomped + 1 + name.len()],
|
||||
)
|
||||
};
|
||||
parts.push(Accessor::TupleIndex(value));
|
||||
|
||||
chomped += name.len() + 1;
|
||||
}
|
||||
Err(_) => return Err(chomped as u32 + 1),
|
||||
},
|
||||
},
|
||||
None => return Err(chomped as u32 + 1),
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::ast::{Has, Pattern, PatternAs, Spaceable};
|
||||
use crate::blankspace::{space0_e, spaces, spaces_before};
|
||||
use crate::ident::{lowercase_ident, parse_ident, Ident};
|
||||
use crate::ident::{lowercase_ident, parse_ident, Accessor, Ident};
|
||||
use crate::keyword;
|
||||
use crate::parser::Progress::{self, *};
|
||||
use crate::parser::{
|
||||
|
@ -377,34 +377,45 @@ fn loc_ident_pattern_help<'a>(
|
|||
Ident::Access { module_name, parts } => {
|
||||
// Plain identifiers (e.g. `foo`) are allowed in patterns, but
|
||||
// more complex ones (e.g. `Foo.bar` or `foo.bar.baz`) are not.
|
||||
if crate::keyword::KEYWORDS.contains(&parts[0]) {
|
||||
Err((NoProgress, EPattern::End(original_state.pos())))
|
||||
} else if module_name.is_empty() && parts.len() == 1 {
|
||||
Ok((
|
||||
MadeProgress,
|
||||
Loc {
|
||||
region: loc_ident.region,
|
||||
value: Pattern::Identifier(parts[0]),
|
||||
},
|
||||
state,
|
||||
))
|
||||
} else {
|
||||
let malformed_str = if module_name.is_empty() {
|
||||
parts.join(".")
|
||||
} else {
|
||||
format!("{}.{}", module_name, parts.join("."))
|
||||
};
|
||||
Ok((
|
||||
MadeProgress,
|
||||
Loc {
|
||||
region: loc_ident.region,
|
||||
value: Pattern::Malformed(
|
||||
String::from_str_in(&malformed_str, arena).into_bump_str(),
|
||||
),
|
||||
},
|
||||
state,
|
||||
))
|
||||
|
||||
for keyword in crate::keyword::KEYWORDS.iter() {
|
||||
if parts[0] == Accessor::RecordField(keyword) {
|
||||
return Err((NoProgress, EPattern::End(original_state.pos())));
|
||||
}
|
||||
}
|
||||
|
||||
if module_name.is_empty() && parts.len() == 1 {
|
||||
if let Accessor::RecordField(var) = &parts[0] {
|
||||
return Ok((
|
||||
MadeProgress,
|
||||
Loc {
|
||||
region: loc_ident.region,
|
||||
value: Pattern::Identifier(var),
|
||||
},
|
||||
state,
|
||||
));
|
||||
}
|
||||
}
|
||||
let mut malformed_str = String::new_in(arena);
|
||||
|
||||
if !module_name.is_empty() {
|
||||
malformed_str.push_str(module_name);
|
||||
};
|
||||
for part in parts {
|
||||
if !malformed_str.is_empty() {
|
||||
malformed_str.push('.');
|
||||
}
|
||||
malformed_str.push_str(part.as_inner());
|
||||
}
|
||||
|
||||
Ok((
|
||||
MadeProgress,
|
||||
Loc {
|
||||
region: loc_ident.region,
|
||||
value: Pattern::Malformed(malformed_str.into_bump_str()),
|
||||
},
|
||||
state,
|
||||
))
|
||||
}
|
||||
Ident::RecordAccessorFunction(_string) => Err((
|
||||
MadeProgress,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue