From 2b91af02df9bcaa9d876f9cb2e49d35faaedda08 Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Sat, 26 Nov 2022 09:48:34 -0800 Subject: [PATCH] Introduce `record!` combinator ... and refactor header parser to fully use combinators, in support of future combinator-based superpowers --- crates/compiler/fmt/src/annotation.rs | 27 +- crates/compiler/fmt/src/collection.rs | 4 +- crates/compiler/fmt/src/module.rs | 362 +++++++---- crates/compiler/fmt/src/spaces.rs | 150 ++--- crates/compiler/load_internal/src/file.rs | 143 ++-- crates/compiler/parse/src/ast.rs | 18 +- crates/compiler/parse/src/expr.rs | 8 +- crates/compiler/parse/src/header.rs | 225 +++---- crates/compiler/parse/src/module.rs | 610 ++++++------------ crates/compiler/parse/src/parser.rs | 44 +- crates/compiler/parse/src/type_annotation.rs | 76 +-- .../pass/empty_app_header.header.result-ast | 70 +- .../empty_hosted_header.header.result-ast | 66 +- .../empty_interface_header.header.result-ast | 40 +- .../empty_platform_header.header.result-ast | 87 ++- .../pass/full_app_header.header.result-ast | 118 ++-- ...p_header_trailing_commas.header.result-ast | 160 +++-- .../function_effect_types.header.result-ast | 159 +++-- .../interface_with_newline.header.result-ast | 40 +- .../pass/minimal_app_header.header.result-ast | 54 +- .../pass/nested_module.header.result-ast | 40 +- .../nonempty_hosted_header.header.result-ast | 260 ++++---- ...nonempty_platform_header.header.result-ast | 139 ++-- .../pass/provides_type.header.result-ast | 136 ++-- .../pass/requires_type.header.result-ast | 145 +++-- crates/packaging/src/tarball.rs | 14 +- 26 files changed, 1709 insertions(+), 1486 deletions(-) diff --git a/crates/compiler/fmt/src/annotation.rs b/crates/compiler/fmt/src/annotation.rs index 6be2cb6945..b3c9050c8e 100644 --- a/crates/compiler/fmt/src/annotation.rs +++ b/crates/compiler/fmt/src/annotation.rs @@ -63,9 +63,7 @@ pub trait Formattable { _parens: Parens, _newlines: Newlines, indent: u16, - ) { - self.format(buf, indent); - } + ); fn format<'buf>(&self, buf: &mut Buf<'buf>, indent: u16) { self.format_with_options(buf, Parens::NotNeeded, Newlines::No, indent); @@ -96,18 +94,13 @@ where } } -impl<'a, T> Formattable for Collection<'a, T> -where - T: Formattable, -{ - fn is_multiline(&self) -> bool { - // if there are any comments, they must go on their own line - // because otherwise they'd comment out the closing delimiter - !self.final_comments().is_empty() || - // if any of the items in the collection are multiline, - // then the whole collection must be multiline - self.items.iter().any(Formattable::is_multiline) - } +pub fn is_collection_multiline(collection: &Collection<'_, T>) -> bool { + // if there are any comments, they must go on their own line + // because otherwise they'd comment out the closing delimiter + !collection.final_comments().is_empty() || + // if any of the items in the collection are multiline, + // then the whole collection must be multiline + collection.items.iter().any(Formattable::is_multiline) } /// A Located formattable value is also formattable @@ -577,7 +570,7 @@ impl<'a> Formattable for HasImpls<'a> { fn is_multiline(&self) -> bool { match self { HasImpls::SpaceBefore(_, _) | HasImpls::SpaceAfter(_, _) => true, - HasImpls::HasImpls(impls) => impls.is_multiline(), + HasImpls::HasImpls(impls) => is_collection_multiline(impls), } } @@ -657,7 +650,7 @@ impl<'a> Formattable for HasAbilities<'a> { fn is_multiline(&self) -> bool { match self { HasAbilities::SpaceAfter(..) | HasAbilities::SpaceBefore(..) => true, - HasAbilities::Has(has_abilities) => has_abilities.is_multiline(), + HasAbilities::Has(has_abilities) => is_collection_multiline(has_abilities), } } diff --git a/crates/compiler/fmt/src/collection.rs b/crates/compiler/fmt/src/collection.rs index 80798788ce..643928fcb1 100644 --- a/crates/compiler/fmt/src/collection.rs +++ b/crates/compiler/fmt/src/collection.rs @@ -1,7 +1,7 @@ use roc_parse::ast::{Collection, CommentOrNewline, ExtractSpaces}; use crate::{ - annotation::{Formattable, Newlines}, + annotation::{is_collection_multiline, Formattable, Newlines}, spaces::{fmt_comments_only, NewlineAt, INDENT}, Buf, }; @@ -34,7 +34,7 @@ pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>( Braces::Square => ']', }; - if items.is_multiline() { + if is_collection_multiline(&items) { let braces_indent = indent; let item_indent = braces_indent + INDENT; if newline == Newlines::Yes { diff --git a/crates/compiler/fmt/src/module.rs b/crates/compiler/fmt/src/module.rs index 530769e759..d0a575fc81 100644 --- a/crates/compiler/fmt/src/module.rs +++ b/crates/compiler/fmt/src/module.rs @@ -1,184 +1,248 @@ -use crate::annotation::{Formattable, Newlines}; +use crate::annotation::{is_collection_multiline, Formattable, Newlines, Parens}; use crate::collection::{fmt_collection, Braces}; use crate::expr::fmt_str_literal; +use crate::spaces::RemoveSpaces; use crate::spaces::{fmt_comments_only, fmt_default_spaces, fmt_spaces, NewlineAt, INDENT}; use crate::Buf; -use roc_parse::ast::{Collection, Module, Spaced}; +use bumpalo::Bump; +use roc_parse::ast::{Collection, Header, Module, Spaced, Spaces}; use roc_parse::header::{ - AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry, - PackageName, PlatformHeader, PlatformRequires, To, TypedIdent, + AppHeader, ExposedName, ExposesKeyword, GeneratesKeyword, HostedHeader, ImportsEntry, + ImportsKeyword, InterfaceHeader, Keyword, KeywordItem, ModuleName, PackageEntry, + PackageKeyword, PackageName, PackagesKeyword, PlatformHeader, PlatformRequires, + ProvidesKeyword, ProvidesTo, RequiresKeyword, To, ToKeyword, TypedIdent, WithKeyword, }; use roc_parse::ident::UppercaseIdent; use roc_region::all::Loc; pub fn fmt_module<'a>(buf: &mut Buf<'_>, module: &'a Module<'a>) { - match module { - Module::Interface { header } => { + fmt_comments_only(buf, module.comments.iter(), NewlineAt::Bottom, 0); + match &module.header { + Header::Interface(header) => { fmt_interface_header(buf, header); } - Module::App { header } => { + Header::App(header) => { fmt_app_header(buf, header); } - Module::Platform { header } => { + Header::Platform(header) => { fmt_platform_header(buf, header); } - Module::Hosted { header } => { + Header::Hosted(header) => { fmt_hosted_header(buf, header); } } } +macro_rules! keywords { + ($($name:ident),* $(,)?) => { + $( + impl Formattable for $name { + fn is_multiline(&self) -> bool { + false + } + + fn format_with_options<'buf>( + &self, + buf: &mut Buf<'buf>, + _parens: crate::annotation::Parens, + _newlines: Newlines, + indent: u16, + ) { + buf.indent(indent); + buf.push_str($name::KEYWORD); + } + } + + impl<'a> RemoveSpaces<'a> for $name { + fn remove_spaces(&self, _arena: &'a Bump) -> Self { + *self + } + } + )* + } +} + +keywords! { + ExposesKeyword, + ImportsKeyword, + WithKeyword, + GeneratesKeyword, + PackageKeyword, + PackagesKeyword, + RequiresKeyword, + ProvidesKeyword, + ToKeyword, +} + +impl Formattable for Option { + fn is_multiline(&self) -> bool { + if let Some(v) = self { + v.is_multiline() + } else { + false + } + } + + fn format_with_options<'buf>( + &self, + buf: &mut Buf<'buf>, + parens: crate::annotation::Parens, + newlines: Newlines, + indent: u16, + ) { + if let Some(v) = self { + v.format_with_options(buf, parens, newlines, indent); + } + } +} + +impl<'a> Formattable for ProvidesTo<'a> { + fn is_multiline(&self) -> bool { + if let Some(types) = &self.types { + if is_collection_multiline(types) { + return true; + } + } + self.provides_keyword.is_multiline() + || is_collection_multiline(&self.entries) + || self.to_keyword.is_multiline() + } + + fn format_with_options<'buf>( + &self, + buf: &mut Buf<'buf>, + _parens: crate::annotation::Parens, + _newlines: Newlines, + 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); + } +} + +impl<'a> Formattable for PlatformRequires<'a> { + fn is_multiline(&self) -> bool { + is_collection_multiline(&self.rigids) || self.signature.is_multiline() + } + + fn format_with_options<'buf>( + &self, + buf: &mut Buf<'buf>, + _parens: crate::annotation::Parens, + _newlines: Newlines, + indent: u16, + ) { + fmt_requires(buf, self, indent); + } +} + +impl<'a, V: Formattable> Formattable for Spaces<'a, V> { + fn is_multiline(&self) -> bool { + !self.before.is_empty() || !self.after.is_empty() || self.item.is_multiline() + } + + fn format_with_options<'buf>( + &self, + buf: &mut Buf<'buf>, + parens: crate::annotation::Parens, + newlines: Newlines, + indent: u16, + ) { + fmt_default_spaces(buf, self.before, indent); + self.item.format_with_options(buf, parens, newlines, indent); + fmt_default_spaces(buf, self.after, indent); + } +} + +impl<'a, K: Formattable, V: Formattable> Formattable for KeywordItem<'a, K, V> { + fn is_multiline(&self) -> bool { + self.keyword.is_multiline() || self.item.is_multiline() + } + + fn format_with_options<'buf>( + &self, + buf: &mut Buf<'buf>, + parens: Parens, + newlines: Newlines, + indent: u16, + ) { + self.keyword + .format_with_options(buf, parens, newlines, indent); + self.item.format_with_options(buf, parens, newlines, indent); + } +} + pub fn fmt_interface_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a InterfaceHeader<'a>) { - let indent = INDENT; - - fmt_comments_only(buf, header.before_header.iter(), NewlineAt::Bottom, indent); - buf.indent(0); buf.push_str("interface"); + let indent = INDENT; + fmt_default_spaces(buf, header.before_name, indent); // module name - fmt_default_spaces(buf, header.after_interface_keyword, indent); + buf.indent(indent); buf.push_str(header.name.value.as_str()); - // exposes - fmt_default_spaces(buf, header.before_exposes, indent); - buf.indent(indent); - buf.push_str("exposes"); - fmt_default_spaces(buf, header.after_exposes, indent); - fmt_exposes(buf, header.exposes, indent); - - // imports - fmt_default_spaces(buf, header.before_imports, indent); - buf.indent(indent); - buf.push_str("imports"); - fmt_default_spaces(buf, header.after_imports, indent); - fmt_imports(buf, header.imports, indent); + 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); } pub fn fmt_hosted_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a HostedHeader<'a>) { - let indent = INDENT; - - fmt_comments_only(buf, header.before_header.iter(), NewlineAt::Bottom, indent); - buf.indent(0); buf.push_str("hosted"); + let indent = INDENT; + fmt_default_spaces(buf, header.before_name, indent); - // module name - fmt_default_spaces(buf, header.after_hosted_keyword, indent); buf.push_str(header.name.value.as_str()); - // exposes - fmt_default_spaces(buf, header.before_exposes, indent); - buf.indent(indent); - buf.push_str("exposes"); - fmt_default_spaces(buf, header.after_exposes, indent); - fmt_exposes(buf, header.exposes, indent); - - // imports - fmt_default_spaces(buf, header.before_imports, indent); - buf.indent(indent); - buf.push_str("imports"); - fmt_default_spaces(buf, header.after_imports, indent); - fmt_imports(buf, header.imports, indent); - - // generates - fmt_default_spaces(buf, header.before_generates, indent); - buf.indent(indent); - buf.push_str("generates"); - fmt_default_spaces(buf, header.after_generates, indent); - buf.push_str(header.generates.into()); - - // with - fmt_default_spaces(buf, header.before_with, indent); - buf.indent(indent); - buf.push_str("with"); - fmt_default_spaces(buf, header.after_with, indent); - fmt_exposes(buf, header.generates_with, indent); + 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.generates.format(buf, indent); + header.generates_with.keyword.format(buf, indent); + fmt_exposes(buf, header.generates_with.item, indent); } pub fn fmt_app_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a AppHeader<'a>) { - let indent = INDENT; - - fmt_comments_only(buf, header.before_header.iter(), NewlineAt::Bottom, indent); - buf.indent(0); buf.push_str("app"); + let indent = INDENT; + fmt_default_spaces(buf, header.before_name, indent); - fmt_default_spaces(buf, header.after_app_keyword, indent); fmt_str_literal(buf, header.name.value, indent); - // packages - fmt_default_spaces(buf, header.before_packages, indent); - buf.indent(indent); - buf.push_str("packages"); - fmt_default_spaces(buf, header.after_packages, indent); - fmt_packages(buf, header.packages, indent); - - // imports - fmt_default_spaces(buf, header.before_imports, indent); - buf.indent(indent); - buf.push_str("imports"); - fmt_default_spaces(buf, header.after_imports, indent); - fmt_imports(buf, header.imports, indent); - - // provides - fmt_default_spaces(buf, header.before_provides, indent); - buf.indent(indent); - buf.push_str("provides"); - fmt_default_spaces(buf, header.after_provides, indent); - fmt_provides(buf, header.provides, header.provides_types, indent); - fmt_default_spaces(buf, header.before_to, indent); - buf.indent(indent); - buf.push_str("to"); - fmt_default_spaces(buf, header.after_to, indent); - fmt_to(buf, header.to.value, indent); + if let Some(packages) = &header.packages { + packages.keyword.format(buf, indent); + fmt_packages(buf, packages.item, indent); + } + if let Some(imports) = &header.imports { + imports.keyword.format(buf, indent); + fmt_imports(buf, imports.item, indent); + } + header.provides.format(buf, indent); } pub fn fmt_platform_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a PlatformHeader<'a>) { - let indent = INDENT; - - fmt_comments_only(buf, header.before_header.iter(), NewlineAt::Bottom, indent); - buf.indent(0); buf.push_str("platform"); + let indent = INDENT; + fmt_default_spaces(buf, header.before_name, indent); - fmt_default_spaces(buf, header.after_platform_keyword, indent); fmt_package_name(buf, header.name.value, indent); - // requires - fmt_default_spaces(buf, header.before_requires, indent); - buf.indent(indent); - buf.push_str("requires"); - fmt_default_spaces(buf, header.after_requires, indent); - fmt_requires(buf, &header.requires, indent); - - // exposes - fmt_default_spaces(buf, header.before_exposes, indent); - buf.indent(indent); - buf.push_str("exposes"); - fmt_default_spaces(buf, header.after_exposes, indent); - fmt_exposes(buf, header.exposes, indent); - - // packages - fmt_default_spaces(buf, header.before_packages, indent); - buf.indent(indent); - buf.push_str("packages"); - fmt_default_spaces(buf, header.after_packages, indent); - fmt_packages(buf, header.packages, indent); - - // imports - fmt_default_spaces(buf, header.before_imports, indent); - buf.indent(indent); - buf.push_str("imports"); - fmt_default_spaces(buf, header.after_imports, indent); - fmt_imports(buf, header.imports, indent); - - // provides - fmt_default_spaces(buf, header.before_provides, indent); - buf.indent(indent); - buf.push_str("provides"); - fmt_default_spaces(buf, header.after_provides, indent); - fmt_provides(buf, header.provides, None, 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); } fn fmt_requires<'a, 'buf>(buf: &mut Buf<'buf>, requires: &PlatformRequires<'a>, indent: u16) { @@ -195,7 +259,13 @@ impl<'a> Formattable for TypedIdent<'a> { false } - fn format<'buf>(&self, buf: &mut Buf<'buf>, indent: u16) { + fn format_with_options<'buf>( + &self, + buf: &mut Buf<'buf>, + _parens: Parens, + _newlines: Newlines, + indent: u16, + ) { buf.indent(indent); buf.push_str(self.ident.value); fmt_default_spaces(buf, self.spaces_before_colon, indent); @@ -306,7 +376,13 @@ impl<'a> Formattable for ModuleName<'a> { false } - fn format<'buf>(&self, buf: &mut Buf<'buf>, _indent: u16) { + fn format_with_options<'buf>( + &self, + buf: &mut Buf<'buf>, + _parens: Parens, + _newlines: Newlines, + _indent: u16, + ) { buf.push_str(self.as_str()); } } @@ -316,7 +392,13 @@ impl<'a> Formattable for ExposedName<'a> { false } - fn format<'buf>(&self, buf: &mut Buf<'buf>, indent: u16) { + fn format_with_options<'buf>( + &self, + buf: &mut Buf<'buf>, + _parens: Parens, + _newlines: Newlines, + indent: u16, + ) { buf.indent(indent); buf.push_str(self.as_str()); } @@ -341,7 +423,13 @@ impl<'a> Formattable for PackageEntry<'a> { false } - fn format<'buf>(&self, buf: &mut Buf<'buf>, indent: u16) { + fn format_with_options<'buf>( + &self, + buf: &mut Buf<'buf>, + _parens: Parens, + _newlines: Newlines, + indent: u16, + ) { fmt_packages_entry(buf, self, indent); } } @@ -351,7 +439,13 @@ impl<'a> Formattable for ImportsEntry<'a> { false } - fn format<'buf>(&self, buf: &mut Buf<'buf>, indent: u16) { + fn format_with_options<'buf>( + &self, + buf: &mut Buf<'buf>, + _parens: Parens, + _newlines: Newlines, + indent: u16, + ) { fmt_imports_entry(buf, self, indent); } } diff --git a/crates/compiler/fmt/src/spaces.rs b/crates/compiler/fmt/src/spaces.rs index 1ca70d6ee8..98c6ba6170 100644 --- a/crates/compiler/fmt/src/spaces.rs +++ b/crates/compiler/fmt/src/spaces.rs @@ -4,12 +4,13 @@ use roc_module::called_via::{BinOp, UnaryOp}; use roc_parse::{ ast::{ AbilityMember, AssignedField, Collection, CommentOrNewline, Defs, Expr, Has, HasAbilities, - HasAbility, HasClause, HasImpls, Module, Pattern, Spaced, StrLiteral, StrSegment, Tag, - TypeAnnotation, TypeDef, TypeHeader, ValueDef, WhenBranch, + HasAbility, HasClause, HasImpls, Header, Module, Pattern, Spaced, Spaces, StrLiteral, + StrSegment, Tag, TypeAnnotation, TypeDef, TypeHeader, ValueDef, WhenBranch, }, header::{ - AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName, - PackageEntry, PackageName, PlatformHeader, PlatformRequires, To, TypedIdent, + AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, KeywordItem, + ModuleName, PackageEntry, PackageName, PlatformHeader, PlatformRequires, ProvidesTo, To, + TypedIdent, }, ident::UppercaseIdent, }; @@ -242,83 +243,74 @@ impl<'a> RemoveSpaces<'a> for Defs<'a> { } } +impl<'a, V: RemoveSpaces<'a>> RemoveSpaces<'a> for Spaces<'a, V> { + fn remove_spaces(&self, arena: &'a Bump) -> Self { + Spaces { + before: &[], + item: self.item.remove_spaces(arena), + after: &[], + } + } +} + +impl<'a, K: RemoveSpaces<'a>, V: RemoveSpaces<'a>> RemoveSpaces<'a> for KeywordItem<'a, K, V> { + fn remove_spaces(&self, arena: &'a Bump) -> Self { + KeywordItem { + keyword: self.keyword.remove_spaces(arena), + item: self.item.remove_spaces(arena), + } + } +} + +impl<'a> RemoveSpaces<'a> for ProvidesTo<'a> { + fn remove_spaces(&self, arena: &'a Bump) -> Self { + ProvidesTo { + provides_keyword: self.provides_keyword.remove_spaces(arena), + entries: self.entries.remove_spaces(arena), + types: self.types.remove_spaces(arena), + to_keyword: self.to_keyword.remove_spaces(arena), + to: self.to.remove_spaces(arena), + } + } +} + impl<'a> RemoveSpaces<'a> for Module<'a> { fn remove_spaces(&self, arena: &'a Bump) -> Self { - match self { - Module::Interface { header } => Module::Interface { - header: InterfaceHeader { - name: header.name.remove_spaces(arena), - exposes: header.exposes.remove_spaces(arena), - imports: header.imports.remove_spaces(arena), - before_header: &[], - after_interface_keyword: &[], - before_exposes: &[], - after_exposes: &[], - before_imports: &[], - after_imports: &[], - }, - }, - Module::App { header } => Module::App { - header: AppHeader { - name: header.name.remove_spaces(arena), - packages: header.packages.remove_spaces(arena), - imports: header.imports.remove_spaces(arena), - provides: header.provides.remove_spaces(arena), - provides_types: header.provides_types.map(|ts| ts.remove_spaces(arena)), - to: header.to.remove_spaces(arena), - before_header: &[], - after_app_keyword: &[], - before_packages: &[], - after_packages: &[], - before_imports: &[], - after_imports: &[], - before_provides: &[], - after_provides: &[], - before_to: &[], - after_to: &[], - }, - }, - Module::Platform { header } => Module::Platform { - header: PlatformHeader { - name: header.name.remove_spaces(arena), - requires: header.requires.remove_spaces(arena), - exposes: header.exposes.remove_spaces(arena), - packages: header.packages.remove_spaces(arena), - imports: header.imports.remove_spaces(arena), - provides: header.provides.remove_spaces(arena), - before_header: &[], - after_platform_keyword: &[], - before_requires: &[], - after_requires: &[], - before_exposes: &[], - after_exposes: &[], - before_packages: &[], - after_packages: &[], - before_imports: &[], - after_imports: &[], - before_provides: &[], - after_provides: &[], - }, - }, - Module::Hosted { header } => Module::Hosted { - header: HostedHeader { - name: header.name.remove_spaces(arena), - exposes: header.exposes.remove_spaces(arena), - imports: header.imports.remove_spaces(arena), - generates: header.generates.remove_spaces(arena), - generates_with: header.generates_with.remove_spaces(arena), - before_header: &[], - after_hosted_keyword: &[], - before_exposes: &[], - after_exposes: &[], - before_imports: &[], - after_imports: &[], - before_generates: &[], - after_generates: &[], - before_with: &[], - after_with: &[], - }, - }, + let header = match &self.header { + Header::Interface(header) => Header::Interface(InterfaceHeader { + before_name: &[], + name: header.name.remove_spaces(arena), + exposes: header.exposes.remove_spaces(arena), + imports: header.imports.remove_spaces(arena), + }), + Header::App(header) => Header::App(AppHeader { + before_name: &[], + name: header.name.remove_spaces(arena), + packages: header.packages.remove_spaces(arena), + imports: header.imports.remove_spaces(arena), + provides: header.provides.remove_spaces(arena), + }), + Header::Platform(header) => Header::Platform(PlatformHeader { + before_name: &[], + name: header.name.remove_spaces(arena), + requires: header.requires.remove_spaces(arena), + exposes: header.exposes.remove_spaces(arena), + packages: header.packages.remove_spaces(arena), + imports: header.imports.remove_spaces(arena), + provides: header.provides.remove_spaces(arena), + }), + Header::Hosted(header) => Header::Hosted(HostedHeader { + before_name: &[], + name: header.name.remove_spaces(arena), + exposes: header.exposes.remove_spaces(arena), + imports: header.imports.remove_spaces(arena), + generates: header.generates.remove_spaces(arena), + generates_with: header.generates_with.remove_spaces(arena), + }), + }; + Module { + comments: &[], + header, } } } diff --git a/crates/compiler/load_internal/src/file.rs b/crates/compiler/load_internal/src/file.rs index 55ed52ba71..e510a4753a 100644 --- a/crates/compiler/load_internal/src/file.rs +++ b/crates/compiler/load_internal/src/file.rs @@ -3293,25 +3293,43 @@ fn load_platform_module<'a>( pkg_module_timing.parse_header = parse_header_duration; match parsed { - Ok((ast::Module::Interface { header }, _parse_state)) => { - Err(LoadingProblem::UnexpectedHeader(format!( - "expected platform/package module, got Interface with header\n{:?}", - header - ))) - } - Ok((ast::Module::Hosted { header }, _parse_state)) => { - Err(LoadingProblem::UnexpectedHeader(format!( - "expected platform/package module, got Hosted module with header\n{:?}", - header - ))) - } - Ok((ast::Module::App { header }, _parse_state)) => { - Err(LoadingProblem::UnexpectedHeader(format!( - "expected platform/package module, got App with header\n{:?}", - header - ))) - } - Ok((ast::Module::Platform { header }, parser_state)) => { + Ok(( + ast::Module { + header: ast::Header::Interface(header), + .. + }, + _parse_state, + )) => Err(LoadingProblem::UnexpectedHeader(format!( + "expected platform/package module, got Interface with header\n{:?}", + header + ))), + Ok(( + ast::Module { + header: ast::Header::Hosted(header), + .. + }, + _parse_state, + )) => Err(LoadingProblem::UnexpectedHeader(format!( + "expected platform/package module, got Hosted module with header\n{:?}", + header + ))), + Ok(( + ast::Module { + header: ast::Header::App(header), + .. + }, + _parse_state, + )) => Err(LoadingProblem::UnexpectedHeader(format!( + "expected platform/package module, got App with header\n{:?}", + header + ))), + Ok(( + ast::Module { + header: ast::Header::Platform(header), + .. + }, + parser_state, + )) => { // make a `platform` module that ultimately exposes `main` to the host let platform_module_msg = fabricate_platform_module( arena, @@ -3356,7 +3374,13 @@ fn load_builtin_module_help<'a>( let parsed = roc_parse::module::parse_header(arena, parse_state.clone()); match parsed { - Ok((ast::Module::Interface { header }, parse_state)) => { + Ok(( + ast::Module { + header: ast::Header::Interface(header), + .. + }, + parse_state, + )) => { let info = HeaderInfo { loc_name: Loc { region: header.name.region, @@ -3366,8 +3390,8 @@ fn load_builtin_module_help<'a>( is_root_module, opt_shorthand, packages: &[], - exposes: unspace(arena, header.exposes.items), - imports: unspace(arena, header.imports.items), + exposes: unspace(arena, header.exposes.item.items), + imports: unspace(arena, header.imports.item.items), extra: HeaderFor::Builtin { generates_with: &[], }, @@ -3643,7 +3667,13 @@ fn parse_header<'a>( module_timing.parse_header = parse_header_duration; match parsed { - Ok((ast::Module::Interface { header }, parse_state)) => { + Ok(( + ast::Module { + header: ast::Header::Interface(header), + .. + }, + parse_state, + )) => { verify_interface_matches_file_path(header.name, &filename, &parse_state)?; let header_name_region = header.name.region; @@ -3657,8 +3687,8 @@ fn parse_header<'a>( is_root_module, opt_shorthand, packages: &[], - exposes: unspace(arena, header.exposes.items), - imports: unspace(arena, header.imports.items), + exposes: unspace(arena, header.exposes.item.items), + imports: unspace(arena, header.imports.item.items), extra: HeaderFor::Interface, }; @@ -3690,7 +3720,13 @@ fn parse_header<'a>( Ok((module_id, Msg::Header(header))) } - Ok((ast::Module::Hosted { header }, parse_state)) => { + Ok(( + ast::Module { + header: ast::Header::Hosted(header), + .. + }, + parse_state, + )) => { let info = HeaderInfo { loc_name: Loc { region: header.name.region, @@ -3700,11 +3736,11 @@ fn parse_header<'a>( is_root_module, opt_shorthand, packages: &[], - exposes: unspace(arena, header.exposes.items), - imports: unspace(arena, header.imports.items), + exposes: unspace(arena, header.exposes.item.items), + imports: unspace(arena, header.imports.item.items), extra: HeaderFor::Hosted { - generates: header.generates, - generates_with: unspace(arena, header.generates_with.items), + generates: header.generates.item, + generates_with: unspace(arena, header.generates_with.item.items), }, }; @@ -3718,16 +3754,27 @@ fn parse_header<'a>( Ok((module_id, Msg::Header(header))) } - Ok((ast::Module::App { header }, parse_state)) => { + Ok(( + ast::Module { + header: ast::Header::App(header), + .. + }, + parse_state, + )) => { let mut app_file_dir = filename.clone(); app_file_dir.pop(); - let packages = unspace(arena, header.packages.items); + let packages = if let Some(packages) = header.packages { + unspace(arena, packages.item.items) + } else { + &[] + }; let mut exposes = bumpalo::collections::Vec::new_in(arena); - exposes.extend(unspace(arena, header.provides.items)); - if let Some(provided_types) = header.provides_types { + exposes.extend(unspace(arena, header.provides.entries.items)); + + if let Some(provided_types) = header.provides.types { for provided_type in unspace(arena, provided_types.items) { let string: &str = provided_type.value.into(); let exposed_name = ExposedName::new(string); @@ -3748,9 +3795,13 @@ fn parse_header<'a>( opt_shorthand, packages, exposes, - imports: unspace(arena, header.imports.items), + imports: if let Some(imports) = header.imports { + unspace(arena, imports.item.items) + } else { + &[] + }, extra: HeaderFor::App { - to_platform: header.to.value, + to_platform: header.provides.to.value, }, }; @@ -3763,7 +3814,7 @@ fn parse_header<'a>( ); let app_module_header_msg = Msg::Header(resolved_header); - match header.to.value { + match header.provides.to.value { To::ExistingPackage(existing_package) => { let opt_base_package = packages.iter().find_map(|loc_package_entry| { let Loc { value, .. } = loc_package_entry; @@ -3851,7 +3902,13 @@ fn parse_header<'a>( To::NewPackage(_package_name) => Ok((module_id, app_module_header_msg)), } } - Ok((ast::Module::Platform { header }, parse_state)) => Ok(fabricate_platform_module( + Ok(( + ast::Module { + header: ast::Header::Platform(header), + .. + }, + parse_state, + )) => Ok(fabricate_platform_module( arena, None, None, @@ -4881,13 +4938,13 @@ fn fabricate_platform_module<'a>( opt_shorthand, opt_app_module_id, packages: &[], - provides: unspace(arena, header.provides.items), + provides: unspace(arena, header.provides.item.items), requires: &*arena.alloc([Loc::at( - header.requires.signature.region, - header.requires.signature.extract_spaces().item, + header.requires.item.signature.region, + header.requires.item.signature.extract_spaces().item, )]), - requires_types: unspace(arena, header.requires.rigids.items), - imports: unspace(arena, header.imports.items), + requires_types: unspace(arena, header.requires.item.rigids.items), + imports: unspace(arena, header.imports.item.items), }; send_header_two( diff --git a/crates/compiler/parse/src/ast.rs b/crates/compiler/parse/src/ast.rs index 5655b9f2e9..4f0e564b15 100644 --- a/crates/compiler/parse/src/ast.rs +++ b/crates/compiler/parse/src/ast.rs @@ -8,7 +8,7 @@ use roc_collections::soa::{EitherIndex, Index, Slice}; use roc_module::called_via::{BinOp, CalledVia, UnaryOp}; use roc_region::all::{Loc, Position, Region}; -#[derive(Debug)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Spaces<'a, T> { pub before: &'a [CommentOrNewline<'a>], pub item: T, @@ -81,11 +81,17 @@ impl<'a, T: ExtractSpaces<'a>> ExtractSpaces<'a> for Loc { } #[derive(Clone, Debug, PartialEq)] -pub enum Module<'a> { - Interface { header: InterfaceHeader<'a> }, - App { header: AppHeader<'a> }, - Platform { header: PlatformHeader<'a> }, - Hosted { header: HostedHeader<'a> }, +pub struct Module<'a> { + pub comments: &'a [CommentOrNewline<'a>], + pub header: Header<'a>, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Header<'a> { + Interface(InterfaceHeader<'a>), + App(AppHeader<'a>), + Platform(PlatformHeader<'a>), + Hosted(HostedHeader<'a>), } #[derive(Clone, Copy, Debug, PartialEq)] diff --git a/crates/compiler/parse/src/expr.rs b/crates/compiler/parse/src/expr.rs index ec09ffc84e..5513361eca 100644 --- a/crates/compiler/parse/src/expr.rs +++ b/crates/compiler/parse/src/expr.rs @@ -1089,10 +1089,10 @@ fn opaque_signature_with_space_before<'a>( EType::TIndentStart, ), ), - optional(specialize( + optional(backtrackable(specialize( EExpr::Type, space0_before_e(type_annotation::has_abilities(), EType::TIndentStart,), - )) + ))) ) } @@ -2557,7 +2557,7 @@ fn record_help<'a>() -> impl Parser< and!( // You can optionally have an identifier followed by an '&' to // make this a record update, e.g. { Foo.user & username: "blah" }. - optional(skip_second!( + optional(backtrackable(skip_second!( space0_around_ee( // We wrap the ident in an Expr here, // so that we have a Spaceable value to work with, @@ -2568,7 +2568,7 @@ fn record_help<'a>() -> impl Parser< ERecord::IndentAmpersand, ), word1(b'&', ERecord::Ampersand) - )), + ))), loc!(skip_first!( // We specifically allow space characters inside here, so that // `{ }` can be successfully parsed as an empty record, and then diff --git a/crates/compiler/parse/src/header.rs b/crates/compiler/parse/src/header.rs index 7a3280b1af..f72f2733d9 100644 --- a/crates/compiler/parse/src/header.rs +++ b/crates/compiler/parse/src/header.rs @@ -1,13 +1,13 @@ -use crate::ast::{Collection, CommentOrNewline, Spaced, StrLiteral, TypeAnnotation}; +use crate::ast::{Collection, CommentOrNewline, Spaced, Spaces, StrLiteral, TypeAnnotation}; use crate::blankspace::space0_e; use crate::ident::{lowercase_ident, UppercaseIdent}; -use crate::parser::Progress::*; +use crate::parser::{optional, then}; use crate::parser::{specialize, word1, EPackageEntry, EPackageName, Parser}; -use crate::state::State; use crate::string_literal; use bumpalo::collections::Vec; use roc_module::symbol::Symbol; use roc_region::all::Loc; +use std::fmt::Debug; #[derive(Debug)] pub enum HeaderFor<'a> { @@ -124,40 +124,61 @@ impl<'a> ExposedName<'a> { } } +pub trait Keyword: Copy + Clone + Debug { + const KEYWORD: &'static str; +} + +macro_rules! keywords { + ($($name:ident => $string:expr),* $(,)?) => { + $( + #[derive(Copy, Clone, PartialEq, Eq, Debug)] + pub struct $name; + + impl Keyword for $name { + const KEYWORD: &'static str = $string; + } + )* + } +} + +keywords! { + ExposesKeyword => "exposes", + ImportsKeyword => "imports", + WithKeyword => "with", + GeneratesKeyword => "generates", + PackageKeyword => "package", + PackagesKeyword => "packages", + RequiresKeyword => "requires", + ProvidesKeyword => "provides", + ToKeyword => "to", +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct KeywordItem<'a, K, V> { + pub keyword: Spaces<'a, K>, + pub item: V, +} + #[derive(Clone, Debug, PartialEq)] pub struct InterfaceHeader<'a> { + pub before_name: &'a [CommentOrNewline<'a>], pub name: Loc>, - pub exposes: Collection<'a, Loc>>>, - pub imports: Collection<'a, Loc>>>, - // Potential comments and newlines - these will typically all be empty. - pub before_header: &'a [CommentOrNewline<'a>], - pub after_interface_keyword: &'a [CommentOrNewline<'a>], - pub before_exposes: &'a [CommentOrNewline<'a>], - pub after_exposes: &'a [CommentOrNewline<'a>], - pub before_imports: &'a [CommentOrNewline<'a>], - pub after_imports: &'a [CommentOrNewline<'a>], + pub exposes: KeywordItem<'a, ExposesKeyword, Collection<'a, Loc>>>>, + pub imports: KeywordItem<'a, ImportsKeyword, Collection<'a, Loc>>>>, } #[derive(Clone, Debug, PartialEq)] pub struct HostedHeader<'a> { + pub before_name: &'a [CommentOrNewline<'a>], pub name: Loc>, - pub exposes: Collection<'a, Loc>>>, - pub imports: Collection<'a, Loc>>>, - pub generates: UppercaseIdent<'a>, - pub generates_with: Collection<'a, Loc>>>, + pub exposes: KeywordItem<'a, ExposesKeyword, Collection<'a, Loc>>>>, - // Potential comments and newlines - these will typically all be empty. - pub before_header: &'a [CommentOrNewline<'a>], - pub after_hosted_keyword: &'a [CommentOrNewline<'a>], - pub before_exposes: &'a [CommentOrNewline<'a>], - pub after_exposes: &'a [CommentOrNewline<'a>], - pub before_imports: &'a [CommentOrNewline<'a>], - pub after_imports: &'a [CommentOrNewline<'a>], - pub before_generates: &'a [CommentOrNewline<'a>], - pub after_generates: &'a [CommentOrNewline<'a>], - pub before_with: &'a [CommentOrNewline<'a>], - pub after_with: &'a [CommentOrNewline<'a>], + pub imports: KeywordItem<'a, ImportsKeyword, Collection<'a, Loc>>>>, + + pub generates: KeywordItem<'a, GeneratesKeyword, UppercaseIdent<'a>>, + pub generates_with: + KeywordItem<'a, WithKeyword, Collection<'a, Loc>>>>, } #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -168,42 +189,39 @@ pub enum To<'a> { #[derive(Clone, Debug, PartialEq)] pub struct AppHeader<'a> { + pub before_name: &'a [CommentOrNewline<'a>], pub name: Loc>, - pub packages: Collection<'a, Loc>>>, - pub imports: Collection<'a, Loc>>>, - pub provides: Collection<'a, Loc>>>, - pub provides_types: Option>>>>, - pub to: Loc>, - // Potential comments and newlines - these will typically all be empty. - pub before_header: &'a [CommentOrNewline<'a>], - pub after_app_keyword: &'a [CommentOrNewline<'a>], - pub before_packages: &'a [CommentOrNewline<'a>], - pub after_packages: &'a [CommentOrNewline<'a>], - pub before_imports: &'a [CommentOrNewline<'a>], - pub after_imports: &'a [CommentOrNewline<'a>], - pub before_provides: &'a [CommentOrNewline<'a>], - pub after_provides: &'a [CommentOrNewline<'a>], - pub before_to: &'a [CommentOrNewline<'a>], - pub after_to: &'a [CommentOrNewline<'a>], + pub packages: + Option>>>>>, + pub imports: + Option>>>>>, + pub provides: ProvidesTo<'a>, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct ProvidesTo<'a> { + pub provides_keyword: Spaces<'a, ProvidesKeyword>, + pub entries: Collection<'a, Loc>>>, + pub types: Option>>>>, + + pub to_keyword: Spaces<'a, ToKeyword>, + pub to: Loc>, } #[derive(Clone, Debug, PartialEq)] pub struct PackageHeader<'a> { + pub before_name: &'a [CommentOrNewline<'a>], pub name: Loc>, - pub exposes: Vec<'a, Loc>>>, - pub packages: Vec<'a, (Loc<&'a str>, Loc>)>, - pub imports: Vec<'a, Loc>>, - // Potential comments and newlines - these will typically all be empty. - pub before_header: &'a [CommentOrNewline<'a>], - pub after_package_keyword: &'a [CommentOrNewline<'a>], - pub before_exposes: &'a [CommentOrNewline<'a>], - pub after_exposes: &'a [CommentOrNewline<'a>], - pub before_packages: &'a [CommentOrNewline<'a>], - pub after_packages: &'a [CommentOrNewline<'a>], - pub before_imports: &'a [CommentOrNewline<'a>], - pub after_imports: &'a [CommentOrNewline<'a>], + pub exposes_keyword: Spaces<'a, ExposesKeyword>, + pub exposes: Vec<'a, Loc>>>, + + pub packages_keyword: Spaces<'a, PackagesKeyword>, + pub packages: Vec<'a, (Loc<&'a str>, Loc>)>, + + pub imports_keyword: Spaces<'a, ImportsKeyword>, + pub imports: Vec<'a, Loc>>, } #[derive(Clone, Debug, PartialEq)] @@ -214,26 +232,16 @@ pub struct PlatformRequires<'a> { #[derive(Clone, Debug, PartialEq)] pub struct PlatformHeader<'a> { + pub before_name: &'a [CommentOrNewline<'a>], pub name: Loc>, - pub requires: PlatformRequires<'a>, - pub exposes: Collection<'a, Loc>>>, - pub packages: Collection<'a, Loc>>>, - pub imports: Collection<'a, Loc>>>, - pub provides: Collection<'a, Loc>>>, - // Potential comments and newlines - these will typically all be empty. - pub before_header: &'a [CommentOrNewline<'a>], - pub after_platform_keyword: &'a [CommentOrNewline<'a>], - pub before_requires: &'a [CommentOrNewline<'a>], - pub after_requires: &'a [CommentOrNewline<'a>], - pub before_exposes: &'a [CommentOrNewline<'a>], - pub after_exposes: &'a [CommentOrNewline<'a>], - pub before_packages: &'a [CommentOrNewline<'a>], - pub after_packages: &'a [CommentOrNewline<'a>], - pub before_imports: &'a [CommentOrNewline<'a>], - pub after_imports: &'a [CommentOrNewline<'a>], - pub before_provides: &'a [CommentOrNewline<'a>], - pub after_provides: &'a [CommentOrNewline<'a>], + pub requires: KeywordItem<'a, RequiresKeyword, PlatformRequires<'a>>, + pub exposes: KeywordItem<'a, ExposesKeyword, Collection<'a, Loc>>>>, + pub packages: + KeywordItem<'a, PackagesKeyword, Collection<'a, Loc>>>>, + pub imports: KeywordItem<'a, ImportsKeyword, Collection<'a, Loc>>>>, + pub provides: + KeywordItem<'a, ProvidesKeyword, Collection<'a, Loc>>>>, } #[derive(Copy, Clone, Debug, PartialEq)] @@ -270,50 +278,47 @@ pub struct PackageEntry<'a> { } pub fn package_entry<'a>() -> impl Parser<'a, Spaced<'a, PackageEntry<'a>>, EPackageEntry<'a>> { - move |arena, state, min_indent| { + map!( // You may optionally have a package shorthand, // e.g. "uc" in `uc: roc/unicode 1.0.0` // // (Indirect dependencies don't have a shorthand.) - let (_, opt_shorthand, state) = maybe!(and!( - skip_second!( - specialize(|_, pos| EPackageEntry::Shorthand(pos), lowercase_ident()), - word1(b':', EPackageEntry::Colon) - ), - space0_e(EPackageEntry::IndentPackage) - )) - .parse(arena, state, min_indent)?; - - let (_, package_or_path, state) = + and!( + optional(and!( + skip_second!( + specialize(|_, pos| EPackageEntry::Shorthand(pos), lowercase_ident()), + word1(b':', EPackageEntry::Colon) + ), + space0_e(EPackageEntry::IndentPackage) + )), loc!(specialize(EPackageEntry::BadPackage, package_name())) - .parse(arena, state, min_indent)?; + ), + move |(opt_shorthand, package_or_path)| { + let entry = match opt_shorthand { + Some((shorthand, spaces_after_shorthand)) => PackageEntry { + shorthand, + spaces_after_shorthand, + package_name: package_or_path, + }, + None => PackageEntry { + shorthand: "", + spaces_after_shorthand: &[], + package_name: package_or_path, + }, + }; - let entry = match opt_shorthand { - Some((shorthand, spaces_after_shorthand)) => PackageEntry { - shorthand, - spaces_after_shorthand, - package_name: package_or_path, - }, - None => PackageEntry { - shorthand: "", - spaces_after_shorthand: &[], - package_name: package_or_path, - }, - }; - - Ok((MadeProgress, Spaced::Item(entry), state)) - } + Spaced::Item(entry) + } + ) } pub fn package_name<'a>() -> impl Parser<'a, PackageName<'a>, EPackageName<'a>> { - move |arena, state: State<'a>, min_indent: u32| { - let pos = state.pos(); - specialize(EPackageName::BadPath, string_literal::parse()) - .parse(arena, state, min_indent) - .and_then(|(progress, text, next_state)| match text { - StrLiteral::PlainLine(text) => Ok((progress, PackageName(text), next_state)), - StrLiteral::Line(_) => Err((progress, EPackageName::Escapes(pos))), - StrLiteral::Block(_) => Err((progress, EPackageName::Multiline(pos))), - }) - } + then( + loc!(specialize(EPackageName::BadPath, string_literal::parse())), + move |_arena, state, progress, text| match text.value { + StrLiteral::PlainLine(text) => Ok((progress, PackageName(text), state)), + StrLiteral::Line(_) => Err((progress, EPackageName::Escapes(text.region.start()))), + StrLiteral::Block(_) => Err((progress, EPackageName::Multiline(text.region.start()))), + }, + ) } diff --git a/crates/compiler/parse/src/module.rs b/crates/compiler/parse/src/module.rs index 82c37451fe..6f290e57f5 100644 --- a/crates/compiler/parse/src/module.rs +++ b/crates/compiler/parse/src/module.rs @@ -1,8 +1,10 @@ -use crate::ast::{Collection, CommentOrNewline, Defs, Module, Spaced}; +use crate::ast::{Collection, Defs, Header, Module, Spaced, Spaces}; use crate::blankspace::{space0_around_ee, space0_before_e, space0_e}; use crate::header::{ - package_entry, package_name, AppHeader, ExposedName, HostedHeader, ImportsEntry, - InterfaceHeader, ModuleName, PackageEntry, PlatformHeader, PlatformRequires, To, TypedIdent, + package_entry, package_name, AppHeader, ExposedName, ExposesKeyword, GeneratesKeyword, + HostedHeader, ImportsEntry, ImportsKeyword, InterfaceHeader, Keyword, KeywordItem, ModuleName, + PackageEntry, PackagesKeyword, PlatformHeader, PlatformRequires, ProvidesKeyword, ProvidesTo, + RequiresKeyword, To, ToKeyword, TypedIdent, WithKeyword, }; use crate::ident::{self, lowercase_ident, unqualified_ident, uppercase, UppercaseIdent}; use crate::parser::Progress::{self, *}; @@ -48,132 +50,63 @@ pub fn parse_header<'a>( fn header<'a>() -> impl Parser<'a, Module<'a>, EHeader<'a>> { use crate::parser::keyword_e; - type Clos<'b> = Box<(dyn FnOnce(&'b [CommentOrNewline]) -> Module<'b> + 'b)>; - - map!( - and!( - space0_e(EHeader::IndentStart), - one_of![ - map!( - skip_first!( - keyword_e("interface", EHeader::Start), - increment_min_indent(interface_header()) - ), - |mut header: InterfaceHeader<'a>| -> Clos<'a> { - Box::new(|spaces| { - header.before_header = spaces; - Module::Interface { header } - }) - } + record!(Module { + comments: space0_e(EHeader::IndentStart), + header: one_of![ + map!( + skip_first!( + keyword_e("interface", EHeader::Start), + increment_min_indent(interface_header()) ), - map!( - skip_first!( - keyword_e("app", EHeader::Start), - increment_min_indent(app_header()) - ), - |mut header: AppHeader<'a>| -> Clos<'a> { - Box::new(|spaces| { - header.before_header = spaces; - Module::App { header } - }) - } + Header::Interface + ), + map!( + skip_first!( + keyword_e("app", EHeader::Start), + increment_min_indent(app_header()) ), - map!( - skip_first!( - keyword_e("platform", EHeader::Start), - increment_min_indent(platform_header()) - ), - |mut header: PlatformHeader<'a>| -> Clos<'a> { - Box::new(|spaces| { - header.before_header = spaces; - Module::Platform { header } - }) - } + Header::App + ), + map!( + skip_first!( + keyword_e("platform", EHeader::Start), + increment_min_indent(platform_header()) ), - map!( - skip_first!( - keyword_e("hosted", EHeader::Start), - increment_min_indent(hosted_header()) - ), - |mut header: HostedHeader<'a>| -> Clos<'a> { - Box::new(|spaces| { - header.before_header = spaces; - Module::Hosted { header } - }) - } - ) - ] - ), - |(spaces, make_header): (&'a [CommentOrNewline], Clos<'a>)| { make_header(spaces) } - ) + Header::Platform + ), + map!( + skip_first!( + keyword_e("hosted", EHeader::Start), + increment_min_indent(hosted_header()) + ), + Header::Hosted + ), + ] + }) } #[inline(always)] fn interface_header<'a>() -> impl Parser<'a, InterfaceHeader<'a>, EHeader<'a>> { - |arena, state, min_indent: u32| { - let (_, after_interface_keyword, state) = - space0_e(EHeader::IndentStart).parse(arena, state, min_indent)?; - let (_, name, state) = - loc!(module_name_help(EHeader::ModuleName)).parse(arena, state, min_indent)?; - - let (_, ((before_exposes, after_exposes), exposes), state) = - specialize(EHeader::Exposes, exposes_values()).parse(arena, state, min_indent)?; - let (_, ((before_imports, after_imports), imports), state) = - specialize(EHeader::Imports, imports()).parse(arena, state, min_indent)?; - - let header = InterfaceHeader { - name, - exposes, - imports, - before_header: &[] as &[_], - after_interface_keyword, - before_exposes, - after_exposes, - before_imports, - after_imports, - }; - - Ok((MadeProgress, header, state)) - } + record!(InterfaceHeader { + before_name: space0_e(EHeader::IndentStart), + name: loc!(module_name_help(EHeader::ModuleName)), + exposes: specialize(EHeader::Exposes, exposes_values()), + imports: specialize(EHeader::Imports, imports()), + }) + .trace("interface_header") } #[inline(always)] fn hosted_header<'a>() -> impl Parser<'a, HostedHeader<'a>, EHeader<'a>> { - |arena, state, min_indent: u32| { - let (_, after_hosted_keyword, state) = - space0_e(EHeader::IndentStart).parse(arena, state, min_indent)?; - let (_, name, state) = - loc!(module_name_help(EHeader::ModuleName)).parse(arena, state, min_indent)?; - - let (_, ((before_exposes, after_exposes), exposes), state) = - specialize(EHeader::Exposes, exposes_values()).parse(arena, state, min_indent)?; - let (_, ((before_imports, after_imports), imports), state) = - specialize(EHeader::Imports, imports()).parse(arena, state, min_indent)?; - let (_, ((before_generates, after_generates), generates), state) = - specialize(EHeader::Generates, generates()).parse(arena, state, min_indent)?; - let (_, ((before_with, after_with), generates_with), state) = - specialize(EHeader::GeneratesWith, generates_with()).parse(arena, state, min_indent)?; - - let header = HostedHeader { - name, - exposes, - imports, - generates, - generates_with, - before_header: &[] as &[_], - after_hosted_keyword, - before_exposes, - after_exposes, - before_imports, - after_imports, - before_generates, - after_generates, - before_with, - after_with, - }; - - Ok((MadeProgress, header, state)) - } + record!(HostedHeader { + before_name: space0_e(EHeader::IndentStart), + name: loc!(module_name_help(EHeader::ModuleName)), + exposes: specialize(EHeader::Exposes, exposes_values()), + imports: specialize(EHeader::Imports, imports()), + generates: specialize(EHeader::Generates, generates()), + generates_with: specialize(EHeader::GeneratesWith, generates_with()), + }) + .trace("hosted_header") } fn chomp_module_name(buffer: &[u8]) -> Result<&str, Progress> { @@ -237,127 +170,31 @@ fn module_name<'a>() -> impl Parser<'a, ModuleName<'a>, ()> { #[inline(always)] fn app_header<'a>() -> impl Parser<'a, AppHeader<'a>, EHeader<'a>> { - |arena, state, min_indent: u32| { - let (_, after_app_keyword, state) = - space0_e(EHeader::IndentStart).parse(arena, state, min_indent)?; - let (_, name, state) = loc!(crate::parser::specialize( + record!(AppHeader { + before_name: space0_e(EHeader::IndentStart), + name: loc!(crate::parser::specialize( EHeader::AppName, string_literal::parse() - )) - .parse(arena, state, min_indent)?; - - let (_, opt_pkgs, state) = - maybe!(specialize(EHeader::Packages, packages())).parse(arena, state, min_indent)?; - let (_, opt_imports, state) = - maybe!(specialize(EHeader::Imports, imports())).parse(arena, state, min_indent)?; - let (_, provides, state) = - specialize(EHeader::Provides, provides_to()).parse(arena, state, min_indent)?; - - let (before_packages, after_packages, packages) = match opt_pkgs { - Some(pkgs) => { - let pkgs: Packages<'a> = pkgs; // rustc must be told the type here - - ( - pkgs.before_packages_keyword, - pkgs.after_packages_keyword, - pkgs.entries, - ) - } - None => (&[] as _, &[] as _, Collection::empty()), - }; - - // rustc must be told the type here - #[allow(clippy::type_complexity)] - let opt_imports: Option<( - (&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]), - Collection<'a, Loc>>>, - )> = opt_imports; - - let ((before_imports, after_imports), imports) = - opt_imports.unwrap_or_else(|| ((&[] as _, &[] as _), Collection::empty())); - let provides: ProvidesTo<'a> = provides; // rustc must be told the type here - - let header = AppHeader { - name, - packages, - imports, - provides: provides.entries, - provides_types: provides.types, - to: provides.to, - before_header: &[] as &[_], - after_app_keyword, - before_packages, - after_packages, - before_imports, - after_imports, - before_provides: provides.before_provides_keyword, - after_provides: provides.after_provides_keyword, - before_to: provides.before_to_keyword, - after_to: provides.after_to_keyword, - }; - - Ok((MadeProgress, header, state)) - } + )), + packages: optional(specialize(EHeader::Packages, packages())), + imports: optional(specialize(EHeader::Imports, imports())), + provides: specialize(EHeader::Provides, provides_to()), + }) + .trace("app_header") } #[inline(always)] fn platform_header<'a>() -> impl Parser<'a, PlatformHeader<'a>, EHeader<'a>> { - |arena, state, min_indent: u32| { - let (_, after_platform_keyword, state) = - space0_e(EHeader::IndentStart).parse(arena, state, min_indent)?; - let (_, name, state) = loc!(specialize(EHeader::PlatformName, package_name())) - .parse(arena, state, min_indent)?; - - let (_, ((before_requires, after_requires), requires), state) = - specialize(EHeader::Requires, requires()).parse(arena, state, min_indent)?; - - let (_, ((before_exposes, after_exposes), exposes), state) = - specialize(EHeader::Exposes, exposes_modules()).parse(arena, state, min_indent)?; - - let (_, packages, state) = - specialize(EHeader::Packages, packages()).parse(arena, state, min_indent)?; - - let (_, ((before_imports, after_imports), imports), state) = - specialize(EHeader::Imports, imports()).parse(arena, state, min_indent)?; - - let (_, ((before_provides, after_provides), (provides, _provides_type)), state) = - specialize(EHeader::Provides, provides_without_to()).parse(arena, state, min_indent)?; - - let header = PlatformHeader { - name, - requires, - exposes, - packages: packages.entries, - imports, - provides, - before_header: &[] as &[_], - after_platform_keyword, - before_requires, - after_requires, - before_exposes, - after_exposes, - before_packages: packages.before_packages_keyword, - after_packages: packages.after_packages_keyword, - before_imports, - after_imports, - before_provides, - after_provides, - }; - - Ok((MadeProgress, header, state)) - } -} - -#[derive(Debug)] -struct ProvidesTo<'a> { - entries: Collection<'a, Loc>>>, - types: Option>>>>, - to: Loc>, - - before_provides_keyword: &'a [CommentOrNewline<'a>], - after_provides_keyword: &'a [CommentOrNewline<'a>], - before_to_keyword: &'a [CommentOrNewline<'a>], - after_to_keyword: &'a [CommentOrNewline<'a>], + record!(PlatformHeader { + before_name: space0_e(EHeader::IndentStart), + name: loc!(specialize(EHeader::PlatformName, package_name())), + requires: specialize(EHeader::Requires, requires()), + exposes: specialize(EHeader::Exposes, exposes_modules()), + packages: specialize(EHeader::Packages, packages()), + imports: specialize(EHeader::Imports, imports()), + provides: specialize(EHeader::Provides, provides_exposed()), + }) + .trace("platform_header") } fn provides_to_package<'a>() -> impl Parser<'a, To<'a>, EProvides<'a>> { @@ -372,68 +209,54 @@ fn provides_to_package<'a>() -> impl Parser<'a, To<'a>, EProvides<'a>> { #[inline(always)] fn provides_to<'a>() -> impl Parser<'a, ProvidesTo<'a>, EProvides<'a>> { - map!( - and!( - provides_without_to(), - and!( - spaces_around_keyword( - "to", - EProvides::To, - EProvides::IndentTo, - EProvides::IndentListStart - ), - loc!(provides_to_package()) - ) - ), - |( - ((before_provides_keyword, after_provides_keyword), (entries, provides_types)), - ((before_to_keyword, after_to_keyword), to), - )| { - ProvidesTo { - entries, - types: provides_types, - to, - before_provides_keyword, - after_provides_keyword, - before_to_keyword, - after_to_keyword, - } - } - ) -} - -#[inline(always)] -fn provides_without_to<'a>() -> impl Parser< - 'a, - ( - (&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]), - ( - Collection<'a, Loc>>>, - Option>>>>, - ), - ), - EProvides<'a>, -> { - and!( - spaces_around_keyword( - "provides", + record!(ProvidesTo { + provides_keyword: spaces_around_keyword( + ProvidesKeyword, EProvides::Provides, EProvides::IndentProvides, EProvides::IndentListStart ), - and!( - collection_trailing_sep_e!( - word1(b'[', EProvides::ListStart), - exposes_entry(EProvides::Identifier), - word1(b',', EProvides::ListEnd), - word1(b']', EProvides::ListEnd), - EProvides::IndentListEnd, - Spaced::SpaceBefore - ), - // Optionally - optional(provides_types()) - ) - ) + entries: collection_trailing_sep_e!( + word1(b'[', EProvides::ListStart), + exposes_entry(EProvides::Identifier), + word1(b',', EProvides::ListEnd), + word1(b']', EProvides::ListEnd), + EProvides::IndentListEnd, + Spaced::SpaceBefore + ), + types: optional(backtrackable(provides_types())), + to_keyword: spaces_around_keyword( + ToKeyword, + EProvides::To, + EProvides::IndentTo, + EProvides::IndentListStart + ), + to: loc!(provides_to_package()), + }) + .trace("provides_to") +} + +fn provides_exposed<'a>() -> impl Parser< + 'a, + KeywordItem<'a, ProvidesKeyword, Collection<'a, Loc>>>>, + EProvides<'a>, +> { + record!(KeywordItem { + keyword: spaces_around_keyword( + ProvidesKeyword, + EProvides::Provides, + EProvides::IndentProvides, + EProvides::IndentListStart + ), + item: collection_trailing_sep_e!( + word1(b'[', EProvides::ListStart), + exposes_entry(EProvides::Identifier), + word1(b',', EProvides::ListEnd), + word1(b']', EProvides::ListEnd), + EProvides::IndentListEnd, + Spaced::SpaceBefore + ), + }) } #[inline(always)] @@ -491,34 +314,25 @@ where } #[inline(always)] -fn requires<'a>() -> impl Parser< - 'a, - ( - (&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]), - PlatformRequires<'a>, - ), - ERequires<'a>, -> { - and!( - spaces_around_keyword( - "requires", +fn requires<'a>( +) -> impl Parser<'a, KeywordItem<'a, RequiresKeyword, PlatformRequires<'a>>, ERequires<'a>> { + record!(KeywordItem { + keyword: spaces_around_keyword( + RequiresKeyword, ERequires::Requires, ERequires::IndentRequires, ERequires::IndentListStart ), - platform_requires() - ) + item: platform_requires(), + }) } #[inline(always)] fn platform_requires<'a>() -> impl Parser<'a, PlatformRequires<'a>, ERequires<'a>> { - map!( - and!( - skip_second!(requires_rigids(), space0_e(ERequires::ListStart)), - requires_typed_ident() - ), - |(rigids, signature)| { PlatformRequires { rigids, signature } } - ) + record!(PlatformRequires { + rigids: skip_second!(requires_rigids(), space0_e(ERequires::ListStart)), + signature: requires_typed_ident() + }) } #[inline(always)] @@ -555,20 +369,17 @@ fn requires_typed_ident<'a>() -> impl Parser<'a, Loc>> #[inline(always)] fn exposes_values<'a>() -> impl Parser< 'a, - ( - (&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]), - Collection<'a, Loc>>>, - ), + KeywordItem<'a, ExposesKeyword, Collection<'a, Loc>>>>, EExposes, > { - and!( - spaces_around_keyword( - "exposes", + record!(KeywordItem { + keyword: spaces_around_keyword( + ExposesKeyword, EExposes::Exposes, EExposes::IndentExposes, EExposes::IndentListStart ), - collection_trailing_sep_e!( + item: collection_trailing_sep_e!( word1(b'[', EExposes::ListStart), exposes_entry(EExposes::Identifier), word1(b',', EExposes::ListEnd), @@ -576,52 +387,58 @@ fn exposes_values<'a>() -> impl Parser< EExposes::IndentListEnd, Spaced::SpaceBefore ) - ) + }) } -fn spaces_around_keyword<'a, E>( - keyword: &'static str, +fn spaces_around_keyword<'a, K: Keyword, E>( + keyword_item: K, expectation: fn(Position) -> E, indent_problem1: fn(Position) -> E, indent_problem2: fn(Position) -> E, -) -> impl Parser<'a, (&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]), E> +) -> impl Parser<'a, Spaces<'a, K>, E> where E: 'a + SpaceProblem, { - and!( - skip_second!( - backtrackable(space0_e(indent_problem1)), - crate::parser::keyword_e(keyword, expectation) + map!( + and!( + skip_second!( + backtrackable(space0_e(indent_problem1)), + crate::parser::keyword_e(K::KEYWORD, expectation) + ), + space0_e(indent_problem2) ), - space0_e(indent_problem2) + |(before, after)| { + Spaces { + before, + item: keyword_item, + after, + } + } ) } #[inline(always)] fn exposes_modules<'a>() -> impl Parser< 'a, - ( - (&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]), - Collection<'a, Loc>>>, - ), + KeywordItem<'a, ExposesKeyword, Collection<'a, Loc>>>>, EExposes, > { - and!( - spaces_around_keyword( - "exposes", + record!(KeywordItem { + keyword: spaces_around_keyword( + ExposesKeyword, EExposes::Exposes, EExposes::IndentExposes, EExposes::IndentListStart ), - collection_trailing_sep_e!( + item: collection_trailing_sep_e!( word1(b'[', EExposes::ListStart), exposes_module(EExposes::Identifier), word1(b',', EExposes::ListEnd), word1(b']', EExposes::ListEnd), EExposes::IndentListEnd, Spaced::SpaceBefore - ) - ) + ), + }) } fn exposes_module<'a, F, E>( @@ -638,82 +455,58 @@ where )) } -#[derive(Debug)] -struct Packages<'a> { - entries: Collection<'a, Loc>>>, - before_packages_keyword: &'a [CommentOrNewline<'a>], - after_packages_keyword: &'a [CommentOrNewline<'a>], -} - #[inline(always)] -fn packages<'a>() -> impl Parser<'a, Packages<'a>, EPackages<'a>> { - map!( - and!( - spaces_around_keyword( - "packages", - EPackages::Packages, - EPackages::IndentPackages, - EPackages::IndentListStart - ), - collection_trailing_sep_e!( - word1(b'{', EPackages::ListStart), - specialize(EPackages::PackageEntry, loc!(package_entry())), - word1(b',', EPackages::ListEnd), - word1(b'}', EPackages::ListEnd), - EPackages::IndentListEnd, - Spaced::SpaceBefore - ) - ), - |((before_packages_keyword, after_packages_keyword), entries): ( - (_, _), - Collection<'a, _> - )| { - Packages { - entries, - before_packages_keyword, - after_packages_keyword, - } - } - ) -} - -#[inline(always)] -fn generates<'a>() -> impl Parser< +fn packages<'a>() -> impl Parser< 'a, - ( - (&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]), - UppercaseIdent<'a>, - ), - EGenerates, + KeywordItem<'a, PackagesKeyword, Collection<'a, Loc>>>>, + EPackages<'a>, > { - and!( - spaces_around_keyword( - "generates", + record!(KeywordItem { + keyword: spaces_around_keyword( + PackagesKeyword, + EPackages::Packages, + EPackages::IndentPackages, + EPackages::IndentListStart + ), + item: collection_trailing_sep_e!( + word1(b'{', EPackages::ListStart), + specialize(EPackages::PackageEntry, loc!(package_entry())), + word1(b',', EPackages::ListEnd), + word1(b'}', EPackages::ListEnd), + EPackages::IndentListEnd, + Spaced::SpaceBefore + ) + }) +} + +#[inline(always)] +fn generates<'a>( +) -> impl Parser<'a, KeywordItem<'a, GeneratesKeyword, UppercaseIdent<'a>>, EGenerates> { + record!(KeywordItem { + keyword: spaces_around_keyword( + GeneratesKeyword, EGenerates::Generates, EGenerates::IndentGenerates, EGenerates::IndentTypeStart ), - specialize(|(), pos| EGenerates::Identifier(pos), uppercase()) - ) + item: specialize(|(), pos| EGenerates::Identifier(pos), uppercase()) + }) } #[inline(always)] fn generates_with<'a>() -> impl Parser< 'a, - ( - (&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]), - Collection<'a, Loc>>>, - ), + KeywordItem<'a, WithKeyword, Collection<'a, Loc>>>>, EGeneratesWith, > { - and!( - spaces_around_keyword( - "with", + record!(KeywordItem { + keyword: spaces_around_keyword( + WithKeyword, EGeneratesWith::With, EGeneratesWith::IndentWith, EGeneratesWith::IndentListStart ), - collection_trailing_sep_e!( + item: collection_trailing_sep_e!( word1(b'[', EGeneratesWith::ListStart), exposes_entry(EGeneratesWith::Identifier), word1(b',', EGeneratesWith::ListEnd), @@ -721,26 +514,23 @@ fn generates_with<'a>() -> impl Parser< EGeneratesWith::IndentListEnd, Spaced::SpaceBefore ) - ) + }) } #[inline(always)] fn imports<'a>() -> impl Parser< 'a, - ( - (&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]), - Collection<'a, Loc>>>, - ), + KeywordItem<'a, ImportsKeyword, Collection<'a, Loc>>>>, EImports, > { - and!( - spaces_around_keyword( - "imports", + record!(KeywordItem { + keyword: spaces_around_keyword( + ImportsKeyword, EImports::Imports, EImports::IndentImports, EImports::IndentListStart ), - collection_trailing_sep_e!( + item: collection_trailing_sep_e!( word1(b'[', EImports::ListStart), loc!(imports_entry()), word1(b',', EImports::ListEnd), @@ -748,7 +538,8 @@ fn imports<'a>() -> impl Parser< EImports::IndentListEnd, Spaced::SpaceBefore ) - ) + }) + .trace("imports") } #[inline(always)] @@ -810,15 +601,15 @@ fn imports_entry<'a>() -> impl Parser<'a, Spaced<'a, ImportsEntry<'a>>, EImports and!( and!( // e.g. `pf.` - maybe!(skip_second!( + optional(backtrackable(skip_second!( shortname(), word1(b'.', EImports::ShorthandDot) - )), + ))), // e.g. `Task` module_name_help(EImports::ModuleName) ), // e.g. `.{ Task, after}` - maybe!(skip_first!( + optional(skip_first!( word1(b'.', EImports::ExposingDot), collection_trailing_sep_e!( word1(b'{', EImports::SetStart), @@ -842,4 +633,5 @@ fn imports_entry<'a>() -> impl Parser<'a, Spaced<'a, ImportsEntry<'a>>, EImports Spaced::Item(entry) } ) + .trace("imports_entry") } diff --git a/crates/compiler/parse/src/parser.rs b/crates/compiler/parse/src/parser.rs index 95f5afd263..f5479cc568 100644 --- a/crates/compiler/parse/src/parser.rs +++ b/crates/compiler/parse/src/parser.rs @@ -852,13 +852,14 @@ where self.message ); + let previous_state = state.clone(); INDENT.with(|i| *i.borrow_mut() += 1); let res = self.parser.parse(arena, state, min_indent); INDENT.with(|i| *i.borrow_mut() = cur_indent); let (progress, value, state) = match &res { Ok((progress, result, state)) => (progress, Ok(result), state), - Err((progress, error)) => (progress, Err(error), state), + Err((progress, error)) => (progress, Err(error), &previous_state), }; println!( @@ -1229,11 +1230,8 @@ where match parser.parse(arena, state, min_indent) { Ok((progress, out1, state)) => Ok((progress, Some(out1), state)), - Err((_, _)) => { - // NOTE this will backtrack - // TODO can we get rid of some of the potential backtracking? - Ok((NoProgress, None, original_state)) - } + Err((MadeProgress, e)) => Err((MadeProgress, e)), + Err((NoProgress, _)) => Ok((NoProgress, None, original_state)), } } } @@ -1435,6 +1433,25 @@ macro_rules! and { }; } +/// Take as input something that looks like a struct literal where values are parsers +/// and return a parser that runs each parser and returns a struct literal with the +/// results. +#[macro_export] +macro_rules! record { + ($name:ident $(:: $name_ext:ident)* { $($field:ident: $parser:expr),* $(,)? }) => { + move |arena: &'a bumpalo::Bump, state: $crate::state::State<'a>, min_indent: u32| { + let mut state = state; + let mut progress = NoProgress; + $( + let (new_progress, $field, new_state) = $parser.parse(arena, state, min_indent)?; + state = new_state; + progress = progress.or(new_progress); + )* + Ok((progress, $name $(:: $name_ext)* { $($field),* }, state)) + } + }; +} + /// Similar to `and`, but we modify the min_indent of the second parser to be /// 1 greater than the line_indent() at the start of the first parser. #[macro_export] @@ -1507,21 +1524,6 @@ macro_rules! one_of { }; } -#[macro_export] -macro_rules! maybe { - ($p1:expr) => { - move |arena: &'a bumpalo::Bump, state: $crate::state::State<'a>, min_indent: u32| { - let original_state = state.clone(); - - match $p1.parse(arena, state, min_indent) { - Ok((progress, value, state)) => Ok((progress, Some(value), state)), - Err((MadeProgress, fail)) => Err((MadeProgress, fail)), - Err((NoProgress, _)) => Ok((NoProgress, None, original_state)), - } - } - }; -} - #[macro_export] macro_rules! one_of_with_error { ($toerror:expr; $p1:expr) => { diff --git a/crates/compiler/parse/src/type_annotation.rs b/crates/compiler/parse/src/type_annotation.rs index a965c7677f..f91b53a5e2 100644 --- a/crates/compiler/parse/src/type_annotation.rs +++ b/crates/compiler/parse/src/type_annotation.rs @@ -349,23 +349,20 @@ fn record_type_field<'a>() -> impl Parser<'a, AssignedField<'a, TypeAnnotation<' fn record_type<'a>( stop_at_surface_has: bool, ) -> impl Parser<'a, TypeAnnotation<'a>, ETypeRecord<'a>> { - map!( - and!( - collection_trailing_sep_e!( - word1(b'{', ETypeRecord::Open), - loc!(record_type_field()), - word1(b',', ETypeRecord::End), - word1(b'}', ETypeRecord::End), - ETypeRecord::IndentEnd, - AssignedField::SpaceBefore - ), - optional(allocated(specialize_ref( - ETypeRecord::Type, - term(stop_at_surface_has) - ))) + record!(TypeAnnotation::Record { + fields: collection_trailing_sep_e!( + word1(b'{', ETypeRecord::Open), + loc!(record_type_field()), + word1(b',', ETypeRecord::End), + word1(b'}', ETypeRecord::End), + ETypeRecord::IndentEnd, + AssignedField::SpaceBefore ), - |(fields, ext)| { TypeAnnotation::Record { fields, ext } } - ) + ext: optional(allocated(specialize_ref( + ETypeRecord::Type, + term(stop_at_surface_has) + ))) + }) .trace("type_annotation:record_type") } @@ -515,29 +512,26 @@ pub fn has_abilities<'a>() -> impl Parser<'a, Loc>, EType<'a>> } fn parse_has_ability<'a>() -> impl Parser<'a, HasAbility<'a>, EType<'a>> { - increment_min_indent(map!( - and!( - loc!(specialize(EType::TApply, concrete_type())), - optional(space0_before_e( - loc!(map!( - specialize( - EType::TAbilityImpl, - collection_trailing_sep_e!( - word1(b'{', ETypeAbilityImpl::Open), - specialize(|e: ERecord<'_>, _| e.into(), loc!(record_value_field())), - word1(b',', ETypeAbilityImpl::End), - word1(b'}', ETypeAbilityImpl::End), - ETypeAbilityImpl::IndentEnd, - AssignedField::SpaceBefore - ) - ), - HasImpls::HasImpls - )), - EType::TIndentEnd - )) - ), - |(ability, impls): (_, Option<_>)| { HasAbility::HasAbility { ability, impls } } - )) + increment_min_indent(record!(HasAbility::HasAbility { + ability: loc!(specialize(EType::TApply, concrete_type())), + impls: optional(backtrackable(space0_before_e( + loc!(map!( + specialize( + EType::TAbilityImpl, + collection_trailing_sep_e!( + word1(b'{', ETypeAbilityImpl::Open), + specialize(|e: ERecord<'_>, _| e.into(), loc!(record_value_field())), + word1(b',', ETypeAbilityImpl::End), + word1(b'}', ETypeAbilityImpl::End), + ETypeAbilityImpl::IndentEnd, + AssignedField::SpaceBefore + ) + ), + HasImpls::HasImpls + )), + EType::TIndentEnd + ))) + })) } fn expression<'a>( @@ -594,10 +588,10 @@ fn expression<'a>( } Err(err) => { if !is_trailing_comma_valid { - let (_, comma, _) = optional(skip_first!( + let (_, comma, _) = optional(backtrackable(skip_first!( space0_e(EType::TIndentStart), word1(b',', EType::TStart) - )) + ))) .trace("check trailing comma") .parse(arena, state.clone(), min_indent)?; diff --git a/crates/compiler/parse/tests/snapshots/pass/empty_app_header.header.result-ast b/crates/compiler/parse/tests/snapshots/pass/empty_app_header.header.result-ast index d0ba5ec7b0..27e3c0ae46 100644 --- a/crates/compiler/parse/tests/snapshots/pass/empty_app_header.header.result-ast +++ b/crates/compiler/parse/tests/snapshots/pass/empty_app_header.header.result-ast @@ -1,24 +1,48 @@ -App { - header: AppHeader { - name: @4-14 PlainLine( - "test-app", - ), - packages: [], - imports: [], - provides: [], - provides_types: None, - to: @53-57 ExistingPackage( - "blah", - ), - before_header: [], - after_app_keyword: [], - before_packages: [], - after_packages: [], - before_imports: [], - after_imports: [], - before_provides: [], - after_provides: [], - before_to: [], - after_to: [], - }, +Module { + comments: [], + header: App( + AppHeader { + before_name: [], + name: @4-14 PlainLine( + "test-app", + ), + packages: Some( + KeywordItem { + keyword: Spaces { + before: [], + item: PackagesKeyword, + after: [], + }, + item: [], + }, + ), + imports: Some( + KeywordItem { + keyword: Spaces { + before: [], + item: ImportsKeyword, + after: [], + }, + item: [], + }, + ), + provides: ProvidesTo { + provides_keyword: Spaces { + before: [], + item: ProvidesKeyword, + after: [], + }, + entries: [], + types: None, + to_keyword: Spaces { + before: [], + item: ToKeyword, + after: [], + }, + to: @53-57 ExistingPackage( + "blah", + ), + }, + }, + ), } diff --git a/crates/compiler/parse/tests/snapshots/pass/empty_hosted_header.header.result-ast b/crates/compiler/parse/tests/snapshots/pass/empty_hosted_header.header.result-ast index 8cf0174c92..2bd537602b 100644 --- a/crates/compiler/parse/tests/snapshots/pass/empty_hosted_header.header.result-ast +++ b/crates/compiler/parse/tests/snapshots/pass/empty_hosted_header.header.result-ast @@ -1,23 +1,45 @@ -Hosted { - header: HostedHeader { - name: @7-10 ModuleName( - "Foo", - ), - exposes: [], - imports: [], - generates: UppercaseIdent( - "Bar", - ), - generates_with: [], - before_header: [], - after_hosted_keyword: [], - before_exposes: [], - after_exposes: [], - before_imports: [], - after_imports: [], - before_generates: [], - after_generates: [], - before_with: [], - after_with: [], - }, +Module { + comments: [], + header: Hosted( + HostedHeader { + before_name: [], + name: @7-10 ModuleName( + "Foo", + ), + exposes: KeywordItem { + keyword: Spaces { + before: [], + item: ExposesKeyword, + after: [], + }, + item: [], + }, + imports: KeywordItem { + keyword: Spaces { + before: [], + item: ImportsKeyword, + after: [], + }, + item: [], + }, + generates: KeywordItem { + keyword: Spaces { + before: [], + item: GeneratesKeyword, + after: [], + }, + item: UppercaseIdent( + "Bar", + ), + }, + generates_with: KeywordItem { + keyword: Spaces { + before: [], + item: WithKeyword, + after: [], + }, + item: [], + }, + }, + ), } diff --git a/crates/compiler/parse/tests/snapshots/pass/empty_interface_header.header.result-ast b/crates/compiler/parse/tests/snapshots/pass/empty_interface_header.header.result-ast index 1bd388d31d..f76696a1f0 100644 --- a/crates/compiler/parse/tests/snapshots/pass/empty_interface_header.header.result-ast +++ b/crates/compiler/parse/tests/snapshots/pass/empty_interface_header.header.result-ast @@ -1,15 +1,27 @@ -Interface { - header: InterfaceHeader { - name: @10-13 ModuleName( - "Foo", - ), - exposes: [], - imports: [], - before_header: [], - after_interface_keyword: [], - before_exposes: [], - after_exposes: [], - before_imports: [], - after_imports: [], - }, +Module { + comments: [], + header: Interface( + InterfaceHeader { + before_name: [], + name: @10-13 ModuleName( + "Foo", + ), + exposes: KeywordItem { + keyword: Spaces { + before: [], + item: ExposesKeyword, + after: [], + }, + item: [], + }, + imports: KeywordItem { + keyword: Spaces { + before: [], + item: ImportsKeyword, + after: [], + }, + item: [], + }, + }, + ), } diff --git a/crates/compiler/parse/tests/snapshots/pass/empty_platform_header.header.result-ast b/crates/compiler/parse/tests/snapshots/pass/empty_platform_header.header.result-ast index 736414d3a8..e93effb371 100644 --- a/crates/compiler/parse/tests/snapshots/pass/empty_platform_header.header.result-ast +++ b/crates/compiler/parse/tests/snapshots/pass/empty_platform_header.header.result-ast @@ -1,34 +1,61 @@ -Platform { - header: PlatformHeader { - name: @9-25 PackageName( - "rtfeldman/blah", - ), - requires: PlatformRequires { - rigids: [], - signature: @40-49 TypedIdent { - ident: @40-44 "main", - spaces_before_colon: [], - ann: @47-49 Record { - fields: [], - ext: None, +Module { + comments: [], + header: Platform( + PlatformHeader { + before_name: [], + name: @9-25 PackageName( + "rtfeldman/blah", + ), + requires: KeywordItem { + keyword: Spaces { + before: [], + item: RequiresKeyword, + after: [], + }, + item: PlatformRequires { + rigids: [], + signature: @40-49 TypedIdent { + ident: @40-44 "main", + spaces_before_colon: [], + ann: @47-49 Record { + fields: [], + ext: None, + }, + }, }, }, + exposes: KeywordItem { + keyword: Spaces { + before: [], + item: ExposesKeyword, + after: [], + }, + item: [], + }, + packages: KeywordItem { + keyword: Spaces { + before: [], + item: PackagesKeyword, + after: [], + }, + item: [], + }, + imports: KeywordItem { + keyword: Spaces { + before: [], + item: ImportsKeyword, + after: [], + }, + item: [], + }, + provides: KeywordItem { + keyword: Spaces { + before: [], + item: ProvidesKeyword, + after: [], + }, + item: [], + }, }, - exposes: [], - packages: [], - imports: [], - provides: [], - before_header: [], - after_platform_keyword: [], - before_requires: [], - after_requires: [], - before_exposes: [], - after_exposes: [], - before_packages: [], - after_packages: [], - before_imports: [], - after_imports: [], - before_provides: [], - after_provides: [], - }, + ), } diff --git a/crates/compiler/parse/tests/snapshots/pass/full_app_header.header.result-ast b/crates/compiler/parse/tests/snapshots/pass/full_app_header.header.result-ast index 76545b43e9..71219ddbed 100644 --- a/crates/compiler/parse/tests/snapshots/pass/full_app_header.header.result-ast +++ b/crates/compiler/parse/tests/snapshots/pass/full_app_header.header.result-ast @@ -1,50 +1,74 @@ -App { - header: AppHeader { - name: @4-15 PlainLine( - "quicksort", - ), - packages: [ - @31-47 PackageEntry { - shorthand: "pf", - spaces_after_shorthand: [], - package_name: @35-47 PackageName( - "./platform", - ), - }, - ], - imports: [ - @64-75 Package( - "foo", - ModuleName( - "Bar.Baz", - ), - [], - ), - ], - provides: [ - @93-102 ExposedName( +Module { + comments: [], + header: App( + AppHeader { + before_name: [], + name: @4-15 PlainLine( "quicksort", ), - ], - provides_types: None, - to: @108-110 ExistingPackage( - "pf", - ), - before_header: [], - after_app_keyword: [], - before_packages: [ - Newline, - ], - after_packages: [], - before_imports: [ - Newline, - ], - after_imports: [], - before_provides: [ - Newline, - ], - after_provides: [], - before_to: [], - after_to: [], - }, + packages: Some( + KeywordItem { + keyword: Spaces { + before: [ + Newline, + ], + item: PackagesKeyword, + after: [], + }, + item: [ + @31-47 PackageEntry { + shorthand: "pf", + spaces_after_shorthand: [], + package_name: @35-47 PackageName( + "./platform", + ), + }, + ], + }, + ), + imports: Some( + KeywordItem { + keyword: Spaces { + before: [ + Newline, + ], + item: ImportsKeyword, + after: [], + }, + item: [ + @64-75 Package( + "foo", + ModuleName( + "Bar.Baz", + ), + [], + ), + ], + }, + ), + provides: ProvidesTo { + provides_keyword: Spaces { + before: [ + Newline, + ], + item: ProvidesKeyword, + after: [], + }, + entries: [ + @93-102 ExposedName( + "quicksort", + ), + ], + types: None, + to_keyword: Spaces { + before: [], + item: ToKeyword, + after: [], + }, + to: @108-110 ExistingPackage( + "pf", + ), + }, + }, + ), } diff --git a/crates/compiler/parse/tests/snapshots/pass/full_app_header_trailing_commas.header.result-ast b/crates/compiler/parse/tests/snapshots/pass/full_app_header_trailing_commas.header.result-ast index 8840febceb..6a5177017c 100644 --- a/crates/compiler/parse/tests/snapshots/pass/full_app_header_trailing_commas.header.result-ast +++ b/crates/compiler/parse/tests/snapshots/pass/full_app_header_trailing_commas.header.result-ast @@ -1,75 +1,99 @@ -App { - header: AppHeader { - name: @4-15 PlainLine( - "quicksort", - ), - packages: [ - @31-47 PackageEntry { - shorthand: "pf", - spaces_after_shorthand: [], - package_name: @35-47 PackageName( - "./platform", - ), - }, - ], - imports: [ - @65-141 Package( - "foo", - ModuleName( - "Bar", - ), - Collection { - items: [ - @83-86 SpaceBefore( - ExposedName( - "Baz", +Module { + comments: [], + header: App( + AppHeader { + before_name: [], + name: @4-15 PlainLine( + "quicksort", + ), + packages: Some( + KeywordItem { + keyword: Spaces { + before: [ + Newline, + ], + item: PackagesKeyword, + after: [], + }, + item: [ + @31-47 PackageEntry { + shorthand: "pf", + spaces_after_shorthand: [], + package_name: @35-47 PackageName( + "./platform", ), - [ - Newline, - ], - ), - @96-104 SpaceBefore( - ExposedName( - "FortyTwo", - ), - [ - Newline, - ], - ), + }, ], - final_comments: [ - Newline, - LineComment( - " I'm a happy comment", + }, + ), + imports: Some( + KeywordItem { + keyword: Spaces { + before: [ + Newline, + ], + item: ImportsKeyword, + after: [], + }, + item: [ + @65-141 Package( + "foo", + ModuleName( + "Bar", + ), + Collection { + items: [ + @83-86 SpaceBefore( + ExposedName( + "Baz", + ), + [ + Newline, + ], + ), + @96-104 SpaceBefore( + ExposedName( + "FortyTwo", + ), + [ + Newline, + ], + ), + ], + final_comments: [ + Newline, + LineComment( + " I'm a happy comment", + ), + ], + }, ), ], }, ), - ], - provides: [ - @159-168 ExposedName( - "quicksort", - ), - ], - provides_types: None, - to: @175-177 ExistingPackage( - "pf", - ), - before_header: [], - after_app_keyword: [], - before_packages: [ - Newline, - ], - after_packages: [], - before_imports: [ - Newline, - ], - after_imports: [], - before_provides: [ - Newline, - ], - after_provides: [], - before_to: [], - after_to: [], - }, + provides: ProvidesTo { + provides_keyword: Spaces { + before: [ + Newline, + ], + item: ProvidesKeyword, + after: [], + }, + entries: [ + @159-168 ExposedName( + "quicksort", + ), + ], + types: None, + to_keyword: Spaces { + before: [], + item: ToKeyword, + after: [], + }, + to: @175-177 ExistingPackage( + "pf", + ), + }, + }, + ), } diff --git a/crates/compiler/parse/tests/snapshots/pass/function_effect_types.header.result-ast b/crates/compiler/parse/tests/snapshots/pass/function_effect_types.header.result-ast index 39d72bce49..b607aa0d84 100644 --- a/crates/compiler/parse/tests/snapshots/pass/function_effect_types.header.result-ast +++ b/crates/compiler/parse/tests/snapshots/pass/function_effect_types.header.result-ast @@ -1,71 +1,98 @@ -Platform { - header: PlatformHeader { - name: @9-14 PackageName( - "cli", - ), - requires: PlatformRequires { - rigids: [], - signature: @32-49 TypedIdent { - ident: @32-36 "main", - spaces_before_colon: [], - ann: @39-49 Apply( - "", - "Task", - [ - @44-46 Record { - fields: [], - ext: None, - }, - @47-49 TagUnion { - ext: None, - tags: [], - }, +Module { + comments: [], + header: Platform( + PlatformHeader { + before_name: [], + name: @9-14 PackageName( + "cli", + ), + requires: KeywordItem { + keyword: Spaces { + before: [ + Newline, ], - ), + item: RequiresKeyword, + after: [], + }, + item: PlatformRequires { + rigids: [], + signature: @32-49 TypedIdent { + ident: @32-36 "main", + spaces_before_colon: [], + ann: @39-49 Apply( + "", + "Task", + [ + @44-46 Record { + fields: [], + ext: None, + }, + @47-49 TagUnion { + ext: None, + tags: [], + }, + ], + ), + }, + }, }, - }, - exposes: [], - packages: [], - imports: [ - @110-123 Module( - ModuleName( - "Task", - ), - [ - @117-121 ExposedName( - "Task", + exposes: KeywordItem { + keyword: Spaces { + before: [ + LineComment( + " TODO FIXME", + ), + ], + item: ExposesKeyword, + after: [], + }, + item: [], + }, + packages: KeywordItem { + keyword: Spaces { + before: [ + Newline, + ], + item: PackagesKeyword, + after: [], + }, + item: [], + }, + imports: KeywordItem { + keyword: Spaces { + before: [ + Newline, + ], + item: ImportsKeyword, + after: [], + }, + item: [ + @110-123 Module( + ModuleName( + "Task", + ), + [ + @117-121 ExposedName( + "Task", + ), + ], ), ], - ), - ], - provides: [ - @141-152 ExposedName( - "mainForHost", - ), - ], - before_header: [], - after_platform_keyword: [], - before_requires: [ - Newline, - ], - after_requires: [], - before_exposes: [ - LineComment( - " TODO FIXME", - ), - ], - after_exposes: [], - before_packages: [ - Newline, - ], - after_packages: [], - before_imports: [ - Newline, - ], - after_imports: [], - before_provides: [ - Newline, - ], - after_provides: [], - }, + }, + provides: KeywordItem { + keyword: Spaces { + before: [ + Newline, + ], + item: ProvidesKeyword, + after: [], + }, + item: [ + @141-152 ExposedName( + "mainForHost", + ), + ], + }, + }, + ), } diff --git a/crates/compiler/parse/tests/snapshots/pass/interface_with_newline.header.result-ast b/crates/compiler/parse/tests/snapshots/pass/interface_with_newline.header.result-ast index fe67388046..35be4cfd94 100644 --- a/crates/compiler/parse/tests/snapshots/pass/interface_with_newline.header.result-ast +++ b/crates/compiler/parse/tests/snapshots/pass/interface_with_newline.header.result-ast @@ -1,15 +1,27 @@ -Interface { - header: InterfaceHeader { - name: @10-11 ModuleName( - "T", - ), - exposes: [], - imports: [], - before_header: [], - after_interface_keyword: [], - before_exposes: [], - after_exposes: [], - before_imports: [], - after_imports: [], - }, +Module { + comments: [], + header: Interface( + InterfaceHeader { + before_name: [], + name: @10-11 ModuleName( + "T", + ), + exposes: KeywordItem { + keyword: Spaces { + before: [], + item: ExposesKeyword, + after: [], + }, + item: [], + }, + imports: KeywordItem { + keyword: Spaces { + before: [], + item: ImportsKeyword, + after: [], + }, + item: [], + }, + }, + ), } diff --git a/crates/compiler/parse/tests/snapshots/pass/minimal_app_header.header.result-ast b/crates/compiler/parse/tests/snapshots/pass/minimal_app_header.header.result-ast index 225de00938..5ebdd1547e 100644 --- a/crates/compiler/parse/tests/snapshots/pass/minimal_app_header.header.result-ast +++ b/crates/compiler/parse/tests/snapshots/pass/minimal_app_header.header.result-ast @@ -1,26 +1,32 @@ -App { - header: AppHeader { - name: @4-14 PlainLine( - "test-app", - ), - packages: [], - imports: [], - provides: [], - provides_types: None, - to: @30-38 NewPackage( - PackageName( - "./blah", +Module { + comments: [], + header: App( + AppHeader { + before_name: [], + name: @4-14 PlainLine( + "test-app", ), - ), - before_header: [], - after_app_keyword: [], - before_packages: [], - after_packages: [], - before_imports: [], - after_imports: [], - before_provides: [], - after_provides: [], - before_to: [], - after_to: [], - }, + packages: None, + imports: None, + provides: ProvidesTo { + provides_keyword: Spaces { + before: [], + item: ProvidesKeyword, + after: [], + }, + entries: [], + types: None, + to_keyword: Spaces { + before: [], + item: ToKeyword, + after: [], + }, + to: @30-38 NewPackage( + PackageName( + "./blah", + ), + ), + }, + }, + ), } diff --git a/crates/compiler/parse/tests/snapshots/pass/nested_module.header.result-ast b/crates/compiler/parse/tests/snapshots/pass/nested_module.header.result-ast index 50f7b410ed..6a5064bc38 100644 --- a/crates/compiler/parse/tests/snapshots/pass/nested_module.header.result-ast +++ b/crates/compiler/parse/tests/snapshots/pass/nested_module.header.result-ast @@ -1,15 +1,27 @@ -Interface { - header: InterfaceHeader { - name: @10-21 ModuleName( - "Foo.Bar.Baz", - ), - exposes: [], - imports: [], - before_header: [], - after_interface_keyword: [], - before_exposes: [], - after_exposes: [], - before_imports: [], - after_imports: [], - }, +Module { + comments: [], + header: Interface( + InterfaceHeader { + before_name: [], + name: @10-21 ModuleName( + "Foo.Bar.Baz", + ), + exposes: KeywordItem { + keyword: Spaces { + before: [], + item: ExposesKeyword, + after: [], + }, + item: [], + }, + imports: KeywordItem { + keyword: Spaces { + before: [], + item: ImportsKeyword, + after: [], + }, + item: [], + }, + }, + ), } diff --git a/crates/compiler/parse/tests/snapshots/pass/nonempty_hosted_header.header.result-ast b/crates/compiler/parse/tests/snapshots/pass/nonempty_hosted_header.header.result-ast index c5b245f35f..7688f73379 100644 --- a/crates/compiler/parse/tests/snapshots/pass/nonempty_hosted_header.header.result-ast +++ b/crates/compiler/parse/tests/snapshots/pass/nonempty_hosted_header.header.result-ast @@ -1,130 +1,152 @@ -Hosted { - header: HostedHeader { - name: @7-10 ModuleName( - "Foo", - ), - exposes: Collection { - items: [ - @45-50 SpaceBefore( - ExposedName( - "Stuff", - ), - [ +Module { + comments: [], + header: Hosted( + HostedHeader { + before_name: [], + name: @7-10 ModuleName( + "Foo", + ), + exposes: KeywordItem { + keyword: Spaces { + before: [ Newline, ], - ), - @64-70 SpaceBefore( - ExposedName( - "Things", - ), - [ + item: ExposesKeyword, + after: [ Newline, ], - ), - @84-97 SpaceBefore( - ExposedName( - "somethingElse", - ), - [ - Newline, - ], - ), - ], - final_comments: [ - Newline, - ], - }, - imports: Collection { - items: [ - @143-147 SpaceBefore( - Module( - ModuleName( - "Blah", - ), - [], - ), - [ - Newline, - ], - ), - @161-182 SpaceBefore( - Module( - ModuleName( - "Baz", - ), - [ - @167-172 ExposedName( - "stuff", + }, + item: Collection { + items: [ + @45-50 SpaceBefore( + ExposedName( + "Stuff", ), - @174-180 ExposedName( - "things", + [ + Newline, + ], + ), + @64-70 SpaceBefore( + ExposedName( + "Things", ), - ], - ), - [ + [ + Newline, + ], + ), + @84-97 SpaceBefore( + ExposedName( + "somethingElse", + ), + [ + Newline, + ], + ), + ], + final_comments: [ Newline, ], + }, + }, + imports: KeywordItem { + keyword: Spaces { + before: [ + Newline, + ], + item: ImportsKeyword, + after: [ + Newline, + ], + }, + item: Collection { + items: [ + @143-147 SpaceBefore( + Module( + ModuleName( + "Blah", + ), + [], + ), + [ + Newline, + ], + ), + @161-182 SpaceBefore( + Module( + ModuleName( + "Baz", + ), + [ + @167-172 ExposedName( + "stuff", + ), + @174-180 ExposedName( + "things", + ), + ], + ), + [ + Newline, + ], + ), + ], + final_comments: [ + Newline, + ], + }, + }, + generates: KeywordItem { + keyword: Spaces { + before: [ + Newline, + ], + item: GeneratesKeyword, + after: [], + }, + item: UppercaseIdent( + "Bar", ), - ], - final_comments: [ - Newline, - ], + }, + generates_with: KeywordItem { + keyword: Spaces { + before: [], + item: WithKeyword, + after: [ + Newline, + ], + }, + item: Collection { + items: [ + @239-242 SpaceBefore( + ExposedName( + "map", + ), + [ + Newline, + ], + ), + @256-261 SpaceBefore( + ExposedName( + "after", + ), + [ + Newline, + ], + ), + @275-279 SpaceBefore( + ExposedName( + "loop", + ), + [ + Newline, + ], + ), + ], + final_comments: [ + Newline, + ], + }, + }, }, - generates: UppercaseIdent( - "Bar", - ), - generates_with: Collection { - items: [ - @239-242 SpaceBefore( - ExposedName( - "map", - ), - [ - Newline, - ], - ), - @256-261 SpaceBefore( - ExposedName( - "after", - ), - [ - Newline, - ], - ), - @275-279 SpaceBefore( - ExposedName( - "loop", - ), - [ - Newline, - ], - ), - ], - final_comments: [ - Newline, - ], - }, - before_header: [], - after_hosted_keyword: [], - before_exposes: [ - Newline, - ], - after_exposes: [ - Newline, - ], - before_imports: [ - Newline, - ], - after_imports: [ - Newline, - ], - before_generates: [ - Newline, - ], - after_generates: [], - before_with: [], - after_with: [ - Newline, - ], - }, + ), } diff --git a/crates/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.result-ast b/crates/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.result-ast index 38b5d6f691..2f0663bd04 100644 --- a/crates/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.result-ast +++ b/crates/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.result-ast @@ -1,60 +1,87 @@ -Platform { - header: PlatformHeader { - name: @9-21 PackageName( - "foo/barbaz", - ), - requires: PlatformRequires { - rigids: [ - @36-41 UppercaseIdent( - "Model", - ), - ], - signature: @45-54 TypedIdent { - ident: @45-49 "main", - spaces_before_colon: [], - ann: @52-54 Record { - fields: [], - ext: None, +Module { + comments: [], + header: Platform( + PlatformHeader { + before_name: [], + name: @9-21 PackageName( + "foo/barbaz", + ), + requires: KeywordItem { + keyword: Spaces { + before: [ + Newline, + ], + item: RequiresKeyword, + after: [], + }, + item: PlatformRequires { + rigids: [ + @36-41 UppercaseIdent( + "Model", + ), + ], + signature: @45-54 TypedIdent { + ident: @45-49 "main", + spaces_before_colon: [], + ann: @52-54 Record { + fields: [], + ext: None, + }, + }, }, }, - }, - exposes: [], - packages: [ - @87-99 PackageEntry { - shorthand: "foo", - spaces_after_shorthand: [], - package_name: @92-99 PackageName( - "./foo", - ), + exposes: KeywordItem { + keyword: Spaces { + before: [ + Newline, + ], + item: ExposesKeyword, + after: [], + }, + item: [], }, - ], - imports: [], - provides: [ - @132-143 ExposedName( - "mainForHost", - ), - ], - before_header: [], - after_platform_keyword: [], - before_requires: [ - Newline, - ], - after_requires: [], - before_exposes: [ - Newline, - ], - after_exposes: [], - before_packages: [ - Newline, - ], - after_packages: [], - before_imports: [ - Newline, - ], - after_imports: [], - before_provides: [ - Newline, - ], - after_provides: [], - }, + packages: KeywordItem { + keyword: Spaces { + before: [ + Newline, + ], + item: PackagesKeyword, + after: [], + }, + item: [ + @87-99 PackageEntry { + shorthand: "foo", + spaces_after_shorthand: [], + package_name: @92-99 PackageName( + "./foo", + ), + }, + ], + }, + imports: KeywordItem { + keyword: Spaces { + before: [ + Newline, + ], + item: ImportsKeyword, + after: [], + }, + item: [], + }, + provides: KeywordItem { + keyword: Spaces { + before: [ + Newline, + ], + item: ProvidesKeyword, + after: [], + }, + item: [ + @132-143 ExposedName( + "mainForHost", + ), + ], + }, + }, + ), } diff --git a/crates/compiler/parse/tests/snapshots/pass/provides_type.header.result-ast b/crates/compiler/parse/tests/snapshots/pass/provides_type.header.result-ast index 1991b3bd8f..aa041fd292 100644 --- a/crates/compiler/parse/tests/snapshots/pass/provides_type.header.result-ast +++ b/crates/compiler/parse/tests/snapshots/pass/provides_type.header.result-ast @@ -1,59 +1,83 @@ -App { - header: AppHeader { - name: @4-10 PlainLine( - "test", - ), - packages: [ - @26-42 PackageEntry { - shorthand: "pf", - spaces_after_shorthand: [], - package_name: @30-42 PackageName( - "./platform", +Module { + comments: [], + header: App( + AppHeader { + before_name: [], + name: @4-10 PlainLine( + "test", + ), + packages: Some( + KeywordItem { + keyword: Spaces { + before: [ + Newline, + ], + item: PackagesKeyword, + after: [], + }, + item: [ + @26-42 PackageEntry { + shorthand: "pf", + spaces_after_shorthand: [], + package_name: @30-42 PackageName( + "./platform", + ), + }, + ], + }, + ), + imports: Some( + KeywordItem { + keyword: Spaces { + before: [ + Newline, + ], + item: ImportsKeyword, + after: [], + }, + item: [ + @59-70 Package( + "foo", + ModuleName( + "Bar.Baz", + ), + [], + ), + ], + }, + ), + provides: ProvidesTo { + provides_keyword: Spaces { + before: [ + Newline, + ], + item: ProvidesKeyword, + after: [], + }, + entries: [ + @88-97 ExposedName( + "quicksort", + ), + ], + types: Some( + [ + @102-107 UppercaseIdent( + "Flags", + ), + @109-114 UppercaseIdent( + "Model", + ), + ], + ), + to_keyword: Spaces { + before: [], + item: ToKeyword, + after: [], + }, + to: @121-123 ExistingPackage( + "pf", ), }, - ], - imports: [ - @59-70 Package( - "foo", - ModuleName( - "Bar.Baz", - ), - [], - ), - ], - provides: [ - @88-97 ExposedName( - "quicksort", - ), - ], - provides_types: Some( - [ - @102-107 UppercaseIdent( - "Flags", - ), - @109-114 UppercaseIdent( - "Model", - ), - ], - ), - to: @121-123 ExistingPackage( - "pf", - ), - before_header: [], - after_app_keyword: [], - before_packages: [ - Newline, - ], - after_packages: [], - before_imports: [ - Newline, - ], - after_imports: [], - before_provides: [ - Newline, - ], - after_provides: [], - before_to: [], - after_to: [], - }, + }, + ), } diff --git a/crates/compiler/parse/tests/snapshots/pass/requires_type.header.result-ast b/crates/compiler/parse/tests/snapshots/pass/requires_type.header.result-ast index 35b2f64345..013bac0e27 100644 --- a/crates/compiler/parse/tests/snapshots/pass/requires_type.header.result-ast +++ b/crates/compiler/parse/tests/snapshots/pass/requires_type.header.result-ast @@ -1,67 +1,94 @@ -Platform { - header: PlatformHeader { - name: @9-21 PackageName( - "test/types", - ), - requires: PlatformRequires { - rigids: [ - @37-42 UppercaseIdent( - "Flags", - ), - @44-49 UppercaseIdent( - "Model", - ), - ], - signature: @55-77 TypedIdent { - ident: @55-59 "main", - spaces_before_colon: [], - ann: @62-77 Apply( - "", - "App", - [ - @66-71 Apply( - "", +Module { + comments: [], + header: Platform( + PlatformHeader { + before_name: [], + name: @9-21 PackageName( + "test/types", + ), + requires: KeywordItem { + keyword: Spaces { + before: [ + Newline, + ], + item: RequiresKeyword, + after: [], + }, + item: PlatformRequires { + rigids: [ + @37-42 UppercaseIdent( "Flags", - [], ), - @72-77 Apply( - "", + @44-49 UppercaseIdent( "Model", - [], ), ], - ), + signature: @55-77 TypedIdent { + ident: @55-59 "main", + spaces_before_colon: [], + ann: @62-77 Apply( + "", + "App", + [ + @66-71 Apply( + "", + "Flags", + [], + ), + @72-77 Apply( + "", + "Model", + [], + ), + ], + ), + }, + }, + }, + exposes: KeywordItem { + keyword: Spaces { + before: [ + Newline, + ], + item: ExposesKeyword, + after: [], + }, + item: [], + }, + packages: KeywordItem { + keyword: Spaces { + before: [ + Newline, + ], + item: PackagesKeyword, + after: [], + }, + item: [], + }, + imports: KeywordItem { + keyword: Spaces { + before: [ + Newline, + ], + item: ImportsKeyword, + after: [], + }, + item: [], + }, + provides: KeywordItem { + keyword: Spaces { + before: [ + Newline, + ], + item: ProvidesKeyword, + after: [], + }, + item: [ + @141-152 ExposedName( + "mainForHost", + ), + ], }, }, - exposes: [], - packages: [], - imports: [], - provides: [ - @141-152 ExposedName( - "mainForHost", - ), - ], - before_header: [], - after_platform_keyword: [], - before_requires: [ - Newline, - ], - after_requires: [], - before_exposes: [ - Newline, - ], - after_exposes: [], - before_packages: [ - Newline, - ], - after_packages: [], - before_imports: [ - Newline, - ], - after_imports: [], - before_provides: [ - Newline, - ], - after_provides: [], - }, + ), } diff --git a/crates/packaging/src/tarball.rs b/crates/packaging/src/tarball.rs index f709785685..2a2b9de692 100644 --- a/crates/packaging/src/tarball.rs +++ b/crates/packaging/src/tarball.rs @@ -1,7 +1,7 @@ use brotli::enc::BrotliEncoderParams; use bumpalo::Bump; use flate2::write::GzEncoder; -use roc_parse::ast::Module; +use roc_parse::ast::{Header, Module}; use roc_parse::header::PlatformHeader; use roc_parse::module::parse_header; use roc_parse::state::State; @@ -124,22 +124,20 @@ fn write_archive(path: &Path, writer: W) -> io::Result<()> { let arena = Bump::new(); let mut buf = Vec::new(); - let _other_modules: &[Module<'_>] = match read_header(&arena, &mut buf, path)? { - Module::Interface { .. } => { + let _other_modules: &[Module<'_>] = match read_header(&arena, &mut buf, path)?.header { + Header::Interface(_) => { todo!(); // TODO report error } - Module::App { .. } => { + Header::App(_) => { todo!(); // TODO report error } - Module::Hosted { .. } => { + Header::Hosted(_) => { todo!(); // TODO report error } - Module::Platform { - header: PlatformHeader { imports: _, .. }, - } => { + Header::Platform(PlatformHeader { imports: _, .. }) => { use walkdir::WalkDir; // Add all the prebuilt host files to the archive.