New module header

Implements the new `module` header syntax as described in "module and package changes" [1]:

```
module [Request, Response, req]
```

The old syntax should still work fine, and is automatically upgraded to the new one
when running `roc format`.

[1] https://docs.google.com/document/d/1E_77fO-44BtoBtXoVeWyGh1xN2KRTWTu8q6i25RNNx0/edit
This commit is contained in:
Agus Zubiaga 2024-02-18 19:10:54 -03:00
parent 7754dd7ef7
commit 057a18573a
No known key found for this signature in database
92 changed files with 1445 additions and 1563 deletions

View file

@ -1,7 +1,7 @@
use std::fmt::Debug;
use crate::header::{
self, AppHeader, HostedHeader, InterfaceHeader, ModuleName, PackageHeader, PlatformHeader,
self, AppHeader, HostedHeader, ModuleHeader, ModuleName, PackageHeader, PlatformHeader,
};
use crate::ident::Accessor;
use crate::parser::ESingleQuote;
@ -99,6 +99,21 @@ pub struct Module<'a> {
}
impl<'a> Module<'a> {
pub fn upgrade_header_imports(self, arena: &'a Bump) -> (Self, Defs<'a>) {
let (header, defs) = match self.header {
Header::Module(header) => (
Header::Module(ModuleHeader {
interface_imports: None,
..header
}),
Self::header_imports_to_defs(arena, header.interface_imports),
),
header => (header, Defs::default()),
};
(Module { header, ..self }, defs)
}
pub fn header_imports_to_defs(
arena: &'a Bump,
imports: Option<
@ -209,7 +224,7 @@ impl<'a> Module<'a> {
#[derive(Clone, Debug, PartialEq)]
pub enum Header<'a> {
Interface(InterfaceHeader<'a>),
Module(ModuleHeader<'a>),
App(AppHeader<'a>),
Package(PackageHeader<'a>),
Platform(PlatformHeader<'a>),
@ -2272,7 +2287,7 @@ impl<'a> Malformed for Module<'a> {
impl<'a> Malformed for Header<'a> {
fn is_malformed(&self) -> bool {
match self {
Header::Interface(header) => header.is_malformed(),
Header::Module(header) => header.is_malformed(),
Header::App(header) => header.is_malformed(),
Header::Package(header) => header.is_malformed(),
Header::Platform(header) => header.is_malformed(),

View file

@ -19,7 +19,7 @@ impl<'a> HeaderType<'a> {
}
| HeaderType::Hosted { exposes, .. }
| HeaderType::Builtin { exposes, .. }
| HeaderType::Interface { exposes, .. } => exposes,
| HeaderType::Module { exposes, .. } => exposes,
HeaderType::Platform { .. } | HeaderType::Package { .. } => &[],
}
}
@ -30,7 +30,7 @@ impl<'a> HeaderType<'a> {
HeaderType::Builtin { .. } => "builtin",
HeaderType::Package { .. } => "package",
HeaderType::Platform { .. } => "platform",
HeaderType::Interface { .. } => "interface",
HeaderType::Module { .. } => "module",
}
}
}
@ -73,7 +73,7 @@ pub enum HeaderType<'a> {
/// usually `pf`
config_shorthand: &'a str,
},
Interface {
Module {
name: ModuleName<'a>,
exposes: &'a [Loc<ExposedName<'a>>],
},
@ -82,9 +82,9 @@ pub enum HeaderType<'a> {
impl<'a> HeaderType<'a> {
pub fn get_name(self) -> Option<&'a str> {
match self {
Self::Interface { name, .. }
| Self::Builtin { name, .. }
| Self::Hosted { name, .. } => Some(name.into()),
Self::Module { name, .. } | Self::Builtin { name, .. } | Self::Hosted { name, .. } => {
Some(name.into())
}
Self::App {
output_name: StrLiteral::PlainLine(name),
..
@ -106,13 +106,11 @@ impl<'a> HeaderType<'a> {
pub fn to_maybe_builtin(self, module_id: ModuleId) -> Self {
match self {
HeaderType::Interface { name, exposes } if module_id.is_builtin() => {
HeaderType::Builtin {
name,
exposes,
generates_with: &[],
}
}
HeaderType::Module { name, exposes } if module_id.is_builtin() => HeaderType::Builtin {
name,
exposes,
generates_with: &[],
},
_ => self,
}
}
@ -246,12 +244,12 @@ pub struct KeywordItem<'a, K, V> {
}
#[derive(Clone, Debug, PartialEq)]
pub struct InterfaceHeader<'a> {
pub before_name: &'a [CommentOrNewline<'a>],
pub name: Loc<ModuleName<'a>>,
pub struct ModuleHeader<'a> {
pub before_exposes: &'a [CommentOrNewline<'a>],
pub exposes: Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>,
pub exposes: KeywordItem<'a, ExposesKeyword, Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>>,
pub imports: KeywordItem<'a, ImportsKeyword, Collection<'a, Loc<Spaced<'a, ImportsEntry<'a>>>>>,
// Keeping this so we can format old interface header into module headers
pub interface_imports: Option<KeywordItem<'a, ImportsKeyword, ImportsCollection<'a>>>,
}
pub type ImportsKeywordItem<'a> = KeywordItem<'a, ImportsKeyword, ImportsCollection<'a>>;
@ -430,7 +428,7 @@ where
}
}
impl<'a> Malformed for InterfaceHeader<'a> {
impl<'a> Malformed for ModuleHeader<'a> {
fn is_malformed(&self) -> bool {
false
}

View file

@ -1,10 +1,11 @@
use crate::ast::{Collection, Defs, Header, Module, Spaced, Spaces};
use crate::ast::{Collection, CommentOrNewline, 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, ExposesKeyword, GeneratesKeyword,
HostedHeader, ImportsEntry, ImportsKeyword, InterfaceHeader, Keyword, KeywordItem, ModuleName,
PackageEntry, PackageHeader, PackagesKeyword, PlatformHeader, PlatformRequires,
ProvidesKeyword, ProvidesTo, RequiresKeyword, To, ToKeyword, TypedIdent, WithKeyword,
HostedHeader, ImportsEntry, ImportsKeyword, ImportsKeywordItem, Keyword, KeywordItem,
ModuleHeader, ModuleName, PackageEntry, PackageHeader, 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, *};
@ -60,12 +61,20 @@ pub fn header<'a>() -> impl Parser<'a, Module<'a>, EHeader<'a>> {
record!(Module {
comments: space0_e(EHeader::IndentStart),
header: one_of![
map!(
skip_first!(
keyword("module", EHeader::Start),
increment_min_indent(module_header())
),
Header::Module
),
// Old headers
map!(
skip_first!(
keyword("interface", EHeader::Start),
increment_min_indent(interface_header())
),
Header::Interface
Header::Module
),
map!(
skip_first!(
@ -100,22 +109,65 @@ pub fn header<'a>() -> impl Parser<'a, Module<'a>, EHeader<'a>> {
}
#[inline(always)]
fn interface_header<'a>() -> impl Parser<'a, InterfaceHeader<'a>, EHeader<'a>> {
record!(InterfaceHeader {
before_name: space0_e(EHeader::IndentStart),
name: loc!(module_name_help(EHeader::ModuleName)),
exposes: specialize_err(EHeader::Exposes, exposes_values()),
imports: specialize_err(EHeader::Imports, imports()),
fn module_header<'a>() -> impl Parser<'a, ModuleHeader<'a>, EHeader<'a>> {
record!(ModuleHeader {
before_exposes: space0_e(EHeader::IndentStart),
exposes: specialize_err(EHeader::Exposes, exposes_list()),
interface_imports: succeed!(None)
})
.trace("module_header")
}
/// Parse old interface headers so we can format them into module headers
#[inline(always)]
fn interface_header<'a>() -> impl Parser<'a, ModuleHeader<'a>, EHeader<'a>> {
use bumpalo::collections::Vec;
let before_exposes = map_with_arena!(
and!(
skip_second!(
space0_e(EHeader::IndentStart),
loc!(module_name_help(EHeader::ModuleName))
),
specialize_err(EHeader::Exposes, exposes_kw())
),
|arena: &'a bumpalo::Bump,
(before_name, kw): (&'a [CommentOrNewline<'a>], Spaces<'a, ExposesKeyword>)| {
let mut combined: Vec<CommentOrNewline> =
Vec::with_capacity_in(before_name.len() + kw.before.len() + kw.after.len(), arena);
combined.extend(before_name);
combined.extend(kw.before);
combined.extend(kw.after);
arena.alloc(combined)
}
);
record!(ModuleHeader {
before_exposes: before_exposes,
exposes: specialize_err(EHeader::Exposes, exposes_list()).trace("exposes_list"),
interface_imports: map!(
specialize_err(EHeader::Imports, imports()),
imports_none_if_empty
)
.trace("imports"),
})
.trace("interface_header")
}
fn imports_none_if_empty(value: ImportsKeywordItem<'_>) -> Option<ImportsKeywordItem<'_>> {
if value.item.is_empty() {
None
} else {
Some(value)
}
}
#[inline(always)]
fn hosted_header<'a>() -> impl Parser<'a, HostedHeader<'a>, EHeader<'a>> {
record!(HostedHeader {
before_name: space0_e(EHeader::IndentStart),
name: loc!(module_name_help(EHeader::ModuleName)),
exposes: specialize_err(EHeader::Exposes, exposes_values()),
exposes: specialize_err(EHeader::Exposes, exposes_values_kw()),
imports: specialize_err(EHeader::Imports, imports()),
generates: specialize_err(EHeader::Generates, generates()),
generates_with: specialize_err(EHeader::GeneratesWith, generates_with()),
@ -388,28 +440,39 @@ fn requires_typed_ident<'a>() -> impl Parser<'a, Loc<Spaced<'a, TypedIdent<'a>>>
}
#[inline(always)]
fn exposes_values<'a>() -> impl Parser<
fn exposes_values_kw<'a>() -> impl Parser<
'a,
KeywordItem<'a, ExposesKeyword, Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>>,
EExposes,
> {
record!(KeywordItem {
keyword: spaces_around_keyword(
ExposesKeyword,
EExposes::Exposes,
EExposes::IndentExposes,
EExposes::IndentListStart
),
item: collection_trailing_sep_e!(
byte(b'[', EExposes::ListStart),
exposes_entry(EExposes::Identifier),
byte(b',', EExposes::ListEnd),
byte(b']', EExposes::ListEnd),
Spaced::SpaceBefore
)
keyword: exposes_kw(),
item: exposes_list()
})
}
#[inline(always)]
fn exposes_kw<'a>() -> impl Parser<'a, Spaces<'a, ExposesKeyword>, EExposes> {
spaces_around_keyword(
ExposesKeyword,
EExposes::Exposes,
EExposes::IndentExposes,
EExposes::IndentListStart,
)
}
#[inline(always)]
fn exposes_list<'a>() -> impl Parser<'a, Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>, EExposes>
{
collection_trailing_sep_e!(
byte(b'[', EExposes::ListStart),
exposes_entry(EExposes::Identifier),
byte(b',', EExposes::ListEnd),
byte(b']', EExposes::ListEnd),
Spaced::SpaceBefore
)
}
pub fn spaces_around_keyword<'a, K: Keyword, E>(
keyword_item: K,
expectation: fn(Position) -> E,