mirror of
https://github.com/roc-lang/roc.git
synced 2024-11-13 09:49:11 +03:00
commit
dd4d447bf1
@ -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),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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) => {
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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))),
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user