Merge pull request #1062 from rtfeldman/parse-header

Parse header
This commit is contained in:
Richard Feldman 2021-03-10 21:54:51 -05:00 committed by GitHub
commit dd4d447bf1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 1556 additions and 593 deletions

View File

@ -22,7 +22,7 @@ use roc_mono::ir::{
CapturedSymbols, ExternalSpecializations, PartialProc, PendingSpecialization, Proc, Procs,
};
use roc_mono::layout::{Layout, LayoutCache, LayoutProblem};
use roc_parse::ast::{self, Attempting, StrLiteral, TypeAnnotation};
use roc_parse::ast::{self, StrLiteral, TypeAnnotation};
use roc_parse::header::{
ExposesEntry, ImportsEntry, PackageEntry, PackageOrPath, PlatformHeader, To, TypedIdent,
};
@ -2304,8 +2304,8 @@ fn load_pkg_config<'a>(
Ok(bytes_vec) => {
let parse_start = SystemTime::now();
let bytes = arena.alloc(bytes_vec);
let parse_state = parser::State::new_in(arena, bytes, Attempting::Module);
let parsed = roc_parse::module::header().parse(&arena, parse_state);
let parse_state = parser::State::new_in(arena, bytes);
let parsed = roc_parse::module::parse_header(&arena, parse_state);
let parse_header_duration = parse_start.elapsed().unwrap();
// Insert the first entries for this module's timings
@ -2319,19 +2319,19 @@ fn load_pkg_config<'a>(
effect_module_timing.parse_header = parse_header_duration;
match parsed {
Ok((_, ast::Module::Interface { header }, _parse_state)) => {
Ok((ast::Module::Interface { header }, _parse_state)) => {
Err(LoadingProblem::UnexpectedHeader(format!(
"expected platform/package module, got Interface with header\n{:?}",
header
)))
}
Ok((_, ast::Module::App { header }, _parse_state)) => {
Ok((ast::Module::App { header }, _parse_state)) => {
Err(LoadingProblem::UnexpectedHeader(format!(
"expected platform/package module, got App with header\n{:?}",
header
)))
}
Ok((_, ast::Module::Platform { header }, parser_state)) => {
Ok((ast::Module::Platform { header }, parser_state)) => {
// make a Pkg-Config module that ultimately exposes `main` to the host
let pkg_config_module_msg = fabricate_pkg_config_module(
arena,
@ -2359,8 +2359,8 @@ fn load_pkg_config<'a>(
Ok(Msg::Many(vec![effects_module_msg, pkg_config_module_msg]))
}
Err((_, fail, _)) => Err(LoadingProblem::ParsingFailed(
fail.into_parse_problem(filename, bytes),
Err(fail) => Err(LoadingProblem::ParsingFailed(
SyntaxError::Header(fail).into_parse_problem(filename, bytes),
)),
}
}
@ -2474,8 +2474,8 @@ fn parse_header<'a>(
start_time: SystemTime,
) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> {
let parse_start = SystemTime::now();
let parse_state = parser::State::new_in(arena, src_bytes, Attempting::Module);
let parsed = roc_parse::module::header().parse(&arena, parse_state);
let parse_state = parser::State::new_in(arena, src_bytes);
let parsed = roc_parse::module::parse_header(&arena, parse_state);
let parse_header_duration = parse_start.elapsed().unwrap();
// Insert the first entries for this module's timings
@ -2485,7 +2485,7 @@ fn parse_header<'a>(
module_timing.parse_header = parse_header_duration;
match parsed {
Ok((_, ast::Module::Interface { header }, parse_state)) => {
Ok((ast::Module::Interface { header }, parse_state)) => {
let header_src = unsafe {
let chomped = src_bytes.len() - parse_state.bytes.len();
std::str::from_utf8_unchecked(&src_bytes[..chomped])
@ -2514,7 +2514,7 @@ fn parse_header<'a>(
module_timing,
))
}
Ok((_, ast::Module::App { header }, parse_state)) => {
Ok((ast::Module::App { header }, parse_state)) => {
let mut pkg_config_dir = filename.clone();
pkg_config_dir.pop();
@ -2623,7 +2623,7 @@ fn parse_header<'a>(
},
}
}
Ok((_, ast::Module::Platform { header }, _parse_state)) => Ok(fabricate_effects_module(
Ok((ast::Module::Platform { header }, _parse_state)) => Ok(fabricate_effects_module(
arena,
&"",
module_ids,
@ -2632,8 +2632,8 @@ fn parse_header<'a>(
header,
module_timing,
)),
Err((_, fail, _)) => Err(LoadingProblem::ParsingFailed(
fail.into_parse_problem(filename, src_bytes),
Err(fail) => Err(LoadingProblem::ParsingFailed(
SyntaxError::Header(fail).into_parse_problem(filename, src_bytes),
)),
}
}

View File

@ -20,6 +20,7 @@ use roc_region::all::{Located, Region};
use crate::parser::Progress::{self, *};
// public for testing purposes
pub fn expr<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, SyntaxError<'a>> {
// Recursive parsers must not directly invoke functions which return (impl Parser),
// as this causes rustc to stack overflow. Thus, parse_expr must be a

View File

@ -1,12 +1,11 @@
use crate::blankspace::space0;
use crate::ast::{CommentOrNewline, Spaceable, StrLiteral, TypeAnnotation};
use crate::blankspace::space0_e;
use crate::ident::lowercase_ident;
use crate::module::package_name;
use crate::parser::{ascii_char, optional, Either, Parser, Progress::*, State, SyntaxError};
use crate::string_literal;
use crate::{
ast::{CommentOrNewline, Spaceable, StrLiteral, TypeAnnotation},
parser::specialize,
use crate::parser::Progress::{self, *};
use crate::parser::{
specialize, word1, EPackageEntry, EPackageName, EPackageOrPath, Parser, State,
};
use crate::string_literal;
use bumpalo::collections::Vec;
use inlinable_string::InlinableString;
use roc_region::all::Loc;
@ -242,18 +241,32 @@ impl<'a> Spaceable<'a> for PackageEntry<'a> {
}
}
pub fn package_entry<'a>() -> impl Parser<'a, PackageEntry<'a>, SyntaxError<'a>> {
pub fn package_entry<'a>() -> impl Parser<'a, PackageEntry<'a>, EPackageEntry<'a>> {
move |arena, state| {
// You may optionally have a package shorthand,
// e.g. "uc" in `uc: roc/unicode 1.0.0`
//
// (Indirect dependencies don't have a shorthand.)
let (_, opt_shorthand, state) = optional(and!(
skip_second!(lowercase_ident(), ascii_char(b':')),
space0(1)
let min_indent = 1;
let (_, opt_shorthand, state) = maybe!(and!(
skip_second!(
specialize(|_, r, c| EPackageEntry::Shorthand(r, c), lowercase_ident()),
word1(b':', EPackageEntry::Colon)
),
space0_e(
min_indent,
EPackageEntry::Space,
EPackageEntry::IndentPackageOrPath
)
))
.parse(arena, state)?;
let (_, package_or_path, state) = loc!(specialize(
EPackageEntry::BadPackageOrPath,
package_or_path()
))
.parse(arena, state)?;
let (_, package_or_path, state) = loc!(package_or_path()).parse(arena, state)?;
let entry = match opt_shorthand {
Some((shorthand, spaces_after_shorthand)) => PackageEntry::Entry {
@ -272,27 +285,117 @@ pub fn package_entry<'a>() -> impl Parser<'a, PackageEntry<'a>, SyntaxError<'a>>
}
}
pub fn package_or_path<'a>() -> impl Parser<'a, PackageOrPath<'a>, SyntaxError<'a>> {
map!(
either!(
specialize(
|e, r, c| SyntaxError::Expr(crate::parser::EExpr::Str(e, r, c)),
string_literal::parse()
),
and!(
package_name(),
skip_first!(one_or_more!(ascii_char(b' ')), package_version())
)
pub fn package_or_path<'a>() -> impl Parser<'a, PackageOrPath<'a>, EPackageOrPath<'a>> {
one_of![
map!(
specialize(EPackageOrPath::BadPath, string_literal::parse()),
PackageOrPath::Path
),
|answer| {
match answer {
Either::First(str_literal) => PackageOrPath::Path(str_literal),
Either::Second((name, version)) => PackageOrPath::Package(name, version),
}
}
)
map!(
and!(
specialize(EPackageOrPath::BadPackage, package_name()),
skip_first!(skip_spaces(), package_version())
),
|(name, version)| { PackageOrPath::Package(name, version) }
)
]
}
fn package_version<'a>() -> impl Parser<'a, Version<'a>, SyntaxError<'a>> {
fn skip_spaces<'a, T>() -> impl Parser<'a, (), T>
where
T: 'a,
{
|_, mut state: State<'a>| {
let mut chomped = 0;
let mut it = state.bytes.iter();
while let Some(b' ') = it.next() {
chomped += 1;
}
if chomped == 0 {
Ok((NoProgress, (), state))
} else {
state.column += chomped;
state.bytes = it.as_slice();
Ok((MadeProgress, (), state))
}
}
}
fn package_version<'a, T>() -> impl Parser<'a, Version<'a>, T>
where
T: 'a,
{
move |_, _| todo!("TODO parse package version")
}
#[inline(always)]
pub fn package_name<'a>() -> impl Parser<'a, PackageName<'a>, EPackageName> {
use encode_unicode::CharExt;
// 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.
|_, mut state: State<'a>| match chomp_package_part(state.bytes) {
Err(progress) => Err((
progress,
EPackageName::Account(state.line, state.column),
state,
)),
Ok(account) => {
let mut chomped = account.len();
if let Ok(('/', width)) = char::from_utf8_slice_start(&state.bytes[chomped..]) {
chomped += width;
match chomp_package_part(&state.bytes[chomped..]) {
Err(progress) => Err((
progress,
EPackageName::Pkg(state.line, state.column + chomped as u16),
state,
)),
Ok(pkg) => {
chomped += pkg.len();
state.column += chomped as u16;
state.bytes = &state.bytes[chomped..];
let value = PackageName { account, pkg };
Ok((MadeProgress, value, state))
}
}
} else {
Err((
MadeProgress,
EPackageName::MissingSlash(state.line, state.column + chomped as u16),
state,
))
}
}
}
}
fn chomp_package_part(buffer: &[u8]) -> Result<&str, Progress> {
use encode_unicode::CharExt;
let mut chomped = 0;
while let Ok((ch, width)) = char::from_utf8_slice_start(&buffer[chomped..]) {
if ch == '-' || ch.is_ascii_alphanumeric() {
chomped += width;
} else {
// we're done
break;
}
}
if chomped == 0 {
Err(Progress::NoProgress)
} else {
let name = unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) };
Ok(name)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -43,7 +43,7 @@ pub enum Either<First, Second> {
}
impl<'a> State<'a> {
pub fn new_in(arena: &'a Bump, bytes: &'a [u8], _attempting: Attempting) -> State<'a> {
pub fn new_in(arena: &'a Bump, bytes: &'a [u8]) -> State<'a> {
State {
bytes,
line: 0,
@ -334,9 +334,147 @@ pub enum SyntaxError<'a> {
Type(Type<'a>),
Pattern(EPattern<'a>),
Expr(EExpr<'a>),
Header(EHeader<'a>),
Space(BadInputError),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EHeader<'a> {
Provides(EProvides<'a>, Row, Col),
Exposes(EExposes, Row, Col),
Imports(EImports, Row, Col),
Requires(ERequires<'a>, Row, Col),
Packages(EPackages<'a>, Row, Col),
Effects(EEffects<'a>, Row, Col),
Space(BadInputError, Row, Col),
Start(Row, Col),
ModuleName(Row, Col),
AppName(EString<'a>, Row, Col),
PlatformName(EPackageName, Row, Col),
IndentStart(Row, Col),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EProvides<'a> {
Provides(Row, Col),
To(Row, Col),
IndentProvides(Row, Col),
IndentTo(Row, Col),
IndentListStart(Row, Col),
IndentListEnd(Row, Col),
IndentPackage(Row, Col),
ListStart(Row, Col),
ListEnd(Row, Col),
Identifier(Row, Col),
Package(EPackageOrPath<'a>, Row, Col),
Space(BadInputError, Row, Col),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EExposes {
Exposes(Row, Col),
IndentExposes(Row, Col),
IndentListStart(Row, Col),
IndentListEnd(Row, Col),
ListStart(Row, Col),
ListEnd(Row, Col),
Identifier(Row, Col),
Space(BadInputError, Row, Col),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ERequires<'a> {
Requires(Row, Col),
IndentRequires(Row, Col),
IndentListStart(Row, Col),
IndentListEnd(Row, Col),
ListStart(Row, Col),
ListEnd(Row, Col),
TypedIdent(ETypedIdent<'a>, Row, Col),
Space(BadInputError, Row, Col),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ETypedIdent<'a> {
Space(BadInputError, Row, Col),
HasType(Row, Col),
IndentHasType(Row, Col),
Name(Row, Col),
Type(Type<'a>, Row, Col),
IndentType(Row, Col),
Identifier(Row, Col),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EPackages<'a> {
Space(BadInputError, Row, Col),
Packages(Row, Col),
IndentPackages(Row, Col),
ListStart(Row, Col),
ListEnd(Row, Col),
IndentListStart(Row, Col),
IndentListEnd(Row, Col),
PackageEntry(EPackageEntry<'a>, Row, Col),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EPackageName {
MissingSlash(Row, Col),
Account(Row, Col),
Pkg(Row, Col),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EPackageOrPath<'a> {
BadPath(EString<'a>, Row, Col),
BadPackage(EPackageName, Row, Col),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EPackageEntry<'a> {
BadPackageOrPath(EPackageOrPath<'a>, Row, Col),
Shorthand(Row, Col),
Colon(Row, Col),
IndentPackageOrPath(Row, Col),
Space(BadInputError, Row, Col),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EEffects<'a> {
Space(BadInputError, Row, Col),
Effects(Row, Col),
IndentEffects(Row, Col),
ListStart(Row, Col),
ListEnd(Row, Col),
IndentListStart(Row, Col),
IndentListEnd(Row, Col),
TypedIdent(ETypedIdent<'a>, Row, Col),
ShorthandDot(Row, Col),
Shorthand(Row, Col),
TypeName(Row, Col),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EImports {
Imports(Row, Col),
IndentImports(Row, Col),
IndentListStart(Row, Col),
IndentListEnd(Row, Col),
ListStart(Row, Col),
ListEnd(Row, Col),
Identifier(Row, Col),
ExposingDot(Row, Col),
ShorthandDot(Row, Col),
Shorthand(Row, Col),
ModuleName(Row, Col),
Space(BadInputError, Row, Col),
IndentSetStart(Row, Col),
IndentSetEnd(Row, Col),
SetStart(Row, Col),
SetEnd(Row, Col),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BadInputError {
HasTab,
@ -446,14 +584,6 @@ pub enum EString<'a> {
Format(&'a SyntaxError<'a>, Row, Col),
}
// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
// pub enum Escape {
// EscapeUnknown,
// BadUnicodeFormat(u16),
// BadUnicodeCode(u16),
// BadUnicodeLength(u16, i32, i32),
// }
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ERecord<'a> {
End(Row, Col),
@ -1128,31 +1258,27 @@ where
ToError: Fn(Row, Col) -> E,
E: 'a,
{
move |arena, state: State<'a>| {
let initial_state = state.clone();
// first parse the keyword characters
let (_, _, after_keyword_state) = ascii_string(keyword)
.parse(arena, state)
.map_err(|(_, _, state)| (NoProgress, if_error(state.line, state.column), state))?;
move |_, mut state: State<'a>| {
let width = keyword.len();
// then we must have at least one space character
// TODO this is potentially wasteful if there are a lot of spaces
match peek_utf8_char(&after_keyword_state) {
Ok((next, _width)) if next == ' ' || next == '#' || next == '\n' => {
// give back the state after parsing the keyword, but before the whitespace
// that way we can attach the whitespace to whatever follows
Ok((MadeProgress, (), after_keyword_state))
if !state.bytes.starts_with(keyword.as_bytes()) {
return Err((NoProgress, if_error(state.line, state.column), state));
}
// the next character should not be an identifier character
// to prevent treating `whence` or `iffy` as keywords
match state.bytes.get(width) {
Some(next) if *next == b' ' || *next == b'#' || *next == b'\n' => {
state.column += width as u16;
state.bytes = &state.bytes[width..];
Ok((MadeProgress, (), state))
}
_ => {
// this is not a keyword, maybe it's `whence` or `iffy`
// anyway, make no progress and return the initial state
// so we can try something else
Err((
NoProgress,
if_error(initial_state.line, initial_state.column),
initial_state,
))
None => {
state.column += width as u16;
state.bytes = &state.bytes[width..];
Ok((MadeProgress, (), state))
}
Some(_) => Err((NoProgress, if_error(state.line, state.column), state)),
}
}
}
@ -1589,6 +1715,46 @@ macro_rules! collection {
};
}
#[macro_export]
macro_rules! collection_e {
($opening_brace:expr, $elem:expr, $delimiter:expr, $closing_brace:expr, $min_indent:expr, $space_problem:expr, $indent_problem:expr) => {
skip_first!(
$opening_brace,
skip_first!(
// We specifically allow space characters inside here, so that
// `[ ]` can be successfully parsed as an empty list, and then
// changed by the formatter back into `[]`.
//
// We don't allow newlines or comments in the middle of empty
// roc_collections because those are normally stored in an Expr,
// and there's no Expr in which to store them in an empty collection!
//
// We could change the AST to add extra storage specifically to
// support empty literals containing newlines or comments, but this
// does not seem worth even the tiniest regression in compiler performance.
zero_or_more!($crate::parser::word1(b' ', |row, col| $space_problem(
crate::parser::BadInputError::LineTooLong,
row,
col
))),
skip_second!(
$crate::parser::sep_by0(
$delimiter,
$crate::blankspace::space0_around_ee(
$elem,
$min_indent,
$space_problem,
$indent_problem,
$indent_problem
)
),
$closing_brace
)
)
)
};
}
/// Parse zero or more elements between two braces (e.g. square braces).
/// Elements can be optionally surrounded by spaces, and are separated by a
/// delimiter (e.g comma-separated) with optionally a trailing delimiter.
@ -1719,6 +1885,19 @@ macro_rules! one_of {
};
}
#[macro_export]
macro_rules! maybe {
($p1:expr) => {
move |arena: &'a bumpalo::Bump, state: $crate::parser::State<'a>| match $p1
.parse(arena, state)
{
Ok((progress, value, state)) => Ok((progress, Some(value), state)),
Err((MadeProgress, fail, state)) => Err((MadeProgress, fail, state)),
Err((NoProgress, _, state)) => Ok((NoProgress, None, state)),
}
};
}
#[macro_export]
macro_rules! one_of_with_error {
($toerror:expr; $p1:expr) => {

View File

@ -1,13 +1,12 @@
use crate::ast::{self, Attempting};
use crate::ast;
use crate::blankspace::space0_before;
use crate::expr::expr;
use crate::module::{header, module_defs};
use crate::module::module_defs;
use crate::parser::{loc, Parser, State, SyntaxError};
use bumpalo::collections::Vec;
use bumpalo::Bump;
use roc_region::all::Located;
#[allow(dead_code)]
pub fn parse_expr_with<'a>(
arena: &'a Bump,
input: &'a str,
@ -15,24 +14,12 @@ pub fn parse_expr_with<'a>(
parse_loc_with(arena, input).map(|loc_expr| loc_expr.value)
}
pub fn parse_header_with<'a>(
arena: &'a Bump,
input: &'a str,
) -> Result<ast::Module<'a>, SyntaxError<'a>> {
let state = State::new_in(arena, input.trim().as_bytes(), Attempting::Module);
let answer = header().parse(arena, state);
answer
.map(|(_, loc_expr, _)| loc_expr)
.map_err(|(_, fail, _)| fail)
}
#[allow(dead_code)]
pub fn parse_defs_with<'a>(
arena: &'a Bump,
input: &'a str,
) -> Result<Vec<'a, Located<ast::Def<'a>>>, SyntaxError<'a>> {
let state = State::new_in(arena, input.trim().as_bytes(), Attempting::Module);
let state = State::new_in(arena, input.trim().as_bytes());
let answer = module_defs().parse(arena, state);
answer
.map(|(_, loc_expr, _)| loc_expr)
@ -44,7 +31,7 @@ pub fn parse_loc_with<'a>(
arena: &'a Bump,
input: &'a str,
) -> Result<Located<ast::Expr<'a>>, SyntaxError<'a>> {
let state = State::new_in(arena, input.trim().as_bytes(), Attempting::Module);
let state = State::new_in(arena, input.trim().as_bytes());
let parser = space0_before(loc(expr(0)), 0);
let answer = parser.parse(&arena, state);

View File

@ -23,14 +23,12 @@ mod test_parse {
use roc_parse::ast::Pattern::{self, *};
use roc_parse::ast::StrLiteral::{self, *};
use roc_parse::ast::StrSegment::*;
use roc_parse::ast::{
self, Attempting, Def, EscapedChar, Spaceable, TypeAnnotation, WhenBranch,
};
use roc_parse::ast::{self, Def, EscapedChar, Spaceable, TypeAnnotation, WhenBranch};
use roc_parse::header::{
AppHeader, Effects, ExposesEntry, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry,
PackageName, PackageOrPath, PlatformHeader, To,
};
use roc_parse::module::{app_header, interface_header, module_defs, platform_header};
use roc_parse::module::module_defs;
use roc_parse::parser::{Parser, State, SyntaxError};
use roc_parse::test_helpers::parse_expr_with;
use roc_region::all::{Located, Region};
@ -43,10 +41,9 @@ mod test_parse {
assert_eq!(Ok(expected_expr), actual);
}
fn assert_parsing_fails<'a>(input: &'a str, _reason: SyntaxError, _attempting: Attempting) {
fn assert_parsing_fails<'a>(input: &'a str, _reason: SyntaxError) {
let arena = Bump::new();
let actual = parse_expr_with(&arena, input);
// let expected_fail = Fail { reason, attempting };
assert!(actual.is_err());
}
@ -291,7 +288,7 @@ mod test_parse {
#[test]
fn empty_source_file() {
assert_parsing_fails("", SyntaxError::Eof(Region::zero()), Attempting::Module);
assert_parsing_fails("", SyntaxError::Eof(Region::zero()));
}
#[test]
@ -308,11 +305,7 @@ mod test_parse {
// Make sure it's longer than our maximum line length
assert_eq!(too_long_str.len(), max_line_length + 1);
assert_parsing_fails(
&too_long_str,
SyntaxError::LineTooLong(0),
Attempting::Module,
);
assert_parsing_fails(&too_long_str, SyntaxError::LineTooLong(0));
}
// INT LITERALS
@ -2416,7 +2409,7 @@ mod test_parse {
let imports = Vec::new_in(&arena);
let provides = Vec::new_in(&arena);
let module_name = StrLiteral::PlainLine("test-app");
let expected = AppHeader {
let header = AppHeader {
name: Located::new(0, 0, 4, 14, module_name),
packages,
imports,
@ -2433,17 +2426,15 @@ mod test_parse {
after_to: &[],
};
let expected = roc_parse::ast::Module::App { header };
let src = indoc!(
r#"
app "test-app" packages {} imports [] provides [] to blah
"#
);
let actual = app_header()
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
let actual = roc_parse::module::parse_header(&arena, State::new_in(&arena, src.as_bytes()))
.map(|tuple| tuple.0);
assert_eq!(Ok(expected), actual);
}
@ -2457,7 +2448,7 @@ mod test_parse {
let imports = Vec::new_in(&arena);
let provides = Vec::new_in(&arena);
let module_name = StrLiteral::PlainLine("test-app");
let expected = AppHeader {
let header = AppHeader {
name: Located::new(0, 0, 4, 14, module_name),
packages,
imports,
@ -2474,17 +2465,16 @@ mod test_parse {
after_to: &[],
};
let expected = roc_parse::ast::Module::App { header };
let src = indoc!(
r#"
app "test-app" provides [] to "./blah"
"#
);
let actual = app_header()
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
let actual = roc_parse::module::parse_header(&arena, State::new_in(&arena, src.as_bytes()))
.map(|tuple| tuple.0);
assert_eq!(Ok(expected), actual);
}
@ -2509,7 +2499,8 @@ mod test_parse {
let provide_entry = Located::new(3, 3, 15, 24, Exposed("quicksort"));
let provides = bumpalo::vec![in &arena; provide_entry];
let module_name = StrLiteral::PlainLine("quicksort");
let expected = AppHeader {
let header = AppHeader {
name: Located::new(0, 0, 4, 15, module_name),
packages,
imports,
@ -2526,6 +2517,8 @@ mod test_parse {
after_to: &[],
};
let expected = roc_parse::ast::Module::App { header };
let src = indoc!(
r#"
app "quicksort"
@ -2535,12 +2528,8 @@ mod test_parse {
"#
);
let actual = app_header()
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
let actual = roc_parse::module::parse_header(&arena, State::new_in(&arena, src.as_bytes()))
.map(|tuple| tuple.0);
assert_eq!(Ok(expected), actual);
}
@ -2560,7 +2549,7 @@ mod test_parse {
spaces_after_effects_keyword: &[],
spaces_after_type_name: &[],
};
let expected = PlatformHeader {
let header = PlatformHeader {
name: Located::new(0, 0, 9, 23, pkg_name),
requires: Vec::new_in(&arena),
exposes: Vec::new_in(&arena),
@ -2581,13 +2570,11 @@ mod test_parse {
after_provides: &[],
};
let expected = roc_parse::ast::Module::Platform { header };
let src = "platform rtfeldman/blah requires {} exposes [] packages {} imports [] provides [] effects fx.Blah {}";
let actual = platform_header()
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
let actual = roc_parse::module::parse_header(&arena, State::new_in(&arena, src.as_bytes()))
.map(|tuple| tuple.0);
assert_eq!(Ok(expected), actual);
}
@ -2621,7 +2608,7 @@ mod test_parse {
spaces_after_effects_keyword: &[],
spaces_after_type_name: &[],
};
let expected = PlatformHeader {
let header = PlatformHeader {
name: Located::new(0, 0, 9, 19, pkg_name),
requires: Vec::new_in(&arena),
exposes: Vec::new_in(&arena),
@ -2642,6 +2629,8 @@ mod test_parse {
after_provides: &[],
};
let expected = roc_parse::ast::Module::Platform { header };
let src = indoc!(
r#"
platform foo/barbaz
@ -2653,12 +2642,8 @@ mod test_parse {
effects fx.Effect {}
"#
);
let actual = platform_header()
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
let actual = roc_parse::module::parse_header(&arena, State::new_in(&arena, src.as_bytes()))
.map(|tuple| tuple.0);
assert_eq!(Ok(expected), actual);
}
@ -2669,7 +2654,7 @@ mod test_parse {
let exposes = Vec::new_in(&arena);
let imports = Vec::new_in(&arena);
let module_name = ModuleName::new("Foo");
let expected = InterfaceHeader {
let header = InterfaceHeader {
name: Located::new(0, 0, 10, 13, module_name),
exposes,
imports,
@ -2680,17 +2665,16 @@ mod test_parse {
before_imports: &[],
after_imports: &[],
};
let expected = roc_parse::ast::Module::Interface { header };
let src = indoc!(
r#"
interface Foo exposes [] imports []
"#
);
let actual = interface_header()
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
let actual = roc_parse::module::parse_header(&arena, State::new_in(&arena, src.as_bytes()))
.map(|tuple| tuple.0);
assert_eq!(Ok(expected), actual);
}
@ -2701,7 +2685,7 @@ mod test_parse {
let exposes = Vec::new_in(&arena);
let imports = Vec::new_in(&arena);
let module_name = ModuleName::new("Foo.Bar.Baz");
let expected = InterfaceHeader {
let header = InterfaceHeader {
name: Located::new(0, 0, 10, 21, module_name),
exposes,
imports,
@ -2712,17 +2696,16 @@ mod test_parse {
before_imports: &[],
after_imports: &[],
};
let expected = roc_parse::ast::Module::Interface { header };
let src = indoc!(
r#"
interface Foo.Bar.Baz exposes [] imports []
"#
);
let actual = interface_header()
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
let actual = roc_parse::module::parse_header(&arena, State::new_in(&arena, src.as_bytes()))
.map(|tuple| tuple.0);
assert_eq!(Ok(expected), actual);
}
@ -2748,10 +2731,7 @@ mod test_parse {
"#
);
let actual = module_defs()
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.parse(&arena, State::new_in(&arena, src.as_bytes()))
.map(|tuple| tuple.1);
// It should occur twice in the debug output - once for the pattern,
@ -2810,10 +2790,7 @@ mod test_parse {
);
let actual = module_defs()
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.parse(&arena, State::new_in(&arena, src.as_bytes()))
.map(|tuple| tuple.1);
assert_eq!(Ok(expected), actual);
@ -2833,11 +2810,8 @@ mod test_parse {
);
let actual = module_defs()
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
.parse(&arena, State::new_in(&arena, src.as_bytes()))
.map(|tuple| tuple.0);
assert!(actual.is_ok());
}
@ -2858,11 +2832,8 @@ mod test_parse {
);
let actual = module_defs()
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
.parse(&arena, State::new_in(&arena, src.as_bytes()))
.map(|tuple| tuple.0);
assert!(actual.is_ok());
}
@ -2882,11 +2853,8 @@ mod test_parse {
);
let actual = module_defs()
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
.parse(&arena, State::new_in(&arena, src.as_bytes()))
.map(|tuple| tuple.0);
dbg!(&actual);

View File

@ -153,6 +153,7 @@ fn to_syntax_report<'a>(
0,
0,
),
Header(header) => to_header_report(alloc, filename, &header, 0, 0),
_ => todo!("unhandled parse error: {:?}", parse_problem),
}
}
@ -204,7 +205,7 @@ fn to_expr_report<'a>(
let region = *region;
let doc = alloc.stack(vec![
alloc.reflow(r"I am in the middle of parsing a definition, but I got stuck here:"),
alloc.reflow(r"I am partway through parsing a definition, but I got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("Looks like you are trying to define a function. "),
@ -380,7 +381,7 @@ fn to_expr_report<'a>(
let region = Region::from_row_col(*row, *col);
let doc = alloc.stack(vec![
alloc.reflow(r"I am in the middle of parsing a definition, but I got stuck here:"),
alloc.reflow(r"I am partway through parsing a definition, but I got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("Looks like you are trying to define a function. "),
@ -419,7 +420,7 @@ fn to_lambda_report<'a>(
let doc = alloc.stack(vec![
alloc
.reflow(r"I am in the middle of parsing a function argument list, but I got stuck here:"),
.reflow(r"I am partway through parsing a function argument list, but I got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("I was expecting a "),
@ -440,7 +441,7 @@ fn to_lambda_report<'a>(
let doc = alloc.stack(vec![
alloc
.reflow(r"I am in the middle of parsing a function argument list, but I got stuck here:"),
.reflow(r"I am partway through parsing a function argument list, but I got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("I was expecting a "),
@ -464,7 +465,7 @@ fn to_lambda_report<'a>(
let doc = alloc.stack(vec![
alloc
.reflow(r"I am in the middle of parsing a function argument list, but I got stuck here:"),
.reflow(r"I am partway through parsing a function argument list, but I got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("I was expecting a "),
@ -485,7 +486,7 @@ fn to_lambda_report<'a>(
let doc = alloc.stack(vec![
alloc
.reflow(r"I am in the middle of parsing a function argument list, but I got stuck here:"),
.reflow(r"I am partway through parsing a function argument list, but I got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("I was expecting a "),
@ -509,7 +510,7 @@ fn to_lambda_report<'a>(
let doc = alloc.stack(vec![
alloc
.reflow(r"I am in the middle of parsing a function argument list, but I got stuck at this comma:"),
.reflow(r"I am partway through parsing a function argument list, but I got stuck at this comma:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("I was expecting an argument pattern before this, "),
@ -529,7 +530,7 @@ fn to_lambda_report<'a>(
let doc = alloc.stack(vec![
alloc
.reflow(r"I am in the middle of parsing a function argument list, but I got stuck here:"),
.reflow(r"I am partway through parsing a function argument list, but I got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("I was expecting an argument pattern before this, "),
@ -2469,6 +2470,479 @@ fn to_tapply_report<'a>(
}
}
fn to_header_report<'a>(
alloc: &'a RocDocAllocator<'a>,
filename: PathBuf,
parse_problem: &roc_parse::parser::EHeader<'a>,
start_row: Row,
start_col: Col,
) -> Report<'a> {
use roc_parse::parser::EHeader;
match parse_problem {
EHeader::Provides(provides, row, col) => {
to_provides_report(alloc, filename, &provides, *row, *col)
}
EHeader::Exposes(exposes, row, col) => {
to_exposes_report(alloc, filename, &exposes, *row, *col)
}
EHeader::Imports(imports, row, col) => {
to_imports_report(alloc, filename, &imports, *row, *col)
}
EHeader::Requires(requires, row, col) => {
to_requires_report(alloc, filename, &requires, *row, *col)
}
EHeader::Packages(packages, row, col) => {
to_packages_report(alloc, filename, &packages, *row, *col)
}
EHeader::Effects(effects, row, col) => {
to_effects_report(alloc, filename, &effects, *row, *col)
}
EHeader::IndentStart(row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, *row, *col);
let region = Region::from_row_col(*row, *col);
let doc = alloc.stack(vec![
alloc.reflow(r"I am partway through parsing a header, but got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![alloc.reflow("I may be confused by indentation.")]),
]);
Report {
filename,
doc,
title: "INCOMPLETE HEADER".to_string(),
}
}
EHeader::Start(row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, *row, *col);
let region = Region::from_row_col(*row, *col);
let doc = alloc.stack(vec![
alloc.reflow(r"I am expecting a header, but got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("I am expecting a module keyword next, one of "),
alloc.keyword("interface"),
alloc.reflow(", "),
alloc.keyword("app"),
alloc.reflow(" or "),
alloc.keyword("platform"),
alloc.reflow("."),
]),
]);
Report {
filename,
doc,
title: "MISSING HEADER".to_string(),
}
}
EHeader::ModuleName(row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, *row, *col);
let region = Region::from_row_col(*row, *col);
let doc = alloc.stack(vec![
alloc.reflow(r"I am partway through parsing a header, but got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("I am expecting a module name next, like "),
alloc.parser_suggestion("BigNum"),
alloc.reflow(" or "),
alloc.parser_suggestion("Main"),
alloc.reflow(". Module names must start with an uppercase letter."),
]),
]);
Report {
filename,
doc,
title: "WEIRD MODULE NAME".to_string(),
}
}
EHeader::AppName(_, row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, *row, *col);
let region = Region::from_row_col(*row, *col);
let doc = alloc.stack(vec![
alloc.reflow(r"I am partway through parsing a header, but got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("I am expecting an application name next, like "),
alloc.parser_suggestion("app \"main\""),
alloc.reflow(" or "),
alloc.parser_suggestion("app \"editor\""),
alloc.reflow(". App names are surrounded by quotation marks."),
]),
]);
Report {
filename,
doc,
title: "WEIRD APP NAME".to_string(),
}
}
EHeader::PlatformName(_, row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, *row, *col);
let region = Region::from_row_col(*row, *col);
let doc = alloc.stack(vec![
alloc.reflow(r"I am partway through parsing a header, but got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("I am expecting a platform name next, like "),
alloc.parser_suggestion("roc/core"),
alloc.reflow("."),
]),
]);
Report {
filename,
doc,
title: "WEIRD MODULE NAME".to_string(),
}
}
EHeader::Space(error, row, col) => to_space_report(alloc, filename, &error, *row, *col),
}
}
fn to_provides_report<'a>(
alloc: &'a RocDocAllocator<'a>,
filename: PathBuf,
parse_problem: &roc_parse::parser::EProvides,
start_row: Row,
start_col: Col,
) -> Report<'a> {
use roc_parse::parser::EProvides;
match *parse_problem {
EProvides::Identifier(row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
let region = Region::from_row_col(row, col);
let doc = alloc.stack(vec![
alloc
.reflow(r"I am partway through parsing a provides list, but I got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![alloc.reflow(
"I was expecting a type name, value name or function name next, like ",
)]),
alloc
.parser_suggestion("provides [ Animal, default, tame ]")
.indent(4),
]);
Report {
filename,
doc,
title: "WEIRD PROVIDES".to_string(),
}
}
EProvides::Provides(row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
let region = Region::from_row_col(row, col);
let doc = alloc.stack(vec![
alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("I am expecting the "),
alloc.keyword("provides"),
alloc.reflow(" keyword next, like "),
]),
alloc
.parser_suggestion("provides [ Animal, default, tame ]")
.indent(4),
]);
Report {
filename,
doc,
title: "WEIRD PROVIDES".to_string(),
}
}
EProvides::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col),
_ => todo!("unhandled parse error {:?}", parse_problem),
}
}
fn to_exposes_report<'a>(
alloc: &'a RocDocAllocator<'a>,
filename: PathBuf,
parse_problem: &roc_parse::parser::EExposes,
start_row: Row,
start_col: Col,
) -> Report<'a> {
use roc_parse::parser::EExposes;
match *parse_problem {
EExposes::Identifier(row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
let region = Region::from_row_col(row, col);
let doc = alloc.stack(vec![
alloc.reflow(r"I am partway through parsing a exposes list, but I got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![alloc.reflow(
"I was expecting a type name, value name or function name next, like ",
)]),
alloc
.parser_suggestion("exposes [ Animal, default, tame ]")
.indent(4),
]);
Report {
filename,
doc,
title: "WEIRD EXPOSES".to_string(),
}
}
EExposes::Exposes(row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
let region = Region::from_row_col(row, col);
let doc = alloc.stack(vec![
alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("I am expecting the "),
alloc.keyword("exposes"),
alloc.reflow(" keyword next, like "),
]),
alloc
.parser_suggestion("exposes [ Animal, default, tame ]")
.indent(4),
]);
Report {
filename,
doc,
title: "WEIRD EXPOSES".to_string(),
}
}
EExposes::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col),
_ => todo!("unhandled parse error {:?}", parse_problem),
}
}
fn to_imports_report<'a>(
alloc: &'a RocDocAllocator<'a>,
filename: PathBuf,
parse_problem: &roc_parse::parser::EImports,
start_row: Row,
start_col: Col,
) -> Report<'a> {
use roc_parse::parser::EImports;
match *parse_problem {
EImports::Identifier(row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
let region = Region::from_row_col(row, col);
let doc = alloc.stack(vec![
alloc.reflow(r"I am partway through parsing a imports list, but I got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![alloc.reflow(
"I was expecting a type name, value name or function name next, like ",
)]),
alloc
.parser_suggestion("imports [ Animal, default, tame ]")
.indent(4),
]);
Report {
filename,
doc,
title: "WEIRD EXPOSES".to_string(),
}
}
EImports::Imports(row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
let region = Region::from_row_col(row, col);
let doc = alloc.stack(vec![
alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("I am expecting the "),
alloc.keyword("imports"),
alloc.reflow(" keyword next, like "),
]),
alloc
.parser_suggestion("imports [ Animal, default, tame ]")
.indent(4),
]);
Report {
filename,
doc,
title: "WEIRD IMPORTS".to_string(),
}
}
EImports::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col),
EImports::ModuleName(row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
let region = Region::from_row_col(row, col);
let doc = alloc.stack(vec![
alloc.reflow(r"I am partway through parsing a header, but got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("I am expecting a module name next, like "),
alloc.parser_suggestion("BigNum"),
alloc.reflow(" or "),
alloc.parser_suggestion("Main"),
alloc.reflow(". Module names must start with an uppercase letter."),
]),
]);
Report {
filename,
doc,
title: "WEIRD MODULE NAME".to_string(),
}
}
_ => todo!("unhandled parse error {:?}", parse_problem),
}
}
fn to_requires_report<'a>(
alloc: &'a RocDocAllocator<'a>,
filename: PathBuf,
parse_problem: &roc_parse::parser::ERequires<'a>,
start_row: Row,
start_col: Col,
) -> Report<'a> {
use roc_parse::parser::ERequires;
match *parse_problem {
ERequires::Requires(row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
let region = Region::from_row_col(row, col);
let doc = alloc.stack(vec![
alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("I am expecting the "),
alloc.keyword("requires"),
alloc.reflow(" keyword next, like "),
]),
alloc
.parser_suggestion("requires { main : Task I64 Str }")
.indent(4),
]);
Report {
filename,
doc,
title: "MISSING REQUIRES".to_string(),
}
}
ERequires::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col),
_ => todo!("unhandled parse error {:?}", parse_problem),
}
}
fn to_packages_report<'a>(
alloc: &'a RocDocAllocator<'a>,
filename: PathBuf,
parse_problem: &roc_parse::parser::EPackages,
start_row: Row,
start_col: Col,
) -> Report<'a> {
use roc_parse::parser::EPackages;
match *parse_problem {
EPackages::Packages(row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
let region = Region::from_row_col(row, col);
let doc = alloc.stack(vec![
alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("I am expecting the "),
alloc.keyword("packages"),
alloc.reflow(" keyword next, like "),
]),
alloc.parser_suggestion("packages {}").indent(4),
]);
Report {
filename,
doc,
title: "MISSING PACKAGES".to_string(),
}
}
EPackages::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col),
_ => todo!("unhandled parse error {:?}", parse_problem),
}
}
fn to_effects_report<'a>(
alloc: &'a RocDocAllocator<'a>,
filename: PathBuf,
parse_problem: &roc_parse::parser::EEffects,
start_row: Row,
start_col: Col,
) -> Report<'a> {
use roc_parse::parser::EEffects;
match *parse_problem {
EEffects::Effects(row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
let region = Region::from_row_col(row, col);
let doc = alloc.stack(vec![
alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("I am expecting the "),
alloc.keyword("effects"),
alloc.reflow(" keyword next, like "),
]),
alloc.parser_suggestion("effects {}").indent(4),
]);
Report {
filename,
doc,
title: "MISSING PACKAGES".to_string(),
}
}
EEffects::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col),
_ => todo!("unhandled parse error {:?}", parse_problem),
}
}
fn to_space_report<'a>(
alloc: &'a RocDocAllocator<'a>,
filename: PathBuf,

View File

@ -11,7 +11,7 @@ use roc_collections::all::{ImMap, MutMap, SendSet};
use roc_constrain::expr::constrain_expr;
use roc_constrain::module::{constrain_imported_values, Import};
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds};
use roc_parse::ast::{self, Attempting};
use roc_parse::ast;
use roc_parse::blankspace::space0_before;
use roc_parse::parser::{loc, Parser, State, SyntaxError};
use roc_problem::can::Problem;
@ -110,7 +110,7 @@ pub fn parse_loc_with<'a>(
arena: &'a Bump,
input: &'a str,
) -> Result<Located<ast::Expr<'a>>, SyntaxError<'a>> {
let state = State::new_in(arena, input.trim().as_bytes(), Attempting::Module);
let state = State::new_in(arena, input.trim().as_bytes());
let parser = space0_before(loc(roc_parse::expr::expr(0)), 0);
let answer = parser.parse(&arena, state);

View File

@ -169,6 +169,36 @@ mod test_reporting {
}
}
fn list_header_reports<F>(arena: &Bump, src: &str, buf: &mut String, callback: F)
where
F: FnOnce(RocDocBuilder<'_>, &mut String),
{
use ven_pretty::DocAllocator;
use roc_parse::parser::State;
let state = State::new_in(arena, src.as_bytes());
let filename = filename_from_string(r"\code\proj\Main.roc");
let src_lines: Vec<&str> = src.split('\n').collect();
use roc_parse::parser::Parser;
match roc_parse::module::header().parse(arena, state) {
Err((_, fail, _)) => {
let interns = Interns::default();
let home = crate::helpers::test_home();
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
let problem = fail.into_parse_problem(filename.clone(), src.as_bytes());
let doc = parse_problem(&alloc, filename, 0, problem);
callback(doc.pretty(&alloc).append(alloc.line()), buf)
}
Ok(_) => todo!(),
}
}
fn report_problem_as(src: &str, expected_rendering: &str) {
let mut buf: String = String::new();
let arena = Bump::new();
@ -193,6 +223,30 @@ mod test_reporting {
assert_eq!(buf, expected_rendering);
}
fn report_header_problem_as(src: &str, expected_rendering: &str) {
let mut buf: String = String::new();
let arena = Bump::new();
let callback = |doc: RocDocBuilder<'_>, buf: &mut String| {
doc.1
.render_raw(70, &mut roc_reporting::report::CiWrite::new(buf))
.expect("list_reports")
};
list_header_reports(&arena, src, &mut buf, callback);
// convenient to copy-paste the generated message
if true {
if buf != expected_rendering {
for line in buf.split("\n") {
println!(" {}", line);
}
}
}
assert_eq!(buf, expected_rendering);
}
fn color_report_problem_as(src: &str, expected_rendering: &str) {
let mut buf: String = String::new();
let arena = Bump::new();
@ -5702,4 +5756,113 @@ mod test_reporting {
),
)
}
#[test]
fn provides_to_identifier() {
report_header_problem_as(
indoc!(
r#"
app "test-base64"
packages { base: "platform" }
imports [base.Task, Base64 ]
provides [ main, @Foo ] to base
"#
),
indoc!(
r#"
WEIRD PROVIDES
I am in the middle of parsing a provides list, but I got stuck here:
3 imports [base.Task, Base64 ]
4 provides [ main, @Foo ] to base
^
I was expecting a type name, value name or function name next, like
provides [ Animal, default, tame ]
"#
),
)
}
#[test]
fn exposes_identifier() {
report_header_problem_as(
indoc!(
r#"
interface Foobar
exposes [ main, @Foo ]
imports [base.Task, Base64 ]
"#
),
indoc!(
r#"
WEIRD EXPOSES
I am in the middle of parsing a exposes list, but I got stuck here:
1 interface Foobar
2 exposes [ main, @Foo ]
^
I was expecting a type name, value name or function name next, like
exposes [ Animal, default, tame ]
"#
),
)
}
#[test]
fn invalid_module_name() {
report_header_problem_as(
indoc!(
r#"
interface foobar
exposes [ main, @Foo ]
imports [base.Task, Base64 ]
"#
),
indoc!(
r#"
WEIRD MODULE NAME
I am partway through parsing a header, but got stuck here:
1 interface foobar
^
I am expecting a module name next, like BigNum or Main. Module names
must start with an uppercase letter.
"#
),
)
}
#[test]
fn invalid_app_name() {
report_header_problem_as(
indoc!(
r#"
app foobar
exposes [ main, @Foo ]
imports [base.Task, Base64 ]
"#
),
indoc!(
r#"
WEIRD APP NAME
I am partway through parsing a header, but got stuck here:
1 app foobar
^
I am expecting an application name next, like app "main" or
app "editor". App names are surrounded by quotation marks.
"#
),
)
}
}

View File

@ -16,8 +16,8 @@ use roc_module::ident::ModuleName;
use roc_module::low_level::LowLevel;
use roc_module::operator::CalledVia;
use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol};
use roc_parse::ast;
use roc_parse::ast::StrLiteral;
use roc_parse::ast::{self, Attempting};
use roc_parse::blankspace::space0_before;
use roc_parse::expr::expr;
use roc_parse::parser::{loc, Parser, State, SyntaxError};
@ -235,7 +235,7 @@ pub fn str_to_expr2<'a>(
scope: &mut Scope,
region: Region,
) -> Result<(Expr2, self::Output), SyntaxError<'a>> {
let state = State::new_in(arena, input.trim().as_bytes(), Attempting::Module);
let state = State::new_in(arena, input.trim().as_bytes());
let parser = space0_before(loc(expr(0)), 0);
let parse_res = parser.parse(&arena, state);

View File

@ -2,7 +2,7 @@ use bumpalo::collections::Vec;
use bumpalo::Bump;
use roc_fmt::def::fmt_def;
use roc_fmt::module::fmt_module;
use roc_parse::ast::{Attempting, Def, Module};
use roc_parse::ast::{Def, Module};
use roc_parse::module::module_defs;
use roc_parse::parser;
use roc_parse::parser::{Parser, SyntaxError};
@ -36,11 +36,11 @@ impl<'a> File<'a> {
let allocation = arena.alloc(bytes);
let module_parse_state = parser::State::new_in(arena, allocation, Attempting::Module);
let parsed_module = roc_parse::module::header().parse(&arena, module_parse_state);
let module_parse_state = parser::State::new_in(arena, allocation);
let parsed_module = roc_parse::module::parse_header(&arena, module_parse_state);
match parsed_module {
Ok((_, module, state)) => {
Ok((module, state)) => {
let parsed_defs = module_defs().parse(&arena, state);
match parsed_defs {
@ -52,7 +52,7 @@ impl<'a> File<'a> {
Err((_, error, _)) => Err(ReadError::ParseDefs(error)),
}
}
Err((_, error, _)) => Err(ReadError::ParseHeader(error)),
Err(error) => Err(ReadError::ParseHeader(SyntaxError::Header(error))),
}
}