New package header syntax

Implements the new package header syntax as discussed in Zulip [1].

package [Csv] {
    parser: "../parser/main.roc"
}

Old headers still parse and are automatically upgraded to the new
syntax by the formatter.

[1] https://roc.zulipchat.com/#narrow/stream/304641-ideas/topic/Fewer.2Fdifferent.20keywords.20in.20the.20file.20header/near/418444862
This commit is contained in:
Agus Zubiaga 2024-03-04 19:58:22 -03:00
parent 8dedd9f03c
commit e3b600c282
No known key found for this signature in database
16 changed files with 169 additions and 132 deletions

View File

@ -1,3 +1 @@
package "csv"
exposes [Csv]
packages {}
package [Csv] {}

View File

@ -1,3 +1 @@
package "json"
exposes [JsonParser]
packages {}
package [JsonParser] {}

View File

@ -1,3 +1,15 @@
package "builtins"
exposes [Str, Num, Bool, Result, List, Dict, Set, Decode, Encode, Hash, Box, TotallyNotJson, Inspect]
packages {}
package [
Str,
Num,
Bool,
Result,
List,
Dict,
Set,
Decode,
Encode,
Hash,
Box,
TotallyNotJson,
Inspect,
] {}

View File

@ -5,7 +5,7 @@ use crate::spaces::RemoveSpaces;
use crate::spaces::{fmt_comments_only, fmt_default_spaces, fmt_spaces, NewlineAt, INDENT};
use crate::Buf;
use bumpalo::Bump;
use roc_parse::ast::{Collection, Header, Module, Spaced, Spaces};
use roc_parse::ast::{Collection, CommentOrNewline, Header, Module, Spaced, Spaces};
use roc_parse::header::{
AppHeader, ExposedName, ExposesKeyword, GeneratesKeyword, HostedHeader, ImportsEntry,
ImportsKeyword, Keyword, KeywordItem, ModuleHeader, ModuleName, PackageEntry, PackageHeader,
@ -177,15 +177,8 @@ pub fn fmt_module_header<'a>(buf: &mut Buf, header: &'a ModuleHeader<'a>) {
buf.indent(0);
buf.push_str("module");
if header.before_exposes.iter().all(|c| c.is_newline()) {
buf.spaces(1);
fmt_exposes(buf, header.exposes, 0);
} else {
let indent = INDENT;
fmt_default_spaces(buf, header.before_exposes, indent);
fmt_exposes(buf, header.exposes, indent);
};
let indent = fmt_spaces_with_outdent(buf, header.before_exposes, INDENT);
fmt_exposes(buf, header.exposes, indent);
}
pub fn fmt_hosted_header<'a>(buf: &mut Buf, header: &'a HostedHeader<'a>) {
@ -209,37 +202,32 @@ pub fn fmt_app_header<'a>(buf: &mut Buf, header: &'a AppHeader<'a>) {
buf.indent(0);
buf.push_str("app");
let indent = INDENT;
let indent = fmt_spaces_with_outdent(buf, header.before_provides, INDENT);
fmt_exposes(buf, header.provides, indent);
if header.before_provides.iter().all(|c| c.is_newline()) {
buf.spaces(1);
fmt_exposes(buf, header.provides, 0);
} else {
fmt_default_spaces(buf, header.before_provides, indent);
fmt_exposes(buf, header.provides, indent);
}
let indent = fmt_spaces_with_outdent(buf, header.before_packages, INDENT);
fmt_packages(buf, header.packages.value, indent);
}
if header.before_packages.iter().all(|c| c.is_newline()) {
pub fn fmt_spaces_with_outdent(buf: &mut Buf, spaces: &[CommentOrNewline], indent: u16) -> u16 {
if spaces.iter().all(|c| c.is_newline()) {
buf.spaces(1);
fmt_packages(buf, header.packages.value, 0);
0
} else {
fmt_default_spaces(buf, header.before_packages, indent);
fmt_packages(buf, header.packages.value, indent);
fmt_default_spaces(buf, spaces, indent);
indent
}
}
pub fn fmt_package_header<'a>(buf: &mut Buf, header: &'a PackageHeader<'a>) {
buf.indent(0);
buf.push_str("package");
let indent = INDENT;
fmt_default_spaces(buf, header.before_name, indent);
fmt_package_name(buf, header.name.value, indent);
let indent = fmt_spaces_with_outdent(buf, header.before_exposes, INDENT);
fmt_exposes(buf, header.exposes, 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);
let indent = fmt_spaces_with_outdent(buf, header.before_packages, INDENT);
fmt_packages(buf, header.packages.value, indent);
}
pub fn fmt_platform_header<'a>(buf: &mut Buf, header: &'a PlatformHeader<'a>) {

View File

