Parse platform headers

This commit is contained in:
Richard Feldman 2020-10-17 16:28:36 -04:00
parent a31ed6943f
commit dfa61b4c18
7 changed files with 240 additions and 23 deletions

View file

@ -1,4 +1,4 @@
use crate::header::ModuleName;
use crate::header::{ModuleName, PackageName};
use crate::ident::Ident;
use bumpalo::collections::String;
use bumpalo::collections::Vec;
@ -10,6 +10,7 @@ use roc_region::all::{Loc, Region};
pub enum Module<'a> {
Interface { header: InterfaceHeader<'a> },
App { header: AppHeader<'a> },
Platform { header: PlatformHeader<'a> },
}
#[derive(Clone, Debug, PartialEq)]
@ -19,7 +20,7 @@ pub struct InterfaceHeader<'a> {
pub imports: Vec<'a, Loc<ImportsEntry<'a>>>,
// Potential comments and newlines - these will typically all be empty.
pub after_interface: &'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>],
@ -40,13 +41,48 @@ pub struct AppHeader<'a> {
pub imports: Vec<'a, Loc<ImportsEntry<'a>>>,
// Potential comments and newlines - these will typically all be empty.
pub after_interface: &'a [CommentOrNewline<'a>],
pub after_app_keyword: &'a [CommentOrNewline<'a>],
pub before_provides: &'a [CommentOrNewline<'a>],
pub after_provides: &'a [CommentOrNewline<'a>],
pub before_imports: &'a [CommentOrNewline<'a>],
pub after_imports: &'a [CommentOrNewline<'a>],
}
#[derive(Clone, Debug, PartialEq)]
pub struct PlatformHeader<'a> {
pub name: Loc<PackageName<'a>>,
pub provides: Vec<'a, Loc<ExposesEntry<'a>>>,
pub requires: Vec<'a, Loc<ExposesEntry<'a>>>,
pub imports: Vec<'a, Loc<ImportsEntry<'a>>>,
pub effects: Vec<'a, Loc<EffectsEntry<'a>>>,
// Potential comments and newlines - these will typically all be empty.
pub after_platform_keyword: &'a [CommentOrNewline<'a>],
pub before_provides: &'a [CommentOrNewline<'a>],
pub after_provides: &'a [CommentOrNewline<'a>],
pub before_requires: &'a [CommentOrNewline<'a>],
pub after_requires: &'a [CommentOrNewline<'a>],
pub before_imports: &'a [CommentOrNewline<'a>],
pub after_imports: &'a [CommentOrNewline<'a>],
pub before_effects: &'a [CommentOrNewline<'a>],
pub after_effects: &'a [CommentOrNewline<'a>],
}
#[derive(Clone, Debug, PartialEq)]
pub enum EffectsEntry<'a> {
/// e.g.
///
/// printLine : Str -> Effect {}
Effect {
ident: Loc<&'a str>,
ann: Loc<TypeAnnotation<'a>>,
},
// Spaces
SpaceBefore(&'a EffectsEntry<'a>, &'a [CommentOrNewline<'a>]),
SpaceAfter(&'a EffectsEntry<'a>, &'a [CommentOrNewline<'a>]),
}
#[derive(Clone, Debug, PartialEq)]
pub enum ExposesEntry<'a> {
/// e.g. `Task`
@ -582,6 +618,15 @@ impl<'a> Spaceable<'a> for ImportsEntry<'a> {
}
}
impl<'a> Spaceable<'a> for EffectsEntry<'a> {
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
EffectsEntry::SpaceBefore(self, spaces)
}
fn after(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
EffectsEntry::SpaceAfter(self, spaces)
}
}
impl<'a, Val> Spaceable<'a> for AssignedField<'a, Val> {
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
AssignedField::SpaceBefore(self, spaces)

View file

@ -3,6 +3,12 @@ use bumpalo::collections::Vec;
use inlinable_string::InlinableString;
use roc_region::all::Loc;
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
pub struct PackageName<'a> {
pub account: &'a str,
pub pkg: &'a str,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
pub struct ModuleName<'a>(&'a str);

View file

