Parse package module header

This commit is contained in:
Richard Feldman 2022-12-02 16:19:11 -05:00
parent d90f551670
commit da595a86b0
No known key found for this signature in database
GPG Key ID: F1F21AA5B1D9E43B
11 changed files with 149 additions and 52 deletions

View File

@ -8,7 +8,7 @@ use bumpalo::Bump;
use roc_parse::ast::{Collection, Header, Module, Spaced, Spaces};
use roc_parse::header::{
AppHeader, ExposedName, ExposesKeyword, GeneratesKeyword, HostedHeader, ImportsEntry,
ImportsKeyword, InterfaceHeader, Keyword, KeywordItem, ModuleName, PackageEntry,
ImportsKeyword, InterfaceHeader, Keyword, KeywordItem, ModuleName, PackageEntry, PackageHeader,
PackageKeyword, PackageName, PackagesKeyword, PlatformHeader, PlatformRequires,
ProvidesKeyword, ProvidesTo, RequiresKeyword, To, ToKeyword, TypedIdent, WithKeyword,
};
@ -24,6 +24,9 @@ pub fn fmt_module<'a>(buf: &mut Buf<'_>, module: &'a Module<'a>) {
Header::App(header) => {
fmt_app_header(buf, header);
}
Header::Package(header) => {
fmt_package_header(buf, header);
}
Header::Platform(header) => {
fmt_platform_header(buf, header);
}
@ -226,6 +229,22 @@ pub fn fmt_app_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a AppHeader<'a>)
header.provides.format(buf, indent);
}
pub fn fmt_package_header<'a, 'buf>(buf: &mut Buf<'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);
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);
header.imports.keyword.format(buf, indent);
fmt_imports(buf, header.imports.item, indent);
}
pub fn fmt_platform_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a PlatformHeader<'a>) {
buf.indent(0);
buf.push_str("platform");

View File