@ -299,9 +299,9 @@ impl<'a> RemoveSpaces<'a> for Module<'a> {
.remove_spaces(arena),
}),
Header::Package(header) => Header::Package(PackageHeader {
before_name: &[],
name: header.name.remove_spaces(arena),
before_exposes: &[],
exposes: header.exposes.remove_spaces(arena),
before_packages: &[],
packages: header.packages.remove_spaces(arena),
}),
Header::Platform(header) => Header::Platform(PlatformHeader {

View File

@ -4851,12 +4851,12 @@ fn build_package_header<'a>(
module_timing: ModuleTiming,
) -> Result<(ModuleId, PQModuleName<'a>, ModuleHeader<'a>), LoadingProblem<'a>> {
let exposes = bumpalo::collections::Vec::from_iter_in(
unspace(arena, header.exposes.item.items).iter().copied(),
unspace(arena, header.exposes.items).iter().copied(),
arena,
);
let packages = unspace(arena, header.packages.item.items);
let packages = unspace(arena, header.packages.value.items);
let exposes_ids = get_exposes_ids(
header.exposes.item.items,
header.exposes.items,
arena,
&module_ids,
&ident_ids_by_module,

View File

@ -294,12 +294,10 @@ pub struct ProvidesTo<'a> {
#[derive(Clone, Debug, PartialEq)]
pub struct PackageHeader<'a> {
pub before_name: &'a [CommentOrNewline<'a>],
pub name: Loc<PackageName<'a>>,
pub exposes: KeywordItem<'a, ExposesKeyword, Collection<'a, Loc<Spaced<'a, ModuleName<'a>>>>>,
pub packages:
KeywordItem<'a, PackagesKeyword, Collection<'a, Loc<Spaced<'a, PackageEntry<'a>>>>>,
pub before_exposes: &'a [CommentOrNewline<'a>],
pub exposes: Collection<'a, Loc<Spaced<'a, ModuleName<'a>>>>,
pub before_packages: &'a [CommentOrNewline<'a>],
pub packages: Loc<Collection<'a, Loc<Spaced<'a, PackageEntry<'a>>>>>,
}
#[derive(Clone, Debug, PartialEq)]

View File

@ -86,7 +86,7 @@ pub fn header<'a>() -> impl Parser<'a, Module<'a>, EHeader<'a>> {
map!(
skip_first!(
keyword("package", EHeader::Start),
increment_min_indent(package_header())
increment_min_indent(one_of![package_header(), old_package_header()])
),
Header::Package
),
@ -377,14 +377,57 @@ fn old_app_header<'a>() -> impl Parser<'a, AppHeader<'a>, EHeader<'a>> {
#[inline(always)]
fn package_header<'a>() -> impl Parser<'a, PackageHeader<'a>, EHeader<'a>> {
record!(PackageHeader {
before_name: space0_e(EHeader::IndentStart),
name: loc!(specialize_err(EHeader::PackageName, package_name())),
exposes: specialize_err(EHeader::Exposes, exposes_modules()),
packages: specialize_err(EHeader::Packages, packages()),
before_exposes: space0_e(EHeader::IndentStart),
exposes: specialize_err(EHeader::Exposes, exposes_module_collection()),
before_packages: space0_e(EHeader::IndentStart),
packages: specialize_err(EHeader::Packages, loc!(packages_collection())),
})
.trace("package_header")
}
#[derive(Debug, Clone, PartialEq)]
struct OldPackageHeader<'a> {
before_name: &'a [CommentOrNewline<'a>],
exposes: KeywordItem<'a, ExposesKeyword, Collection<'a, Loc<Spaced<'a, ModuleName<'a>>>>>,
packages:
Loc<KeywordItem<'a, PackagesKeyword, Collection<'a, Loc<Spaced<'a, PackageEntry<'a>>>>>>,
}
#[inline(always)]
fn old_package_header<'a>() -> impl Parser<'a, PackageHeader<'a>, EHeader<'a>> {
map_with_arena!(
record!(OldPackageHeader {
before_name: skip_second!(
space0_e(EHeader::IndentStart),
specialize_err(EHeader::PackageName, package_name())
),
exposes: specialize_err(EHeader::Exposes, exposes_modules()),
packages: specialize_err(EHeader::Packages, loc!(packages())),
}),
|arena: &'a bumpalo::Bump, old: OldPackageHeader<'a>| {
let before_exposes = merge_n_spaces!(
arena,
old.before_name,
old.exposes.keyword.before,
old.exposes.keyword.after
);
let before_packages = merge_spaces(
arena,
old.packages.value.keyword.before,
old.packages.value.keyword.after,
);
PackageHeader {
before_exposes,
exposes: old.exposes.item,
before_packages,
packages: old.packages.map(|kw| kw.item),
}
}
)
.trace("old_package_header")
}
#[inline(always)]
fn platform_header<'a>() -> impl Parser<'a, PlatformHeader<'a>, EHeader<'a>> {
record!(PlatformHeader {
@ -638,16 +681,21 @@ fn exposes_modules<'a>() -> impl Parser<
EExposes::IndentExposes,
EExposes::IndentListStart
),
item: collection_trailing_sep_e!(
byte(b'[', EExposes::ListStart),
exposes_module(EExposes::Identifier),
byte(b',', EExposes::ListEnd),
byte(b']', EExposes::ListEnd),
Spaced::SpaceBefore
),
item: exposes_module_collection(),
})
}
fn exposes_module_collection<'a>(
) -> impl Parser<'a, Collection<'a, Loc<Spaced<'a, ModuleName<'a>>>>, EExposes> {
collection_trailing_sep_e!(
byte(b'[', EExposes::ListStart),
exposes_module(EExposes::Identifier),
byte(b',', EExposes::ListEnd),
byte(b']', EExposes::ListEnd),
Spaced::SpaceBefore
)
}
fn exposes_module<'a, F, E>(
to_expectation: F,
) -> impl Parser<'a, Loc<Spaced<'a, ModuleName<'a>>>, E>