@ -1,34 +1,41 @@
use crate::ast::{
AppHeader, Attempting, CommentOrNewline, Def, ExposesEntry, ImportsEntry, InterfaceHeader,
Module,
AppHeader, Attempting, CommentOrNewline, Def, EffectsEntry, ExposesEntry, ImportsEntry,
InterfaceHeader, Module, PlatformHeader,
};
use crate::blankspace::{space0_around, space1};
use crate::expr::def;
use crate::header::ModuleName;
use crate::header::{ModuleName, PackageName};
use crate::ident::unqualified_ident;
use crate::parser::{
self, ascii_char, ascii_string, loc, optional, peek_utf8_char, peek_utf8_char_at, unexpected,
Parser, State,
unexpected_eof, ParseResult, Parser, State,
};
use crate::type_annotation;
use bumpalo::collections::{String, Vec};
use bumpalo::Bump;
use roc_region::all::Located;
pub fn header<'a>() -> impl Parser<'a, Module<'a>> {
one_of!(interface_module(), app_module())
one_of!(interface_module(), app_module(), platform_module())
}
#[inline(always)]
pub fn interface_module<'a>() -> impl Parser<'a, Module<'a>> {
fn app_module<'a>() -> impl Parser<'a, Module<'a>> {
map!(app_header(), |header| { Module::App { header } })
}
#[inline(always)]
fn platform_module<'a>() -> impl Parser<'a, Module<'a>> {
map!(platform_header(), |header| { Module::Platform { header } })
}
#[inline(always)]
fn interface_module<'a>() -> impl Parser<'a, Module<'a>> {
map!(interface_header(), |header| {
Module::Interface { header }
})
}
#[inline(always)]
pub fn app_module<'a>() -> impl Parser<'a, Module<'a>> {
map!(app_header(), |header| { Module::App { header } })
}
#[inline(always)]
pub fn interface_header<'a>() -> impl Parser<'a, InterfaceHeader<'a>> {
parser::map(
@ -40,7 +47,7 @@ pub fn interface_header<'a>() -> impl Parser<'a, InterfaceHeader<'a>> {
and!(exposes(), imports())
),
|(
(after_interface, name),
(after_interface_keyword, name),
(
((before_exposes, after_exposes), exposes),
((before_imports, after_imports), imports),
@ -50,7 +57,7 @@ pub fn interface_header<'a>() -> impl Parser<'a, InterfaceHeader<'a>> {
name,
exposes,
imports,
after_interface,
after_interface_keyword,
before_exposes,
after_exposes,
before_imports,
@ -60,6 +67,46 @@ pub fn interface_header<'a>() -> impl Parser<'a, InterfaceHeader<'a>> {
)
}
#[inline(always)]
pub fn package_name<'a>() -> impl Parser<'a, PackageName<'a>> {
// e.g. rtfeldman/blah
//
// Package names and accounts can be capitalized and can contain dashes.
// They cannot contain underscores or other special characters.
// They must be ASCII.
map!(
and!(
move |arena, state| parse_package_part(arena, state),
skip_first!(ascii_char('/'), move |arena, state| parse_package_part(
arena, state
))
),
|(account, pkg)| { PackageName { account, pkg } }
)
}
pub fn parse_package_part<'a>(arena: &'a Bump, mut state: State<'a>) -> ParseResult<'a, &'a str> {
let mut part_buf = String::new_in(arena); // The current "part" (parts are dot-separated.)
while !state.bytes.is_empty() {
match peek_utf8_char(&state) {
Ok((ch, bytes_parsed)) => {
if ch == '-' || ch.is_ascii_alphanumeric() {
part_buf.push(ch);
state = state.advance_without_indenting(bytes_parsed)?;
} else {
return Ok((part_buf.into_bump_str(), state));
}
}
Err(reason) => return state.fail(reason),
}
}
Err(unexpected_eof(0, state.attempting, state))
}
#[inline(always)]
pub fn module_name<'a>() -> impl Parser<'a, ModuleName<'a>> {
move |arena, mut state: State<'a>| {
@ -135,7 +182,7 @@ fn app_header<'a>() -> impl Parser<'a, AppHeader<'a>> {
and!(provides(), imports())
),
|(
(after_interface, name),
(after_app_keyword, name),
(
((before_provides, after_provides), provides),
((before_imports, after_imports), imports),
@ -145,7 +192,7 @@ fn app_header<'a>() -> impl Parser<'a, AppHeader<'a>> {
name,
provides,
imports,
after_interface,
after_app_keyword,
before_provides,
after_provides,
before_imports,
@ -155,6 +202,49 @@ fn app_header<'a>() -> impl Parser<'a, AppHeader<'a>> {
)
}
#[inline(always)]
fn platform_header<'a>() -> impl Parser<'a, PlatformHeader<'a>> {
parser::map(
and!(
skip_first!(
ascii_string("platform"),
and!(space1(1), loc!(package_name()))
),
and!(provides(), and!(requires(), and!(imports(), effects())))
),
|(
(after_platform_keyword, name),
(
((before_provides, after_provides), provides),
(
((before_requires, after_requires), requires),
(
((before_imports, after_imports), imports),
((before_effects, after_effects), effects),
),
),
),
)| {
PlatformHeader {
name,
provides,
requires,
imports,
effects,
after_platform_keyword,
before_provides,
after_provides,
before_requires,
after_requires,
before_imports,
after_imports,
before_effects,
after_effects,
}
},
)
}
#[inline(always)]
pub fn module_defs<'a>() -> impl Parser<'a, Vec<'a, Located<Def<'a>>>> {
zero_or_more!(space0_around(loc(def(0)), 0))
@ -180,6 +270,26 @@ fn provides<'a>() -> impl Parser<
)
}
#[inline(always)]
fn requires<'a>() -> impl Parser<
'a,
(
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
Vec<'a, Located<ExposesEntry<'a>>>,
),
> {
and!(
and!(skip_second!(space1(1), ascii_string("requires")), space1(1)),
collection!(
ascii_char('['),
loc!(exposes_entry()),
ascii_char(','),
ascii_char(']'),
1
)
)
}
#[inline(always)]
fn exposes<'a>() -> impl Parser<
'a,
@ -220,6 +330,37 @@ fn imports<'a>() -> impl Parser<
)
}
#[inline(always)]
fn effects<'a>() -> impl Parser<
'a,
(
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
Vec<'a, Located<EffectsEntry<'a>>>,
),
> {
and!(
and!(skip_second!(space1(1), ascii_string("effects")), space1(1)),
collection!(
ascii_char('{'),
loc!(effects_entry()),
ascii_char(','),
ascii_char('}'),
1
)
)
}
#[inline(always)]
fn effects_entry<'a>() -> impl Parser<'a, EffectsEntry<'a>> {
// e.g.
//
// printLine : Str -> Effect {}
map!(
and!(loc(unqualified_ident()), type_annotation::located(0)),
|(ident, ann)| { EffectsEntry::Effect { ident, ann } }
)
}
#[inline(always)]
fn exposes_entry<'a>() -> impl Parser<'a, ExposesEntry<'a>> {
map!(unqualified_ident(), ExposesEntry::Ident)