Introduce record! combinator

... and refactor header parser to fully use combinators, in support of future combinator-based superpowers
This commit is contained in:
Joshua Warner 2022-11-26 09:48:34 -08:00
parent ec6db293f5
commit 2b91af02df
No known key found for this signature in database
GPG key ID: 89AD497003F93FDD
26 changed files with 1709 additions and 1486 deletions

View file

@ -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<Spaced<'a, ImportsEntry<'a>>>>,
)> = 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<Spaced<'a, ExposedName<'a>>>>,
types: Option<Collection<'a, Loc<Spaced<'a, UppercaseIdent<'a>>>>>,
to: Loc<To<'a>>,
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<Spaced<'a, ExposedName<'a>>>>,
Option<Collection<'a, Loc<Spaced<'a, UppercaseIdent<'a>>>>>,
),
),
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<Spaced<'a, ExposedName<'a>>>>>,
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<Spaced<'a, TypedIdent<'a>>>
#[inline(always)]
fn exposes_values<'a>() -> impl Parser<
'a,
(
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>,
),
KeywordItem<'a, ExposesKeyword, Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>>,
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<Spaced<'a, ModuleName<'a>>>>,
),
KeywordItem<'a, ExposesKeyword, Collection<'a, Loc<Spaced<'a, ModuleName<'a>>>>>,
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<Spaced<'a, PackageEntry<'a>>>>,
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<Spaced<'a, PackageEntry<'a>>>>>,
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<Spaced<'a, ExposedName<'a>>>>,
),
KeywordItem<'a, WithKeyword, Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>>,
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<Spaced<'a, ImportsEntry<'a>>>>,
),
KeywordItem<'a, ImportsKeyword, Collection<'a, Loc<Spaced<'a, ImportsEntry<'a>>>>>,
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")
}