@ -9,8 +9,8 @@ use roc_parse::{
},
header::{
AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, KeywordItem,
ModuleName, PackageEntry, PackageName, PlatformHeader, PlatformRequires, ProvidesTo, To,
TypedIdent,
ModuleName, PackageEntry, PackageHeader, PackageName, PlatformHeader, PlatformRequires,
ProvidesTo, To, TypedIdent,
},
ident::UppercaseIdent,
};
@ -290,6 +290,13 @@ impl<'a> RemoveSpaces<'a> for Module<'a> {
imports: header.imports.remove_spaces(arena),
provides: header.provides.remove_spaces(arena),
}),
Header::Package(header) => Header::Package(PackageHeader {
before_name: &[],
name: header.name.remove_spaces(arena),
exposes: header.exposes.remove_spaces(arena),
packages: header.packages.remove_spaces(arena),
imports: header.imports.remove_spaces(arena),
}),
Header::Platform(header) => Header::Platform(PlatformHeader {
before_name: &[],
name: header.name.remove_spaces(arena),

View File

@ -3335,6 +3335,19 @@ fn load_platform_module<'a>(
"expected platform/package module, got App with header\n{:?}",
header
))),
Ok((
ast::Module {
header: ast::Header::Package(header),
..
},
parser_state,
)) => {
todo!(
"Send `packag` module using {:?} and {:?}",
header,
parser_state
)
}
Ok((
ast::Module {
header: ast::Header::Platform(header),
@ -3914,6 +3927,16 @@ fn parse_header<'a>(
To::NewPackage(_package_name) => Ok((module_id, app_module_header_msg)),
}
}
Ok((
ast::Module {
header: ast::Header::Package(header),
..
},
parse_state,
)) => {
todo!("Parse header for {:?} --> {:?}", header, parse_state);
}
Ok((
ast::Module {
header: ast::Header::Platform(header),

View File

@ -9,6 +9,7 @@
"app"
"platform"
"package"
"provides"
"requires"
"exposes"

View File

@ -1,6 +1,6 @@
use std::fmt::Debug;
use crate::header::{AppHeader, HostedHeader, InterfaceHeader, PlatformHeader};
use crate::header::{AppHeader, HostedHeader, InterfaceHeader, PackageHeader, PlatformHeader};
use crate::ident::Ident;
use bumpalo::collections::{String, Vec};
use bumpalo::Bump;
@ -90,6 +90,7 @@ pub struct Module<'a> {
pub enum Header<'a> {
Interface(InterfaceHeader<'a>),
App(AppHeader<'a>),
Package(PackageHeader<'a>),
Platform(PlatformHeader<'a>),
Hosted(HostedHeader<'a>),
}

View File

@ -4,7 +4,6 @@ use crate::ident::{lowercase_ident, UppercaseIdent};
use crate::parser::{optional, then};
use crate::parser::{specialize, word1, EPackageEntry, EPackageName, Parser};
use crate::string_literal;
use bumpalo::collections::Vec;
use roc_module::symbol::Symbol;
use roc_region::all::Loc;
use std::fmt::Debug;
@ -214,14 +213,10 @@ pub struct PackageHeader<'a> {
pub before_name: &'a [CommentOrNewline<'a>],
pub name: Loc<PackageName<'a>>,
pub exposes_keyword: Spaces<'a, ExposesKeyword>,
pub exposes: Vec<'a, Loc<Spaced<'a, ExposedName<'a>>>>,
pub packages_keyword: Spaces<'a, PackagesKeyword>,
pub packages: Vec<'a, (Loc<&'a str>, Loc<PackageName<'a>>)>,
pub imports_keyword: Spaces<'a, ImportsKeyword>,
pub imports: Vec<'a, Loc<ImportsEntry<'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 imports: KeywordItem<'a, ImportsKeyword, Collection<'a, Loc<Spaced<'a, ImportsEntry<'a>>>>>,
}
#[derive(Clone, Debug, PartialEq)]

View File

@ -3,8 +3,8 @@ 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, PackagesKeyword, PlatformHeader, PlatformRequires, ProvidesKeyword, ProvidesTo,
RequiresKeyword, To, ToKeyword, TypedIdent, WithKeyword,
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, *};
@ -67,6 +67,13 @@ fn header<'a>() -> impl Parser<'a, Module<'a>, EHeader<'a>> {
),
Header::App
),
map!(
skip_first!(
keyword_e("package", EHeader::Start),
increment_min_indent(package_header())
),
Header::Package
),
map!(
skip_first!(
keyword_e("platform", EHeader::Start),
@ -183,6 +190,18 @@ fn app_header<'a>() -> impl Parser<'a, AppHeader<'a>, EHeader<'a>> {
.trace("app_header")
}
#[inline(always)]
fn package_header<'a>() -> impl Parser<'a, PackageHeader<'a>, EHeader<'a>> {
record!(PackageHeader {
before_name: space0_e(EHeader::IndentStart),
name: loc!(specialize(EHeader::PackageName, package_name())),
exposes: specialize(EHeader::Exposes, exposes_modules()),
packages: specialize(EHeader::Packages, packages()),
imports: specialize(EHeader::Imports, imports()),
})
.trace("package_header")
}
#[inline(always)]
fn platform_header<'a>() -> impl Parser<'a, PlatformHeader<'a>, EHeader<'a>> {
record!(PlatformHeader {

View File

@ -127,6 +127,7 @@ pub enum EHeader<'a> {
Start(Position),
ModuleName(Position),
AppName(EString<'a>, Position),
PackageName(EPackageName<'a>, Position),
PlatformName(EPackageName<'a>, Position),
IndentStart(Position),

View File

@ -31,6 +31,7 @@ pub enum Token {
KeywordTo = 0b_0010_1110,
KeywordExposes = 0b_0010_1111,
KeywordEffects = 0b_0011_0000,
KeywordPackage = 0b_0111_1100,
KeywordPlatform = 0b_0011_0001,
KeywordRequires = 0b_0011_0010,
KeywordDbg = 0b_0111_1011,
@ -428,6 +429,7 @@ fn lex_ident(uppercase: bool, bytes: &[u8]) -> (Token, usize) {
b"to" => Token::KeywordTo,
b"exposes" => Token::KeywordExposes,
b"effects" => Token::KeywordEffects,
b"package" => Token::KeywordPackage,
b"platform" => Token::KeywordPlatform,
b"requires" => Token::KeywordRequires,
ident => {

View File

@ -10,6 +10,7 @@ use std::fs::File;
use std::io::{self, Read, Write};
use std::path::Path;
use tar;
use walkdir::WalkDir;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Compression {
@ -124,7 +125,9 @@ fn write_archive<W: Write>(path: &Path, writer: W) -> io::Result<()> {
let arena = Bump::new();
let mut buf = Vec::new();
let _other_modules: &[Module<'_>] = match read_header(&arena, &mut buf, path)?.header {
// TODO use this when finding .roc files by discovering them from the root module.
// let other_modules: &[Module<'_>] =
match read_header(&arena, &mut buf, path)?.header {
Header::Interface(_) => {
todo!();
// TODO report error
@ -137,9 +140,10 @@ fn write_archive<W: Write>(path: &Path, writer: W) -> io::Result<()> {
todo!();
// TODO report error
}
Header::Package(_) => {
add_dot_roc_files(root_dir, &mut builder)?;
}
Header::Platform(PlatformHeader { imports: _, .. }) => {
use walkdir::WalkDir;
// Add all the prebuilt host files to the archive.
// These should all be in the same directory as the platform module.
for entry in std::fs::read_dir(root_dir)? {
@ -170,38 +174,7 @@ fn write_archive<W: Write>(path: &Path, writer: W) -> io::Result<()> {
}
}
// Recursively find all the .roc files and add them.
// TODO we can do this more efficiently by parsing the platform module and finding
// all of its dependencies. See below for a commented-out WIP sketch of this.
//
// The WalkDir approach is easier to implement, but has the downside of doing things
// like traversing target/ and zig-cache/ which can be large but will never have
// any .roc files in them!
for entry in WalkDir::new(root_dir).into_iter().filter_entry(|entry| {
let path = entry.path();
// We already got the prebuilt host files, so the only other things
// we care about are .roc files.
path.is_dir() || path.extension().and_then(OsStr::to_str) == Some("roc")
}) {
let entry = entry?;
// Only include files, not directories or symlinks.
// Symlinks may not work on Windows, and directories will get automatically
// added based on the paths of the files inside anyway. (In fact, if we don't
// filter out directories in this step, then empty ones will end up getting added!)
if entry.path().is_file() {
builder.append_path_with_name(
entry.path(),
// Store it without the root path, so that (for example) we don't store
// `examples/cli/main.roc` and therefore end up with the root of the tarball
// being an `examples/cli/` dir instead of having `main.roc` in the root.
entry.path().strip_prefix(root_dir).unwrap(),
)?;
}
}
&[]
add_dot_roc_files(root_dir, &mut builder)?;
}
};
@ -246,6 +219,37 @@ fn write_archive<W: Write>(path: &Path, writer: W) -> io::Result<()> {
builder.finish()
}
fn add_dot_roc_files<W: Write>(
root_dir: &Path,
builder: &mut tar::Builder<W>,
) -> Result<(), io::Error> {
Ok(
for entry in WalkDir::new(root_dir).into_iter().filter_entry(|entry| {
let path = entry.path();
// Ignore everything except directories and .roc files
path.is_dir() || path.extension().and_then(OsStr::to_str) == Some("roc")
}) {
let entry = entry?;
let path = entry.path();
// Only include files, not directories or symlinks.
// Symlinks may not work on Windows, and directories will get automatically
// added based on the paths of the files inside anyway. (In fact, if we don't
// filter out directories in this step, then empty ones can sometimes be added!)
if path.is_file() {
builder.append_path_with_name(
path,
// Store it without the root path, so that (for example) we don't store
// `examples/cli/main.roc` and therefore end up with the root of the tarball
// being an `examples/cli/` dir instead of having `main.roc` in the root.
path.strip_prefix(root_dir).unwrap(),
)?;
}
},
)
}
fn read_header<'a>(
arena: &'a Bump,
buf: &'a mut Vec<u8>,

View File

@ -3299,6 +3299,8 @@ fn to_header_report<'a>(
alloc.keyword("interface"),
alloc.reflow(", "),
alloc.keyword("app"),
alloc.reflow(", "),
alloc.keyword("package"),
alloc.reflow(" or "),
alloc.keyword("platform"),
alloc.reflow("."),
@ -3388,12 +3390,35 @@ fn to_header_report<'a>(
}
}
EHeader::PackageName(_, pos) => {
let surroundings = Region::new(start, *pos);
let region = LineColumnRegion::from_pos(lines.convert_pos(*pos));
let doc = alloc.stack([
alloc.reflow(r"I am partway through parsing a package header, but got stuck here:"),
alloc.region_with_subregion(lines.convert_region(surroundings), region),
alloc.concat([
alloc.reflow("I am expecting a package name next, like "),
alloc.parser_suggestion("\"roc/core\""),
alloc.reflow(". Package names must be quoted."),
]),
]);
Report {
filename,
doc,
title: "INVALID PACKAGE NAME".to_string(),
severity: Severity::RuntimeError,
}
}
EHeader::PlatformName(_, pos) => {
let surroundings = Region::new(start, *pos);
let region = LineColumnRegion::from_pos(lines.convert_pos(*pos));
let doc = alloc.stack([
alloc.reflow(r"I am partway through parsing a header, but got stuck here:"),
alloc
.reflow(r"I am partway through parsing a platform header, but got stuck here:"),
alloc.region_with_subregion(lines.convert_region(surroundings), region),
alloc.concat([
alloc.reflow("I am expecting a platform name next, like "),
@ -3405,7 +3430,7 @@ fn to_header_report<'a>(
Report {
filename,
doc,
title: "WEIRD MODULE NAME".to_string(),
title: "INVALID PLATFORM NAME".to_string(),
severity: Severity::RuntimeError,
}
}