mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-28 06:14:46 +00:00
New app
header syntax
Implements the new app header syntax as discussed in Zulip [1].
app [main] {
cli: platform "../platform/main.roc",
json: "../json/main.roc"
}
Old headers still parse and are automatically upgraded to the new
syntax by the formatter.
[1] 418444862
This commit is contained in:
parent
057a18573a
commit
8dedd9f03c
90 changed files with 1044 additions and 1056 deletions
|
@ -1,11 +1,12 @@
|
|||
use crate::ast::{Collection, CommentOrNewline, Defs, Header, Module, Spaced, Spaces};
|
||||
use crate::blankspace::{space0_around_ee, space0_before_e, space0_e};
|
||||
use crate::expr::merge_spaces;
|
||||
use crate::header::{
|
||||
package_entry, package_name, AppHeader, ExposedName, ExposesKeyword, GeneratesKeyword,
|
||||
HostedHeader, ImportsEntry, ImportsKeyword, ImportsKeywordItem, Keyword, KeywordItem,
|
||||
ModuleHeader, ModuleName, PackageEntry, PackageHeader, PackagesKeyword, PlatformHeader,
|
||||
PlatformRequires, ProvidesKeyword, ProvidesTo, RequiresKeyword, To, ToKeyword, TypedIdent,
|
||||
WithKeyword,
|
||||
HostedHeader, ImportsCollection, 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, *};
|
||||
|
@ -17,7 +18,7 @@ use crate::parser::{
|
|||
use crate::state::State;
|
||||
use crate::string_literal::{self, parse_str_literal};
|
||||
use crate::type_annotation;
|
||||
use roc_region::all::{Loc, Position};
|
||||
use roc_region::all::{Loc, Position, Region};
|
||||
|
||||
fn end_of_file<'a>() -> impl Parser<'a, (), SyntaxError<'a>> {
|
||||
|_arena, state: State<'a>, _min_indent: u32| {
|
||||
|
@ -68,7 +69,6 @@ pub fn header<'a>() -> impl Parser<'a, Module<'a>, EHeader<'a>> {
|
|||
),
|
||||
Header::Module
|
||||
),
|
||||
// Old headers
|
||||
map!(
|
||||
skip_first!(
|
||||
keyword("interface", EHeader::Start),
|
||||
|
@ -79,7 +79,7 @@ pub fn header<'a>() -> impl Parser<'a, Module<'a>, EHeader<'a>> {
|
|||
map!(
|
||||
skip_first!(
|
||||
keyword("app", EHeader::Start),
|
||||
increment_min_indent(app_header())
|
||||
increment_min_indent(one_of![app_header(), old_app_header()])
|
||||
),
|
||||
Header::App
|
||||
),
|
||||
|
@ -118,11 +118,19 @@ fn module_header<'a>() -> impl Parser<'a, ModuleHeader<'a>, EHeader<'a>> {
|
|||
.trace("module_header")
|
||||
}
|
||||
|
||||
macro_rules! merge_n_spaces {
|
||||
($arena:expr, $($slice:expr),*) => {
|
||||
{
|
||||
let mut merged = bumpalo::collections::Vec::with_capacity_in(0 $(+ $slice.len())*, $arena);
|
||||
$(merged.extend_from_slice($slice);)*
|
||||
merged.into_bump_slice()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// 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!(
|
||||
|
@ -133,12 +141,7 @@ fn interface_header<'a>() -> impl Parser<'a, ModuleHeader<'a>, EHeader<'a>> {
|
|||
),
|
||||
|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)
|
||||
merge_n_spaces!(arena, before_name, kw.before, kw.after)
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -237,18 +240,140 @@ fn module_name<'a>() -> impl Parser<'a, ModuleName<'a>, ()> {
|
|||
#[inline(always)]
|
||||
fn app_header<'a>() -> impl Parser<'a, AppHeader<'a>, EHeader<'a>> {
|
||||
record!(AppHeader {
|
||||
before_name: space0_e(EHeader::IndentStart),
|
||||
name: loc!(crate::parser::specialize_err(
|
||||
EHeader::AppName,
|
||||
string_literal::parse_str_literal()
|
||||
)),
|
||||
packages: optional(specialize_err(EHeader::Packages, packages())),
|
||||
imports: optional(specialize_err(EHeader::Imports, imports())),
|
||||
provides: specialize_err(EHeader::Provides, provides_to()),
|
||||
before_provides: space0_e(EHeader::IndentStart),
|
||||
provides: specialize_err(EHeader::Exposes, exposes_list()),
|
||||
before_packages: space0_e(EHeader::IndentStart),
|
||||
packages: specialize_err(EHeader::Packages, loc!(packages_collection())),
|
||||
old_imports: succeed!(None),
|
||||
old_provides_to_new_package: succeed!(None),
|
||||
})
|
||||
.trace("app_header")
|
||||
}
|
||||
|
||||
struct OldAppHeader<'a> {
|
||||
pub before_name: &'a [CommentOrNewline<'a>],
|
||||
pub packages: Option<Loc<OldAppPackages<'a>>>,
|
||||
pub imports: Option<KeywordItem<'a, ImportsKeyword, ImportsCollection<'a>>>,
|
||||
pub provides: ProvidesTo<'a>,
|
||||
}
|
||||
|
||||
type OldAppPackages<'a> =
|
||||
KeywordItem<'a, PackagesKeyword, Collection<'a, Loc<Spaced<'a, PackageEntry<'a>>>>>;
|
||||
|
||||
#[inline(always)]
|
||||
fn old_app_header<'a>() -> impl Parser<'a, AppHeader<'a>, EHeader<'a>> {
|
||||
let old = record!(OldAppHeader {
|
||||
before_name: skip_second!(
|
||||
space0_e(EHeader::IndentStart),
|
||||
loc!(crate::parser::specialize_err(
|
||||
EHeader::AppName,
|
||||
string_literal::parse_str_literal()
|
||||
))
|
||||
),
|
||||
packages: optional(specialize_err(EHeader::Packages, loc!(packages()))),
|
||||
imports: optional(specialize_err(EHeader::Imports, imports())),
|
||||
provides: specialize_err(EHeader::Provides, provides_to()),
|
||||
});
|
||||
|
||||
map_with_arena!(old, |arena: &'a bumpalo::Bump, old: OldAppHeader<'a>| {
|
||||
let mut before_packages: &'a [CommentOrNewline] = &[];
|
||||
|
||||
let packages = match old.packages {
|
||||
Some(packages) => {
|
||||
before_packages = merge_spaces(
|
||||
arena,
|
||||
packages.value.keyword.before,
|
||||
packages.value.keyword.after,
|
||||
);
|
||||
|
||||
if let To::ExistingPackage(platform_shorthand) = old.provides.to.value {
|
||||
packages.map(|coll| {
|
||||
coll.item.map_items(arena, |loc_spaced_pkg| {
|
||||
if loc_spaced_pkg.value.item().shorthand == platform_shorthand {
|
||||
loc_spaced_pkg.map(|spaced_pkg| {
|
||||
spaced_pkg.map(arena, |pkg| {
|
||||
let mut new_pkg = *pkg;
|
||||
new_pkg.platform_marker = Some(merge_spaces(
|
||||
arena,
|
||||
old.provides.to_keyword.before,
|
||||
old.provides.to_keyword.after,
|
||||
));
|
||||
new_pkg
|
||||
})
|
||||
})
|
||||
} else {
|
||||
*loc_spaced_pkg
|
||||
}
|
||||
})
|
||||
})
|
||||
} else {
|
||||
packages.map(|kw| kw.item)
|
||||
}
|
||||
}
|
||||
None => Loc {
|
||||
region: Region::zero(),
|
||||
value: Collection::empty(),
|
||||
},
|
||||
};
|
||||
|
||||
let provides = match old.provides.types {
|
||||
Some(types) => {
|
||||
let mut combined_items = bumpalo::collections::Vec::with_capacity_in(
|
||||
old.provides.entries.items.len() + types.items.len(),
|
||||
arena,
|
||||
);
|
||||
|
||||
combined_items.extend_from_slice(old.provides.entries.items);
|
||||
|
||||
for loc_spaced_type_ident in types.items {
|
||||
combined_items.push(loc_spaced_type_ident.map(|spaced_type_ident| {
|
||||
spaced_type_ident.map(arena, |type_ident| {
|
||||
ExposedName::new(From::from(*type_ident))
|
||||
})
|
||||
}));
|
||||
}
|
||||
|
||||
let value_comments = old.provides.entries.final_comments();
|
||||
let type_comments = types.final_comments();
|
||||
|
||||
let mut combined_comments = bumpalo::collections::Vec::with_capacity_in(
|
||||
value_comments.len() + type_comments.len(),
|
||||
arena,
|
||||
);
|
||||
combined_comments.extend_from_slice(value_comments);
|
||||
combined_comments.extend_from_slice(type_comments);
|
||||
|
||||
Collection::with_items_and_comments(
|
||||
arena,
|
||||
combined_items.into_bump_slice(),
|
||||
combined_comments.into_bump_slice(),
|
||||
)
|
||||
}
|
||||
None => old.provides.entries,
|
||||
};
|
||||
|
||||
AppHeader {
|
||||
before_provides: merge_spaces(
|
||||
arena,
|
||||
old.before_name,
|
||||
old.provides.provides_keyword.before,
|
||||
),
|
||||
provides,
|
||||
before_packages: merge_spaces(
|
||||
arena,
|
||||
before_packages,
|
||||
old.provides.provides_keyword.after,
|
||||
),
|
||||
packages,
|
||||
old_imports: old.imports.and_then(imports_none_if_empty),
|
||||
old_provides_to_new_package: match old.provides.to.value {
|
||||
To::NewPackage(new_pkg) => Some(new_pkg),
|
||||
To::ExistingPackage(_) => None,
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn package_header<'a>() -> impl Parser<'a, PackageHeader<'a>, EHeader<'a>> {
|
||||
record!(PackageHeader {
|
||||
|
@ -544,22 +669,33 @@ fn packages<'a>() -> impl Parser<
|
|||
EPackages<'a>,
|
||||
> {
|
||||
record!(KeywordItem {
|
||||
keyword: spaces_around_keyword(
|
||||
PackagesKeyword,
|
||||
EPackages::Packages,
|
||||
EPackages::IndentPackages,
|
||||
EPackages::IndentListStart
|
||||
),
|
||||
item: collection_trailing_sep_e!(
|
||||
byte(b'{', EPackages::ListStart),
|
||||
specialize_err(EPackages::PackageEntry, loc!(package_entry())),
|
||||
byte(b',', EPackages::ListEnd),
|
||||
byte(b'}', EPackages::ListEnd),
|
||||
Spaced::SpaceBefore
|
||||
)
|
||||
keyword: packages_kw(),
|
||||
item: packages_collection()
|
||||
})
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn packages_kw<'a>() -> impl Parser<'a, Spaces<'a, PackagesKeyword>, EPackages<'a>> {
|
||||
spaces_around_keyword(
|
||||
PackagesKeyword,
|
||||
EPackages::Packages,
|
||||
EPackages::IndentPackages,
|
||||
EPackages::IndentListStart,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn packages_collection<'a>(
|
||||
) -> impl Parser<'a, Collection<'a, Loc<Spaced<'a, PackageEntry<'a>>>>, EPackages<'a>> {
|
||||
collection_trailing_sep_e!(
|
||||
byte(b'{', EPackages::ListStart),
|
||||
specialize_err(EPackages::PackageEntry, loc!(package_entry())),
|
||||
byte(b',', EPackages::ListEnd),
|
||||
byte(b'}', EPackages::ListEnd),
|
||||
Spaced::SpaceBefore
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn generates<'a>(
|
||||
) -> impl Parser<'a, KeywordItem<'a, GeneratesKeyword, UppercaseIdent<'a>>, EGenerates> {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue