Parsing support for snake_case identifiers

In this initial commit, I have done the following:

- Added unit tests to roc_parse's ident.rs file to cover at least the
  simplest Ident enum cases (Tag, OpaqueRef, and simple Access)
- Added '_' as a valid "rest" character in both uppercase and lowercase
  identifier parts
- Updated the test_syntax snapshots appropriately

There is still a lot left to do here. Such as:

- Do we want to allow multiple '_'s to parse successfully?
- Handle qualified access
- Handle accessor functions
- Handle record update functions
- Remove the UnderscoreInMiddle case from BadIdent
- Write unit tests for Malformed Idents

I am not a "Rustacean" by any means, but have been through the Book in
years past.  Any feedback on the way I wrote the tests or any other part
of the implementation would be very appreciated.
This commit is contained in:
Anthony Bullard 2024-11-20 09:00:57 -06:00
parent d7825428df
commit a2083cec30
No known key found for this signature in database
31 changed files with 1214 additions and 460 deletions

View file

@ -1,6 +1,6 @@
use std::cmp::max;
use crate::annotation::{is_collection_multiline, Formattable, Newlines, Parens};
use crate::annotation::{is_collection_multiline, Formattable, MigrationFlags, Newlines, Parens};
use crate::collection::{fmt_collection, Braces};
use crate::expr::fmt_str_literal;
use crate::spaces::{fmt_comments_only, fmt_default_spaces, fmt_spaces, NewlineAt, INDENT};
@ -15,23 +15,27 @@ use roc_parse::header::{
use roc_parse::ident::UppercaseIdent;
use roc_region::all::Loc;
pub fn fmt_header<'a>(buf: &mut Buf<'_>, header: &'a SpacesBefore<'a, Header<'a>>) {
pub fn fmt_header<'a>(
buf: &mut Buf<'_>,
header: &'a SpacesBefore<'a, Header<'a>>,
flags: &MigrationFlags,
) {
fmt_comments_only(buf, header.before.iter(), NewlineAt::Bottom, 0);
match &header.item {
Header::Module(header) => {
fmt_module_header(buf, header);
fmt_module_header(buf, header, flags);
}
Header::App(header) => {
fmt_app_header(buf, header);
fmt_app_header(buf, header, flags);
}
Header::Package(header) => {
fmt_package_header(buf, header);
fmt_package_header(buf, header, flags);
}
Header::Platform(header) => {
fmt_platform_header(buf, header);
fmt_platform_header(buf, header, flags);
}
Header::Hosted(header) => {
fmt_hosted_header(buf, header);
fmt_hosted_header(buf, header, flags);
}
}
}
@ -49,6 +53,7 @@ macro_rules! keywords {
buf: &mut Buf<'_>,
_parens: crate::annotation::Parens,
_newlines: Newlines,
_flags: &crate::annotation::MigrationFlags,
indent: u16,
) {
buf.indent(indent);
@ -84,10 +89,11 @@ impl<V: Formattable> Formattable for Option<V> {
buf: &mut Buf,
parens: crate::annotation::Parens,
newlines: Newlines,
flags: &MigrationFlags,
indent: u16,
) {
if let Some(v) = self {
v.format_with_options(buf, parens, newlines, indent);
v.format_with_options(buf, parens, newlines, flags, indent);
}
}
}
@ -109,12 +115,13 @@ impl<'a> Formattable for ProvidesTo<'a> {
buf: &mut Buf,
_parens: crate::annotation::Parens,
_newlines: Newlines,
flags: &MigrationFlags,
indent: u16,
) {
self.provides_keyword.format(buf, indent);
fmt_provides(buf, self.entries, self.types, indent);
self.to_keyword.format(buf, indent);
fmt_to(buf, self.to.value, indent);
self.provides_keyword.format(buf, flags, indent);
fmt_provides(buf, self.entries, self.types, flags, indent);
self.to_keyword.format(buf, flags, indent);
fmt_to(buf, self.to.value, flags, indent);
}
}
@ -128,9 +135,10 @@ impl<'a> Formattable for PlatformRequires<'a> {
buf: &mut Buf,
_parens: crate::annotation::Parens,
_newlines: Newlines,
flags: &MigrationFlags,
indent: u16,
) {
fmt_requires(buf, self, indent);
fmt_requires(buf, self, flags, indent);
}
}
@ -144,10 +152,12 @@ impl<'a, V: Formattable> Formattable for Spaces<'a, V> {
buf: &mut Buf,
parens: crate::annotation::Parens,
newlines: Newlines,
flags: &MigrationFlags,
indent: u16,
) {
fmt_default_spaces(buf, self.before, indent);
self.item.format_with_options(buf, parens, newlines, indent);
self.item
.format_with_options(buf, parens, newlines, flags, indent);
fmt_default_spaces(buf, self.after, indent);
}
}
@ -157,14 +167,22 @@ impl<'a, K: Formattable, V: Formattable> Formattable for KeywordItem<'a, K, V> {
self.keyword.is_multiline() || self.item.is_multiline()
}
fn format_with_options(&self, buf: &mut Buf, parens: Parens, newlines: Newlines, indent: u16) {
fn format_with_options(
&self,
buf: &mut Buf,
parens: Parens,
newlines: Newlines,
flags: &MigrationFlags,
indent: u16,
) {
self.keyword
.format_with_options(buf, parens, newlines, indent);
self.item.format_with_options(buf, parens, newlines, indent);
.format_with_options(buf, parens, newlines, flags, indent);
self.item
.format_with_options(buf, parens, newlines, flags, indent);
}
}
pub fn fmt_module_header<'a>(buf: &mut Buf, header: &'a ModuleHeader<'a>) {
pub fn fmt_module_header<'a>(buf: &mut Buf, header: &'a ModuleHeader<'a>, flags: &MigrationFlags) {
buf.indent(0);
buf.push_str("module");
@ -177,6 +195,7 @@ pub fn fmt_module_header<'a>(buf: &mut Buf, header: &'a ModuleHeader<'a>) {
fmt_collection(
buf,
flags,
indent,
Braces::Curly,
params.pattern.value,
@ -188,10 +207,10 @@ pub fn fmt_module_header<'a>(buf: &mut Buf, header: &'a ModuleHeader<'a>) {
indent = fmt_spaces_with_outdent(buf, params.after_arrow, indent);
}
fmt_exposes(buf, header.exposes, indent);
fmt_exposes(buf, header.exposes, flags, indent);
}
pub fn fmt_hosted_header<'a>(buf: &mut Buf, header: &'a HostedHeader<'a>) {
pub fn fmt_hosted_header<'a>(buf: &mut Buf, header: &'a HostedHeader<'a>, flags: &MigrationFlags) {
buf.indent(0);
buf.push_str("hosted");
let indent = INDENT;
@ -199,21 +218,21 @@ pub fn fmt_hosted_header<'a>(buf: &mut Buf, header: &'a HostedHeader<'a>) {
buf.push_str(header.name.value.as_str());
header.exposes.keyword.format(buf, indent);
fmt_exposes(buf, header.exposes.item, indent);
header.imports.keyword.format(buf, indent);
fmt_imports(buf, header.imports.item, indent);
header.exposes.keyword.format(buf, flags, indent);
fmt_exposes(buf, header.exposes.item, flags, indent);
header.imports.keyword.format(buf, flags, indent);
fmt_imports(buf, header.imports.item, flags, indent);
}
pub fn fmt_app_header<'a>(buf: &mut Buf, header: &'a AppHeader<'a>) {
pub fn fmt_app_header<'a>(buf: &mut Buf, header: &'a AppHeader<'a>, flags: &MigrationFlags) {
buf.indent(0);
buf.push_str("app");
let indent = fmt_spaces_with_outdent(buf, header.before_provides, 0);
fmt_exposes(buf, header.provides, indent);
fmt_exposes(buf, header.provides, flags, indent);
let indent = fmt_spaces_with_outdent(buf, header.before_packages, indent);
fmt_packages(buf, header.packages.value, indent);
fmt_packages(buf, header.packages.value, flags, indent);
}
pub fn fmt_spaces_with_outdent(buf: &mut Buf, spaces: &[CommentOrNewline], indent: u16) -> u16 {
@ -227,18 +246,26 @@ pub fn fmt_spaces_with_outdent(buf: &mut Buf, spaces: &[CommentOrNewline], inden
}
}
pub fn fmt_package_header<'a>(buf: &mut Buf, header: &'a PackageHeader<'a>) {
pub fn fmt_package_header<'a>(
buf: &mut Buf,
header: &'a PackageHeader<'a>,
flags: &MigrationFlags,
) {
buf.indent(0);
buf.push_str("package");
let indent = fmt_spaces_with_outdent(buf, header.before_exposes, 0);
fmt_exposes(buf, header.exposes, indent);
fmt_exposes(buf, header.exposes, flags, indent);
let indent = fmt_spaces_with_outdent(buf, header.before_packages, indent);
fmt_packages(buf, header.packages.value, indent);
fmt_packages(buf, header.packages.value, flags, indent);
}
pub fn fmt_platform_header<'a>(buf: &mut Buf, header: &'a PlatformHeader<'a>) {
pub fn fmt_platform_header<'a>(
buf: &mut Buf,
header: &'a PlatformHeader<'a>,
flags: &MigrationFlags,
) {
buf.indent(0);
buf.push_str("platform");
let indent = INDENT;
@ -246,23 +273,31 @@ pub fn fmt_platform_header<'a>(buf: &mut Buf, header: &'a PlatformHeader<'a>) {
fmt_package_name(buf, header.name.value, indent);
header.requires.format(buf, indent);
header.exposes.keyword.format(buf, indent);
fmt_exposes(buf, header.exposes.item, indent);
header.packages.keyword.format(buf, indent);
fmt_packages(buf, header.packages.item, indent);
header.imports.keyword.format(buf, indent);
fmt_imports(buf, header.imports.item, indent);
header.provides.keyword.format(buf, indent);
fmt_provides(buf, header.provides.item, None, indent);
header.requires.format(buf, flags, indent);
header.exposes.keyword.format(buf, flags, indent);
fmt_exposes(buf, header.exposes.item, flags, indent);
header.packages.keyword.format(buf, flags, indent);
fmt_packages(buf, header.packages.item, flags, indent);
header.imports.keyword.format(buf, flags, indent);
fmt_imports(buf, header.imports.item, flags, indent);
header.provides.keyword.format(buf, flags, indent);
fmt_provides(buf, header.provides.item, None, flags, indent);
}
fn fmt_requires(buf: &mut Buf, requires: &PlatformRequires, indent: u16) {
fmt_collection(buf, indent, Braces::Curly, requires.rigids, Newlines::No);
fn fmt_requires(buf: &mut Buf, requires: &PlatformRequires, flags: &MigrationFlags, indent: u16) {
fmt_collection(
buf,
flags,
indent,
Braces::Curly,
requires.rigids,
Newlines::No,
);
buf.spaces(1);
fmt_collection(
buf,
flags,
indent,
Braces::Curly,
requires.signatures,
@ -280,6 +315,7 @@ impl<'a> Formattable for TypedIdent<'a> {
buf: &mut Buf,
_parens: Parens,
_newlines: Newlines,
flags: &MigrationFlags,
indent: u16,
) {
buf.indent(indent);
@ -288,7 +324,7 @@ impl<'a> Formattable for TypedIdent<'a> {
buf.push(':');
buf.spaces(1);
self.ann.value.format(buf, indent);
self.ann.value.format(buf, flags, indent);
}
}
@ -316,18 +352,19 @@ impl<'a, T: Formattable> Formattable for Spaced<'a, T> {
buf: &mut Buf,
parens: crate::annotation::Parens,
newlines: Newlines,
flags: &MigrationFlags,
indent: u16,
) {
match self {
Spaced::Item(item) => {
item.format_with_options(buf, parens, newlines, indent);
item.format_with_options(buf, parens, newlines, flags, indent);
}
Spaced::SpaceBefore(item, spaces) => {
fmt_spaces(buf, spaces.iter(), indent);
item.format_with_options(buf, parens, newlines, indent);
item.format_with_options(buf, parens, newlines, flags, indent);
}
Spaced::SpaceAfter(item, spaces) => {
item.format_with_options(buf, parens, newlines, indent);
item.format_with_options(buf, parens, newlines, flags, indent);
fmt_spaces(buf, spaces.iter(), indent);
}
}
@ -337,25 +374,48 @@ impl<'a, T: Formattable> Formattable for Spaced<'a, T> {
fn fmt_imports<'a>(
buf: &mut Buf,
loc_entries: Collection<'a, Loc<Spaced<'a, ImportsEntry<'a>>>>,
flags: &MigrationFlags,
indent: u16,
) {
fmt_collection(buf, indent, Braces::Square, loc_entries, Newlines::No)
fmt_collection(
buf,
flags,
indent,
Braces::Square,
loc_entries,
Newlines::No,
)
}
fn fmt_provides<'a>(
buf: &mut Buf,
loc_exposed_names: Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>,
loc_provided_types: Option<Collection<'a, Loc<Spaced<'a, UppercaseIdent<'a>>>>>,
flags: &MigrationFlags,
indent: u16,
) {
fmt_collection(buf, indent, Braces::Square, loc_exposed_names, Newlines::No);
fmt_collection(
buf,
flags,
indent,
Braces::Square,
loc_exposed_names,
Newlines::No,
);
if let Some(loc_provided) = loc_provided_types {
fmt_default_spaces(buf, &[], indent);
fmt_collection(buf, indent, Braces::Curly, loc_provided, Newlines::No);
fmt_collection(
buf,
flags,
indent,
Braces::Curly,
loc_provided,
Newlines::No,
);
}
}
fn fmt_to(buf: &mut Buf, to: To, indent: u16) {
fn fmt_to(buf: &mut Buf, to: To, _flags: &MigrationFlags, indent: u16) {
match to {
To::ExistingPackage(name) => {
buf.push_str(name);
@ -367,9 +427,17 @@ fn fmt_to(buf: &mut Buf, to: To, indent: u16) {
fn fmt_exposes<N: Formattable + Copy + core::fmt::Debug>(
buf: &mut Buf,
loc_entries: Collection<'_, Loc<Spaced<'_, N>>>,
flags: &MigrationFlags,
indent: u16,
) {
fmt_collection(buf, indent, Braces::Square, loc_entries, Newlines::No)
fmt_collection(
buf,
flags,
indent,
Braces::Square,
loc_entries,
Newlines::No,
)
}
pub trait FormatName {
@ -398,6 +466,7 @@ impl<'a> Formattable for ModuleName<'a> {
buf: &mut Buf,
_parens: Parens,
_newlines: Newlines,
_flags: &MigrationFlags,
_indent: u16,
) {
buf.push_str(self.as_str());
@ -414,6 +483,7 @@ impl<'a> Formattable for ExposedName<'a> {
buf: &mut Buf,
_parens: Parens,
_newlines: Newlines,
_flags: &MigrationFlags,
indent: u16,
) {
buf.indent(indent);
@ -430,9 +500,10 @@ impl<'a> FormatName for ExposedName<'a> {
fn fmt_packages<'a>(
buf: &mut Buf,
loc_entries: Collection<'a, Loc<Spaced<'a, PackageEntry<'a>>>>,
flags: &MigrationFlags,
indent: u16,
) {
fmt_collection(buf, indent, Braces::Curly, loc_entries, Newlines::No)
fmt_collection(buf, flags, indent, Braces::Curly, loc_entries, Newlines::No)
}
impl<'a> Formattable for PackageEntry<'a> {
@ -445,9 +516,10 @@ impl<'a> Formattable for PackageEntry<'a> {
buf: &mut Buf,
_parens: Parens,
_newlines: Newlines,
flags: &MigrationFlags,
indent: u16,
) {
fmt_packages_entry(buf, self, indent);
fmt_packages_entry(buf, self, flags, indent);
}
}
@ -461,12 +533,13 @@ impl<'a> Formattable for ImportsEntry<'a> {
buf: &mut Buf,
_parens: Parens,
_newlines: Newlines,
flags: &MigrationFlags,
indent: u16,
) {
fmt_imports_entry(buf, self, indent);
fmt_imports_entry(buf, self, flags, indent);
}
}
fn fmt_packages_entry(buf: &mut Buf, entry: &PackageEntry, indent: u16) {
fn fmt_packages_entry(buf: &mut Buf, entry: &PackageEntry, _flags: &MigrationFlags, indent: u16) {
buf.push_str(entry.shorthand);
buf.push(':');
fmt_default_spaces(buf, entry.spaces_after_shorthand, indent);
@ -482,7 +555,7 @@ fn fmt_packages_entry(buf: &mut Buf, entry: &PackageEntry, indent: u16) {
fmt_package_name(buf, entry.package_name.value, indent);
}
fn fmt_imports_entry(buf: &mut Buf, entry: &ImportsEntry, indent: u16) {
fn fmt_imports_entry(buf: &mut Buf, entry: &ImportsEntry, flags: &MigrationFlags, indent: u16) {
use roc_parse::header::ImportsEntry::*;
buf.indent(indent);
@ -496,6 +569,7 @@ fn fmt_imports_entry(buf: &mut Buf, entry: &ImportsEntry, indent: u16) {
fmt_collection(
buf,
flags,
indent,
Braces::Curly,
*loc_exposes_entries,
@ -512,14 +586,14 @@ fn fmt_imports_entry(buf: &mut Buf, entry: &ImportsEntry, indent: u16) {
if !entries.is_empty() {
buf.push('.');
fmt_collection(buf, indent, Braces::Curly, *entries, Newlines::No)
fmt_collection(buf, flags, indent, Braces::Curly, *entries, Newlines::No)
}
}
IngestedFile(file_name, typed_ident) => {
fmt_str_literal(buf, *file_name, indent);
fmt_str_literal(buf, *file_name, flags, indent);
buf.push_str_allow_spaces(" as ");
typed_ident.format(buf, 0);
typed_ident.format(buf, flags, 0);
}
}
}