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>> {
pub fn package_or_path<'a>() -> impl Parser<'a, PackageOrPath<'a>, EPackageOrPath<'a>> {
one_of![
map!(
either!(
specialize(
|e, r, c| SyntaxError::Expr(crate::parser::EExpr::Str(e, r, c)),
string_literal::parse()
specialize(EPackageOrPath::BadPath, string_literal::parse()),
PackageOrPath::Path
),
map!(
and!(
package_name(),
skip_first!(one_or_more!(ascii_char(b' ')), package_version())
)
specialize(EPackageOrPath::BadPackage, package_name()),
skip_first!(skip_spaces(), package_version())
),
|answer| {
match answer {
Either::First(str_literal) => PackageOrPath::Path(str_literal),
Either::Second((name, version)) => PackageOrPath::Package(name, 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)
}
}

View File

@ -1,62 +1,65 @@
use crate::ast::{Attempting, CommentOrNewline, Def, Module};
use crate::blankspace::{space0, space0_around, space0_before, space1};
use crate::ast::{CommentOrNewline, Def, Module};
use crate::blankspace::{space0_around, space0_before_e, space0_e};
use crate::expr::def;
use crate::header::{
package_entry, package_or_path, AppHeader, Effects, ExposesEntry, ImportsEntry,
InterfaceHeader, ModuleName, PackageEntry, PackageName, PackageOrPath, PlatformHeader, To,
TypedIdent,
package_entry, package_name, package_or_path, AppHeader, Effects, ExposesEntry, ImportsEntry,
InterfaceHeader, ModuleName, PackageEntry, PlatformHeader, To, TypedIdent,
};
use crate::ident::{lowercase_ident, unqualified_ident, uppercase_ident};
use crate::parser::Progress::{self, *};
use crate::parser::{
self, ascii_char, ascii_string, backtrackable, end_of_file, loc, optional, peek_utf8_char,
peek_utf8_char_at, unexpected, unexpected_eof, Either, ParseResult, Parser, State, SyntaxError,
backtrackable, end_of_file, specialize, word1, Col, EEffects, EExposes, EHeader, EImports,
EPackages, EProvides, ERequires, ETypedIdent, Parser, Row, State, SyntaxError,
};
use crate::string_literal;
use crate::type_annotation;
use bumpalo::collections::{String, Vec};
use bumpalo::Bump;
use bumpalo::collections::Vec;
use roc_region::all::Located;
pub fn header<'a>() -> impl Parser<'a, Module<'a>, SyntaxError<'a>> {
one_of!(interface_module(), app_module(), platform_module())
pub fn parse_header<'a>(
arena: &'a bumpalo::Bump,
state: State<'a>,
) -> Result<(Module<'a>, State<'a>), EHeader<'a>> {
match header().parse(arena, state) {
Ok((_, module, state)) => Ok((module, state)),
Err((_, fail, _)) => Err(fail),
}
}
#[inline(always)]
fn app_module<'a>() -> impl Parser<'a, Module<'a>, SyntaxError<'a>> {
map!(app_header(), |header| { Module::App { header } })
}
fn header<'a>() -> impl Parser<'a, Module<'a>, EHeader<'a>> {
use crate::parser::keyword_e;
#[inline(always)]
fn platform_module<'a>() -> impl Parser<'a, Module<'a>, SyntaxError<'a>> {
map!(platform_header(), |header| { Module::Platform { header } })
}
#[inline(always)]
fn interface_module<'a>() -> impl Parser<'a, Module<'a>, SyntaxError<'a>> {
map!(interface_header(), |header| {
Module::Interface { header }
})
}
#[inline(always)]
pub fn interface_header<'a>() -> impl Parser<'a, InterfaceHeader<'a>, SyntaxError<'a>> {
parser::map(
and!(
skip_first!(
ascii_string("interface"),
and!(space1(1), loc!(module_name()))
one_of![
map!(
skip_first!(keyword_e("app", EHeader::Start), app_header()),
|header| { Module::App { header } }
),
and!(exposes_values(), imports())
map!(
skip_first!(keyword_e("platform", EHeader::Start), platform_header()),
|header| { Module::Platform { header } }
),
|(
(after_interface_keyword, name),
(
((before_exposes, after_exposes), exposes),
((before_imports, after_imports), imports),
),
)| {
InterfaceHeader {
map!(
skip_first!(keyword_e("interface", EHeader::Start), interface_header()),
|header| { Module::Interface { header } }
)
]
}
#[inline(always)]
fn interface_header<'a>() -> impl Parser<'a, InterfaceHeader<'a>, EHeader<'a>> {
|arena, state| {
let min_indent = 1;
let (_, after_interface_keyword, state) =
space0_e(min_indent, EHeader::Space, EHeader::IndentStart).parse(arena, state)?;
let (_, name, state) = loc!(module_name_help(EHeader::ModuleName)).parse(arena, state)?;
let (_, ((before_exposes, after_exposes), exposes), state) =
specialize(EHeader::Exposes, exposes_values()).parse(arena, state)?;
let (_, ((before_imports, after_imports), imports), state) =
specialize(EHeader::Imports, imports()).parse(arena, state)?;
let header = InterfaceHeader {
name,
exposes,
imports,
@ -65,144 +68,92 @@ pub fn interface_header<'a>() -> impl Parser<'a, InterfaceHeader<'a>, SyntaxErro
after_exposes,
before_imports,
after_imports,
}
},
)
}
#[inline(always)]
pub fn package_name<'a>() -> impl Parser<'a, PackageName<'a>, SyntaxError<'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!(
parse_package_part,
skip_first!(ascii_char(b'/'), parse_package_part)
),
|(account, pkg)| { PackageName { account, pkg } }
)
}
pub fn parse_package_part<'a>(
arena: &'a Bump,
mut state: State<'a>,
) -> ParseResult<'a, &'a str, SyntaxError<'a>> {
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 {
let progress = Progress::progress_when(!part_buf.is_empty());
return Ok((progress, part_buf.into_bump_str(), state));
}
}
Err(reason) => {
let progress = Progress::progress_when(!part_buf.is_empty());
return state.fail(arena, progress, reason);
}
}
}
Err(unexpected_eof(arena, state, 0))
}
#[inline(always)]
pub fn module_name<'a>() -> impl Parser<'a, ModuleName<'a>, SyntaxError<'a>> {
move |arena, mut state: State<'a>| {
match peek_utf8_char(&state) {
Ok((first_letter, bytes_parsed)) => {
if !first_letter.is_uppercase() {
return Err(unexpected(0, Attempting::Module, state));
};
let mut buf = String::with_capacity_in(4, arena);
Ok((MadeProgress, header, state))
}
}
buf.push(first_letter);
fn chomp_module_name(buffer: &[u8]) -> Result<&str, Progress> {
use encode_unicode::CharExt;
state = state.advance_without_indenting(bytes_parsed)?;
let mut chomped = 0;
while !state.bytes.is_empty() {
match peek_utf8_char(&state) {
Ok((ch, bytes_parsed)) => {
if let Ok((first_letter, width)) = char::from_utf8_slice_start(&buffer[chomped..]) {
if first_letter.is_uppercase() {
chomped += width;
} else {
return Err(Progress::NoProgress);
}
}
while let Ok((ch, width)) = char::from_utf8_slice_start(&buffer[chomped..]) {
// After the first character, only these are allowed:
//
// * Unicode alphabetic chars - you might include `鹏` if that's clear to your readers
// * ASCII digits - e.g. `1` but not `¾`, both of which pass .is_numeric()
// * A '.' separating module parts
if ch.is_alphabetic() || ch.is_ascii_digit() {
state = state.advance_without_indenting(bytes_parsed)?;
buf.push(ch);
chomped += width;
} else if ch == '.' {
match peek_utf8_char_at(&state, 1) {
Ok((next, next_bytes_parsed)) => {
if next.is_uppercase() {
// If we hit another uppercase letter, keep going!
buf.push('.');
buf.push(next);
chomped += width;
state = state.advance_without_indenting(
bytes_parsed + next_bytes_parsed,
)?;
if let Ok((first_letter, width)) = char::from_utf8_slice_start(&buffer[chomped..]) {
if first_letter.is_uppercase() {
chomped += width;
} else if first_letter == '{' {
// the .{ starting a `Foo.{ bar, baz }` importing clauses
chomped -= width;
break;
} else {
// We have finished parsing the module name.
//
// There may be an identifier after this '.',
// e.g. "baz" in `Foo.Bar.baz`
return Ok((
MadeProgress,
ModuleName::new(buf.into_bump_str()),
state,
));
return Err(Progress::MadeProgress);
}
}
Err(reason) => return state.fail(arena, MadeProgress, reason),
}
} else {
// This is the end of the module name. We're done!
// we're done
break;
}
}
Err(reason) => return state.fail(arena, MadeProgress, reason),
}
}
Ok((MadeProgress, ModuleName::new(buf.into_bump_str()), state))
}
Err(reason) => state.fail(arena, MadeProgress, reason),
let name = unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) };
Ok(name)
}
#[inline(always)]
fn module_name<'a>() -> impl Parser<'a, ModuleName<'a>, ()> {
|_, mut state: State<'a>| match chomp_module_name(state.bytes) {
Ok(name) => {
let width = name.len();
state.column += width as u16;
state.bytes = &state.bytes[width..];
Ok((MadeProgress, ModuleName::new(name), state))
}
Err(progress) => Err((progress, (), state)),
}
}
#[inline(always)]
pub fn app_header<'a>() -> impl Parser<'a, AppHeader<'a>, SyntaxError<'a>> {
map_with_arena!(
and!(
skip_first!(
ascii_string("app"),
and!(
space1(1),
loc!(crate::parser::specialize(
|e, r, c| SyntaxError::Expr(crate::parser::EExpr::Str(e, r, c)),
fn app_header<'a>() -> impl Parser<'a, AppHeader<'a>, EHeader<'a>> {
|arena, state| {
let min_indent = 1;
let (_, after_app_keyword, state) =
space0_e(min_indent, EHeader::Space, EHeader::IndentStart).parse(arena, state)?;
let (_, name, state) = loc!(crate::parser::specialize(
EHeader::AppName,
string_literal::parse()
))
)
),
and!(
optional(packages()),
and!(optional(imports()), provides_to())
)
),
|arena, ((after_app_keyword, name), (opt_pkgs, (opt_imports, provides)))| {
.parse(arena, state)?;
let (_, opt_pkgs, state) =
maybe!(specialize(EHeader::Packages, packages())).parse(arena, state)?;
let (_, opt_imports, state) =
maybe!(specialize(EHeader::Imports, imports())).parse(arena, state)?;
let (_, provides, state) =
specialize(EHeader::Provides, provides_to()).parse(arena, state)?;
let (before_packages, after_packages, package_entries) = match opt_pkgs {
Some(pkgs) => {
let pkgs: Packages<'a> = pkgs; // rustc must be told the type here
@ -227,7 +178,7 @@ pub fn app_header<'a>() -> impl Parser<'a, AppHeader<'a>, SyntaxError<'a>> {
opt_imports.unwrap_or_else(|| ((&[] as _, &[] as _), Vec::new_in(arena)));
let provides: ProvidesTo<'a> = provides; // rustc must be told the type here
AppHeader {
let header = AppHeader {
name,
packages: package_entries,
imports,
@ -242,44 +193,39 @@ pub fn app_header<'a>() -> impl Parser<'a, AppHeader<'a>, SyntaxError<'a>> {
after_provides: provides.after_provides_keyword,
before_to: provides.before_to_keyword,
after_to: provides.after_to_keyword,
};
Ok((MadeProgress, header, state))
}
}
)
}
#[inline(always)]
pub fn platform_header<'a>() -> impl Parser<'a, PlatformHeader<'a>, SyntaxError<'a>> {
parser::map(
and!(
skip_first!(
ascii_string("platform"),
and!(space1(1), loc!(package_name()))
),
and!(
and!(
and!(requires(), and!(exposes_modules(), packages())),
and!(imports(), provides_without_to())
),
effects()
)
),
|(
(after_platform_keyword, name),
(
(
(
((before_requires, after_requires), requires),
(((before_exposes, after_exposes), exposes), packages),
),
(
((before_imports, after_imports), imports),
((before_provides, after_provides), provides),
),
),
effects,
),
)| {
PlatformHeader {
fn platform_header<'a>() -> impl Parser<'a, PlatformHeader<'a>, EHeader<'a>> {
|arena, state| {
let min_indent = 1;
let (_, after_platform_keyword, state) =
space0_e(min_indent, EHeader::Space, EHeader::IndentStart).parse(arena, state)?;
let (_, name, state) =
loc!(specialize(EHeader::PlatformName, package_name())).parse(arena, state)?;
let (_, ((before_requires, after_requires), requires), state) =
specialize(EHeader::Requires, requires()).parse(arena, state)?;
let (_, ((before_exposes, after_exposes), exposes), state) =
specialize(EHeader::Exposes, exposes_modules()).parse(arena, state)?;
let (_, packages, state) = specialize(EHeader::Packages, packages()).parse(arena, state)?;
let (_, ((before_imports, after_imports), imports), state) =
specialize(EHeader::Imports, imports()).parse(arena, state)?;
let (_, ((before_provides, after_provides), provides), state) =
specialize(EHeader::Provides, provides_without_to()).parse(arena, state)?;
let (_, effects, state) = specialize(EHeader::Effects, effects()).parse(arena, state)?;
let header = PlatformHeader {
name,
requires,
exposes,
@ -298,17 +244,18 @@ pub fn platform_header<'a>() -> impl Parser<'a, PlatformHeader<'a>, SyntaxError<
after_imports,
before_provides,
after_provides,
};
Ok((MadeProgress, header, state))
}
},
)
}
#[inline(always)]
pub fn module_defs<'a>() -> impl Parser<'a, Vec<'a, Located<Def<'a>>>, SyntaxError<'a>> {
// force that we pare until the end of the input
skip_second!(zero_or_more!(space0_around(loc(def(0)), 0)), end_of_file())
skip_second!(zero_or_more!(space0_around(loc!(def(0)), 0)), end_of_file())
}
#[derive(Debug)]
struct ProvidesTo<'a> {
entries: Vec<'a, Located<ExposesEntry<'a, &'a str>>>,
to: Located<To<'a>>,
@ -319,48 +266,39 @@ struct ProvidesTo<'a> {
after_to_keyword: &'a [CommentOrNewline<'a>],
}
fn provides_to_package<'a>() -> impl Parser<'a, To<'a>, EProvides<'a>> {
one_of![
specialize(
|_, r, c| EProvides::Identifier(r, c),
map!(lowercase_ident(), To::ExistingPackage)
),
specialize(EProvides::Package, map!(package_or_path(), To::NewPackage))
]
}
#[inline(always)]
fn provides_to<'a>() -> impl Parser<'a, ProvidesTo<'a>, SyntaxError<'a>> {
fn provides_to<'a>() -> impl Parser<'a, ProvidesTo<'a>, EProvides<'a>> {
let min_indent = 1;
map!(
and!(
provides_without_to(),
and!(
skip_second!(backtrackable(space1(1)), ascii_string("provides")),
space1(1)
spaces_around_keyword(
min_indent,
"to",
EProvides::To,
EProvides::Space,
EProvides::IndentTo,
EProvides::IndentListStart
),
and!(
collection!(
ascii_char(b'['),
loc!(map!(unqualified_ident(), ExposesEntry::Exposed)),
ascii_char(b','),
ascii_char(b']'),
1
),
and!(
space1(1),
skip_first!(
ascii_string("to"),
and!(
space1(1),
loc!(either!(lowercase_ident(), package_or_path()))
)
)
)
loc!(provides_to_package())
)
),
|(
(before_provides_keyword, after_provides_keyword),
(entries, (before_to_keyword, (after_to_keyword, loc_to))),
((before_provides_keyword, after_provides_keyword), entries),
((before_to_keyword, after_to_keyword), to),
)| {
let loc_to: Located<Either<&'a str, PackageOrPath<'a>>> = loc_to;
let to_val = match loc_to.value {
Either::First(pkg) => To::ExistingPackage(pkg),
Either::Second(pkg) => To::NewPackage(pkg),
};
let to = Located {
value: to_val,
region: loc_to.region,
};
ProvidesTo {
entries,
to,
@ -380,20 +318,44 @@ fn provides_without_to<'a>() -> impl Parser<
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
Vec<'a, Located<ExposesEntry<'a, &'a str>>>,
),
SyntaxError<'a>,
EProvides<'a>,
> {
let min_indent = 1;
and!(
and!(skip_second!(space1(1), ascii_string("provides")), space1(1)),
collection!(
ascii_char(b'['),
loc!(map!(unqualified_ident(), ExposesEntry::Exposed)),
ascii_char(b','),
ascii_char(b']'),
1
spaces_around_keyword(
min_indent,
"provides",
EProvides::Provides,
EProvides::Space,
EProvides::IndentProvides,
EProvides::IndentListStart
),
collection_e!(
word1(b'[', EProvides::ListStart),
exposes_entry(EProvides::Identifier),
word1(b',', EProvides::ListEnd),
word1(b']', EProvides::ListEnd),
min_indent,
EProvides::Space,
EProvides::IndentListEnd
)
)
}
fn exposes_entry<'a, F, E>(
to_expectation: F,
) -> impl Parser<'a, Located<ExposesEntry<'a, &'a str>>, E>
where
F: Fn(crate::parser::Row, crate::parser::Col) -> E,
F: Copy,
E: 'a,
{
loc!(map!(
specialize(|_, r, c| to_expectation(r, c), unqualified_ident()),
ExposesEntry::Exposed
))
}
#[inline(always)]
fn requires<'a>() -> impl Parser<
'a,
@ -401,16 +363,26 @@ fn requires<'a>() -> impl Parser<
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
Vec<'a, Located<TypedIdent<'a>>>,
),
SyntaxError<'a>,
ERequires<'a>,
> {
let min_indent = 1;
and!(
and!(skip_second!(space1(1), ascii_string("requires")), space1(1)),
collection!(
ascii_char(b'{'),
loc!(typed_ident()),
ascii_char(b','),
ascii_char(b'}'),
1
spaces_around_keyword(
min_indent,
"requires",
ERequires::Requires,
ERequires::Space,
ERequires::IndentRequires,
ERequires::IndentListStart
),
collection_e!(
word1(b'{', ERequires::ListStart),
specialize(ERequires::TypedIdent, loc!(typed_ident())),
word1(b',', ERequires::ListEnd),
word1(b'}', ERequires::ListEnd),
min_indent,
ERequires::Space,
ERequires::IndentListEnd
)
)
}
@ -422,20 +394,51 @@ fn exposes_values<'a>() -> impl Parser<
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
Vec<'a, Located<ExposesEntry<'a, &'a str>>>,
),
SyntaxError<'a>,
EExposes,
> {
let min_indent = 1;
and!(
and!(skip_second!(space1(1), ascii_string("exposes")), space1(1)),
collection!(
ascii_char(b'['),
loc!(map!(unqualified_ident(), ExposesEntry::Exposed)),
ascii_char(b','),
ascii_char(b']'),
1
spaces_around_keyword(
min_indent,
"exposes",
EExposes::Exposes,
EExposes::Space,
EExposes::IndentExposes,
EExposes::IndentListStart
),
collection_e!(
word1(b'[', EExposes::ListStart),
exposes_entry(EExposes::Identifier),
word1(b',', EExposes::ListEnd),
word1(b']', EExposes::ListEnd),
min_indent,
EExposes::Space,
EExposes::IndentListEnd
)
)
}
fn spaces_around_keyword<'a, E>(
min_indent: u16,
keyword: &'static str,
expectation: fn(Row, Col) -> E,
space_problem: fn(crate::parser::BadInputError, Row, Col) -> E,
indent_problem1: fn(Row, Col) -> E,
indent_problem2: fn(Row, Col) -> E,
) -> impl Parser<'a, (&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]), E>
where
E: 'a,
{
and!(
skip_second!(
backtrackable(space0_e(min_indent, space_problem, indent_problem1)),
crate::parser::keyword_e(keyword, expectation)
),
space0_e(min_indent, space_problem, indent_problem2)
)
}
#[inline(always)]
fn exposes_modules<'a>() -> impl Parser<
'a,
@ -443,20 +446,45 @@ fn exposes_modules<'a>() -> impl Parser<
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
Vec<'a, Located<ExposesEntry<'a, ModuleName<'a>>>>,
),
SyntaxError<'a>,
EExposes,
> {
let min_indent = 1;
and!(
and!(skip_second!(space1(1), ascii_string("exposes")), space1(1)),
collection!(
ascii_char(b'['),
loc!(map!(module_name(), ExposesEntry::Exposed)),
ascii_char(b','),
ascii_char(b']'),
1
spaces_around_keyword(
min_indent,
"exposes",
EExposes::Exposes,
EExposes::Space,
EExposes::IndentExposes,
EExposes::IndentListStart
),
collection_e!(
word1(b'[', EExposes::ListStart),
exposes_module(EExposes::Identifier),
word1(b',', EExposes::ListEnd),
word1(b']', EExposes::ListEnd),
min_indent,
EExposes::Space,
EExposes::IndentListEnd
)
)
}
fn exposes_module<'a, F, E>(
to_expectation: F,
) -> impl Parser<'a, Located<ExposesEntry<'a, ModuleName<'a>>>, E>
where
F: Fn(crate::parser::Row, crate::parser::Col) -> E,
F: Copy,
E: 'a,
{
loc!(map!(
specialize(|_, r, c| to_expectation(r, c), module_name()),
ExposesEntry::Exposed
))
}
#[derive(Debug)]
struct Packages<'a> {
entries: Vec<'a, Located<PackageEntry<'a>>>,
@ -466,19 +494,27 @@ struct Packages<'a> {
}
#[inline(always)]
fn packages<'a>() -> impl Parser<'a, Packages<'a>, SyntaxError<'a>> {
fn packages<'a>() -> impl Parser<'a, Packages<'a>, EPackages<'a>> {
let min_indent = 1;
map!(
and!(
and!(
skip_second!(backtrackable(space1(1)), ascii_string("packages")),
space1(1)
spaces_around_keyword(
min_indent,
"packages",
EPackages::Packages,
EPackages::Space,
EPackages::IndentPackages,
EPackages::IndentListStart
),
collection!(
ascii_char(b'{'),
loc!(package_entry()),
ascii_char(b','),
ascii_char(b'}'),
1
collection_e!(
word1(b'{', EPackages::ListStart),
specialize(EPackages::PackageEntry, loc!(package_entry())),
word1(b',', EPackages::ListEnd),
word1(b'}', EPackages::ListEnd),
min_indent,
EPackages::Space,
EPackages::IndentListEnd
)
),
|((before_packages_keyword, after_packages_keyword), entries)| {
@ -498,42 +534,68 @@ fn imports<'a>() -> impl Parser<
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
Vec<'a, Located<ImportsEntry<'a>>>,
),
SyntaxError<'a>,
EImports,
> {
let min_indent = 1;
and!(
and!(
skip_second!(backtrackable(space1(1)), ascii_string("imports")),
space1(1)
spaces_around_keyword(
min_indent,
"imports",
EImports::Imports,
EImports::Space,
EImports::IndentImports,
EImports::IndentListStart
),
collection!(
ascii_char(b'['),
collection_e!(
word1(b'[', EImports::ListStart),
loc!(imports_entry()),
ascii_char(b','),
ascii_char(b']'),
1
word1(b',', EImports::ListEnd),
word1(b']', EImports::ListEnd),
min_indent,
EImports::Space,
EImports::IndentListEnd
)
)
}
#[inline(always)]
fn effects<'a>() -> impl Parser<'a, Effects<'a>, SyntaxError<'a>> {
fn effects<'a>() -> impl Parser<'a, Effects<'a>, EEffects<'a>> {
move |arena, state| {
let (_, spaces_before_effects_keyword, state) =
skip_second!(space1(0), ascii_string("effects")).parse(arena, state)?;
let (_, spaces_after_effects_keyword, state) = space1(0).parse(arena, state)?;
let min_indent = 1;
let (_, (spaces_before_effects_keyword, spaces_after_effects_keyword), state) =
spaces_around_keyword(
min_indent,
"effects",
EEffects::Effects,
EEffects::Space,
EEffects::IndentEffects,
EEffects::IndentListStart,
)
.parse(arena, state)?;
// e.g. `fx.`
let (_, type_shortname, state) =
skip_second!(lowercase_ident(), ascii_char(b'.')).parse(arena, state)?;
let (_, type_shortname, state) = skip_second!(
specialize(|_, r, c| EEffects::Shorthand(r, c), lowercase_ident()),
word1(b'.', EEffects::ShorthandDot)
)
.parse(arena, state)?;
let (_, (type_name, spaces_after_type_name), state) =
and!(uppercase_ident(), space1(0)).parse(arena, state)?;
let (_, entries, state) = collection!(
ascii_char(b'{'),
loc!(typed_ident()),
ascii_char(b','),
ascii_char(b'}'),
1
// the type name, e.g. Effects
let (_, (type_name, spaces_after_type_name), state) = and!(
specialize(|_, r, c| EEffects::TypeName(r, c), uppercase_ident()),
space0_e(min_indent, EEffects::Space, EEffects::IndentListStart)
)
.parse(arena, state)?;
let (_, entries, state) = collection_e!(
word1(b'{', EEffects::ListStart),
specialize(EEffects::TypedIdent, loc!(typed_ident())),
word1(b',', EEffects::ListEnd),
word1(b'}', EEffects::ListEnd),
min_indent,
EEffects::Space,
EEffects::IndentListEnd
)
.parse(arena, state)?;
@ -553,63 +615,89 @@ fn effects<'a>() -> impl Parser<'a, Effects<'a>, SyntaxError<'a>> {
}
#[inline(always)]
fn typed_ident<'a>() -> impl Parser<'a, TypedIdent<'a>, SyntaxError<'a>> {
move |arena, state| {
// You must have a field name, e.g. "email"
let (_, ident, state) = loc!(lowercase_ident()).parse(arena, state)?;
let (_, spaces_before_colon, state) = space0(0).parse(arena, state)?;
let (_, ann, state) = skip_first!(
ascii_char(b':'),
space0_before(type_annotation::located(0), 0)
)
.parse(arena, state)?;
fn typed_ident<'a>() -> impl Parser<'a, TypedIdent<'a>, ETypedIdent<'a>> {
// e.g.
//
// printLine : Str -> Effect {}
let min_indent = 0;
Ok((
MadeProgress,
map!(
and!(
and!(
loc!(specialize(
|_, r, c| ETypedIdent::Identifier(r, c),
lowercase_ident()
)),
space0_e(min_indent, ETypedIdent::Space, ETypedIdent::IndentHasType)
),
skip_first!(
word1(b':', ETypedIdent::HasType),
space0_before_e(
specialize(ETypedIdent::Type, type_annotation::located_help(min_indent)),
min_indent,
ETypedIdent::Space,
ETypedIdent::IndentType,
)
)
),
|((ident, spaces_before_colon), ann)| {
TypedIdent::Entry {
ident,
spaces_before_colon,
ann,
},
state,
))
}
}
)
}
fn shortname<'a>() -> impl Parser<'a, &'a str, EImports> {
specialize(|_, r, c| EImports::Shorthand(r, c), lowercase_ident())
}
fn module_name_help<'a, F, E>(to_expectation: F) -> impl Parser<'a, ModuleName<'a>, E>
where
F: Fn(crate::parser::Row, crate::parser::Col) -> E,
E: 'a,
F: 'a,
{
specialize(move |_, r, c| to_expectation(r, c), module_name())
}
#[inline(always)]
#[allow(clippy::type_complexity)]
fn imports_entry<'a>() -> impl Parser<'a, ImportsEntry<'a>, SyntaxError<'a>> {
fn imports_entry<'a>() -> impl Parser<'a, ImportsEntry<'a>, EImports> {
let min_indent = 1;
type Temp<'a> = (
(Option<&'a str>, ModuleName<'a>),
Option<Vec<'a, Located<ExposesEntry<'a, &'a str>>>>,
);
map_with_arena!(
and!(
and!(
// e.g. `base.`
optional(skip_second!(lowercase_ident(), ascii_char(b'.'))),
maybe!(skip_second!(
shortname(),
word1(b'.', EImports::ShorthandDot)
)),
// e.g. `Task`
module_name()
module_name_help(EImports::ModuleName)
),
// e.g. `.{ Task, after}`
optional(skip_first!(
ascii_char(b'.'),
collection!(
ascii_char(b'{'),
loc!(map!(unqualified_ident(), ExposesEntry::Exposed)),
ascii_char(b','),
ascii_char(b'}'),
1
maybe!(skip_first!(
word1(b'.', EImports::ExposingDot),
collection_e!(
word1(b'{', EImports::SetStart),
exposes_entry(EImports::Identifier),
word1(b',', EImports::SetEnd),
word1(b'}', EImports::SetEnd),
min_indent,
EImports::Space,
EImports::IndentSetEnd
)
))
),
|arena,
((opt_shortname, module_name), opt_values): (
(Option<&'a str>, ModuleName<'a>),
Option<Vec<'a, Located<ExposesEntry<'a, &'a str>>>>
)| {
|arena, ((opt_shortname, module_name), opt_values): Temp<'a>| {
let exposed_values = opt_values.unwrap_or_else(|| Vec::new_in(arena));
match opt_shortname {

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));
}
_ => {
// 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,
))
// 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))
}
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))),
}
}