View File

@ -1 +1 @@
package "rtfeldman/blah" exposes [] packages {}
package [] {}

View File

@ -2,26 +2,10 @@ Module {
comments: [],
header: Package(
PackageHeader {
before_name: [],
name: @8-24 PackageName(
"rtfeldman/blah",
),
exposes: KeywordItem {
keyword: Spaces {
before: [],
item: ExposesKeyword,
after: [],
},
item: [],
},
packages: KeywordItem {
keyword: Spaces {
before: [],
item: PackagesKeyword,
after: [],
},
item: [],
},
before_exposes: [],
exposes: [],
before_packages: [],
packages: @11-13 [],
},
),
}

View File

@ -1 +1 @@
package "rtfeldman/blah" exposes [] packages {}
package [] {}

View File

@ -1,3 +1,3 @@
package "foo/barbaz"
exposes [Foo, Bar]
packages { foo: "./foo" }
package [Foo, Bar] {
foo: "./foo",
}

View File

@ -2,46 +2,36 @@ Module {
comments: [],
header: Package(
PackageHeader {
before_name: [],
name: @8-20 PackageName(
"foo/barbaz",
),
exposes: KeywordItem {
keyword: Spaces {
before: [
before_exposes: [],
exposes: [
@9-12 ModuleName(
"Foo",
),
@14-17 ModuleName(
"Bar",
),
],
before_packages: [],
packages: @19-39 [
@25-37 SpaceBefore(
SpaceAfter(
PackageEntry {
shorthand: "foo",
spaces_after_shorthand: [],
platform_marker: None,
package_name: @30-37 PackageName(
"./foo",
),
},
[
Newline,
],
),
[
Newline,
],
item: ExposesKeyword,
after: [],
},
item: [
@34-37 ModuleName(
"Foo",
),
@39-42 ModuleName(
"Bar",
),
],
},
packages: KeywordItem {
keyword: Spaces {
before: [
Newline,
],
item: PackagesKeyword,
after: [],
},
item: [
@59-71 PackageEntry {
shorthand: "foo",
spaces_after_shorthand: [],
platform_marker: None,
package_name: @64-71 PackageName(
"./foo",
),
},
],
},
),
],
},
),
}

View File

@ -1,3 +1,3 @@
package "foo/barbaz"
exposes [Foo, Bar]
packages { foo: "./foo" }
package [Foo, Bar] {
foo: "./foo"
}

View File

@ -4840,6 +4840,28 @@ mod test_fmt {
);
}
#[test]
fn old_style_package_header_is_upgraded() {
module_formats_to(
indoc!(
"
package \"csv\"
exposes [Csv]
packages {
parser: \"parser/main.roc\"
}
"
),
indoc!(
"
package [Csv] {
parser: \"parser/main.roc\",
}
"
),
);
}
#[test]
fn single_line_app() {
module_formats_same(indoc!(

View File

@ -244,15 +244,14 @@ impl IterTokens for AppHeader<'_> {
impl IterTokens for PackageHeader<'_> {
fn iter_tokens<'a>(&self, arena: &'a Bump) -> BumpVec<'a, Loc<Token>> {
let Self {
before_name: _,
name,
before_exposes: _,
exposes,
before_packages: _,
packages,
} = self;
(name.iter_tokens(arena).into_iter())
.chain(exposes.item.iter_tokens(arena))
.chain(packages.item.iter_tokens(arena))
(exposes.iter_tokens(arena).into_iter())
.chain(packages.value.iter_tokens(arena))
.collect_in(arena)
}
}