mirror of
https://github.com/roc-lang/roc.git
synced 2024-11-11 05:34: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>> {
|
||||
map!(
|
||||
either!(
|
||||
specialize(
|
||||
|e, r, c| SyntaxError::Expr(crate::parser::EExpr::Str(e, r, c)),
|
||||
string_literal::parse()
|
||||
),
|
||||
and!(
|
||||
package_name(),
|
||||
skip_first!(one_or_more!(ascii_char(b' ')), package_version())
|
||||
)
|
||||
pub fn package_or_path<'a>() -> impl Parser<'a, PackageOrPath<'a>, EPackageOrPath<'a>> {
|
||||
one_of![
|
||||
map!(
|
||||
specialize(EPackageOrPath::BadPath, string_literal::parse()),
|
||||
PackageOrPath::Path
|
||||
),
|
||||
|answer| {
|
||||
match answer {
|
||||
Either::First(str_literal) => PackageOrPath::Path(str_literal),
|
||||
Either::Second((name, version)) => PackageOrPath::Package(name, version),
|
||||
}
|
||||
}
|
||||
)
|
||||
map!(
|
||||
and!(
|
||||
specialize(EPackageOrPath::BadPackage, package_name()),
|
||||
skip_first!(skip_spaces(), package_version())
|
||||
),
|
||||
|(name, version)| { PackageOrPath::Package(name, version) }
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
fn package_version<'a>() -> impl Parser<'a, Version<'a>, SyntaxError<'a>> {
|
||||
fn skip_spaces<'a, T>() -> impl Parser<'a, (), T>
|
||||
where
|
||||
T: 'a,
|
||||
{
|
||||
|_, mut state: State<'a>| {
|
||||
let mut chomped = 0;
|
||||
let mut it = state.bytes.iter();
|
||||
|
||||
while let Some(b' ') = it.next() {
|
||||
chomped += 1;
|
||||
}
|
||||
|
||||
if chomped == 0 {
|
||||
Ok((NoProgress, (), state))
|
||||
} else {
|
||||
state.column += chomped;
|
||||
state.bytes = it.as_slice();
|
||||
|
||||
Ok((MadeProgress, (), state))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn package_version<'a, T>() -> impl Parser<'a, Version<'a>, T>
|
||||
where
|
||||
T: 'a,
|
||||
{
|
||||
move |_, _| todo!("TODO parse package version")
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn package_name<'a>() -> impl Parser<'a, PackageName<'a>, EPackageName> {
|
||||
use encode_unicode::CharExt;
|
||||
// e.g. rtfeldman/blah
|
||||
//
|
||||
// Package names and accounts can be capitalized and can contain dashes.
|
||||
// They cannot contain underscores or other special characters.
|
||||
// They must be ASCII.
|
||||
|
||||
|_, mut state: State<'a>| match chomp_package_part(state.bytes) {
|
||||
Err(progress) => Err((
|
||||
progress,
|
||||
EPackageName::Account(state.line, state.column),
|
||||
state,
|
||||
)),
|
||||
Ok(account) => {
|
||||
let mut chomped = account.len();
|
||||
if let Ok(('/', width)) = char::from_utf8_slice_start(&state.bytes[chomped..]) {
|
||||
chomped += width;
|
||||
match chomp_package_part(&state.bytes[chomped..]) {
|
||||
Err(progress) => Err((
|
||||
progress,
|
||||
EPackageName::Pkg(state.line, state.column + chomped as u16),
|
||||
state,
|
||||
)),
|
||||
Ok(pkg) => {
|
||||
chomped += pkg.len();
|
||||
|
||||
state.column += chomped as u16;
|
||||
state.bytes = &state.bytes[chomped..];
|
||||
|
||||
let value = PackageName { account, pkg };
|
||||
Ok((MadeProgress, value, state))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err((
|
||||
MadeProgress,
|
||||
EPackageName::MissingSlash(state.line, state.column + chomped as u16),
|
||||
state,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn chomp_package_part(buffer: &[u8]) -> Result<&str, Progress> {
|
||||
use encode_unicode::CharExt;
|
||||
|
||||
let mut chomped = 0;
|
||||
|
||||
while let Ok((ch, width)) = char::from_utf8_slice_start(&buffer[chomped..]) {
|
||||
if ch == '-' || ch.is_ascii_alphanumeric() {
|
||||
chomped += width;
|
||||
} else {
|
||||
// we're done
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if chomped == 0 {
|
||||
Err(Progress::NoProgress)
|
||||
} else {
|
||||
let name = unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) };
|
||||
|
||||
Ok(name)
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -43,7 +43,7 @@ pub enum Either<First, Second> {
|
||||
}
|
||||
|
||||
impl<'a> State<'a> {
|
||||
pub fn new_in(arena: &'a Bump, bytes: &'a [u8], _attempting: Attempting) -> State<'a> {
|
||||
pub fn new_in(arena: &'a Bump, bytes: &'a [u8]) -> State<'a> {
|
||||
State {
|
||||
bytes,
|
||||
line: 0,
|
||||
@ -334,9 +334,147 @@ pub enum SyntaxError<'a> {
|
||||
Type(Type<'a>),
|
||||
Pattern(EPattern<'a>),
|
||||
Expr(EExpr<'a>),
|
||||
Header(EHeader<'a>),
|
||||
Space(BadInputError),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum EHeader<'a> {
|
||||
Provides(EProvides<'a>, Row, Col),
|
||||
Exposes(EExposes, Row, Col),
|
||||
Imports(EImports, Row, Col),
|
||||
Requires(ERequires<'a>, Row, Col),
|
||||
Packages(EPackages<'a>, Row, Col),
|
||||
Effects(EEffects<'a>, Row, Col),
|
||||
|
||||
Space(BadInputError, Row, Col),
|
||||
Start(Row, Col),
|
||||
ModuleName(Row, Col),
|
||||
AppName(EString<'a>, Row, Col),
|
||||
PlatformName(EPackageName, Row, Col),
|
||||
IndentStart(Row, Col),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum EProvides<'a> {
|
||||
Provides(Row, Col),
|
||||
To(Row, Col),
|
||||
IndentProvides(Row, Col),
|
||||
IndentTo(Row, Col),
|
||||
IndentListStart(Row, Col),
|
||||
IndentListEnd(Row, Col),
|
||||
IndentPackage(Row, Col),
|
||||
ListStart(Row, Col),
|
||||
ListEnd(Row, Col),
|
||||
Identifier(Row, Col),
|
||||
Package(EPackageOrPath<'a>, Row, Col),
|
||||
Space(BadInputError, Row, Col),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum EExposes {
|
||||
Exposes(Row, Col),
|
||||
IndentExposes(Row, Col),
|
||||
IndentListStart(Row, Col),
|
||||
IndentListEnd(Row, Col),
|
||||
ListStart(Row, Col),
|
||||
ListEnd(Row, Col),
|
||||
Identifier(Row, Col),
|
||||
Space(BadInputError, Row, Col),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ERequires<'a> {
|
||||
Requires(Row, Col),
|
||||
IndentRequires(Row, Col),
|
||||
IndentListStart(Row, Col),
|
||||
IndentListEnd(Row, Col),
|
||||
ListStart(Row, Col),
|
||||
ListEnd(Row, Col),
|
||||
TypedIdent(ETypedIdent<'a>, Row, Col),
|
||||
Space(BadInputError, Row, Col),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ETypedIdent<'a> {
|
||||
Space(BadInputError, Row, Col),
|
||||
HasType(Row, Col),
|
||||
IndentHasType(Row, Col),
|
||||
Name(Row, Col),
|
||||
Type(Type<'a>, Row, Col),
|
||||
IndentType(Row, Col),
|
||||
Identifier(Row, Col),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum EPackages<'a> {
|
||||
Space(BadInputError, Row, Col),
|
||||
Packages(Row, Col),
|
||||
IndentPackages(Row, Col),
|
||||
ListStart(Row, Col),
|
||||
ListEnd(Row, Col),
|
||||
IndentListStart(Row, Col),
|
||||
IndentListEnd(Row, Col),
|
||||
PackageEntry(EPackageEntry<'a>, Row, Col),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum EPackageName {
|
||||
MissingSlash(Row, Col),
|
||||
Account(Row, Col),
|
||||
Pkg(Row, Col),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum EPackageOrPath<'a> {
|
||||
BadPath(EString<'a>, Row, Col),
|
||||
BadPackage(EPackageName, Row, Col),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum EPackageEntry<'a> {
|
||||
BadPackageOrPath(EPackageOrPath<'a>, Row, Col),
|
||||
Shorthand(Row, Col),
|
||||
Colon(Row, Col),
|
||||
IndentPackageOrPath(Row, Col),
|
||||
Space(BadInputError, Row, Col),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum EEffects<'a> {
|
||||
Space(BadInputError, Row, Col),
|
||||
Effects(Row, Col),
|
||||
IndentEffects(Row, Col),
|
||||
ListStart(Row, Col),
|
||||
ListEnd(Row, Col),
|
||||
IndentListStart(Row, Col),
|
||||
IndentListEnd(Row, Col),
|
||||
TypedIdent(ETypedIdent<'a>, Row, Col),
|
||||
ShorthandDot(Row, Col),
|
||||
Shorthand(Row, Col),
|
||||
TypeName(Row, Col),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum EImports {
|
||||
Imports(Row, Col),
|
||||
IndentImports(Row, Col),
|
||||
IndentListStart(Row, Col),
|
||||
IndentListEnd(Row, Col),
|
||||
ListStart(Row, Col),
|
||||
ListEnd(Row, Col),
|
||||
Identifier(Row, Col),
|
||||
ExposingDot(Row, Col),
|
||||
ShorthandDot(Row, Col),
|
||||
Shorthand(Row, Col),
|
||||
ModuleName(Row, Col),
|
||||
Space(BadInputError, Row, Col),
|
||||
IndentSetStart(Row, Col),
|
||||
IndentSetEnd(Row, Col),
|
||||
SetStart(Row, Col),
|
||||
SetEnd(Row, Col),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum BadInputError {
|
||||
HasTab,
|
||||
@ -446,14 +584,6 @@ pub enum EString<'a> {
|
||||
Format(&'a SyntaxError<'a>, Row, Col),
|
||||
}
|
||||
|
||||
// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
// pub enum Escape {
|
||||
// EscapeUnknown,
|
||||
// BadUnicodeFormat(u16),
|
||||
// BadUnicodeCode(u16),
|
||||
// BadUnicodeLength(u16, i32, i32),
|
||||
// }
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ERecord<'a> {
|
||||
End(Row, Col),
|
||||
@ -1128,31 +1258,27 @@ where
|
||||
ToError: Fn(Row, Col) -> E,
|
||||
E: 'a,
|
||||
{
|
||||
move |arena, state: State<'a>| {
|
||||
let initial_state = state.clone();
|
||||
// first parse the keyword characters
|
||||
let (_, _, after_keyword_state) = ascii_string(keyword)
|
||||
.parse(arena, state)
|
||||
.map_err(|(_, _, state)| (NoProgress, if_error(state.line, state.column), state))?;
|
||||
move |_, mut state: State<'a>| {
|
||||
let width = keyword.len();
|
||||
|
||||
// then we must have at least one space character
|
||||
// TODO this is potentially wasteful if there are a lot of spaces
|
||||
match peek_utf8_char(&after_keyword_state) {
|
||||
Ok((next, _width)) if next == ' ' || next == '#' || next == '\n' => {
|
||||
// give back the state after parsing the keyword, but before the whitespace
|
||||
// that way we can attach the whitespace to whatever follows
|
||||
Ok((MadeProgress, (), after_keyword_state))
|
||||
if !state.bytes.starts_with(keyword.as_bytes()) {
|
||||
return Err((NoProgress, if_error(state.line, state.column), state));
|
||||
}
|
||||
|
||||
// the next character should not be an identifier character
|
||||
// to prevent treating `whence` or `iffy` as keywords
|
||||
match state.bytes.get(width) {
|
||||
Some(next) if *next == b' ' || *next == b'#' || *next == b'\n' => {
|
||||
state.column += width as u16;
|
||||
state.bytes = &state.bytes[width..];
|
||||
Ok((MadeProgress, (), state))
|
||||
}
|
||||
_ => {
|
||||
// this is not a keyword, maybe it's `whence` or `iffy`
|
||||
// anyway, make no progress and return the initial state
|
||||
// so we can try something else
|
||||
Err((
|
||||
NoProgress,
|
||||
if_error(initial_state.line, initial_state.column),
|
||||
initial_state,
|
||||
))
|
||||
None => {
|
||||
state.column += width as u16;
|
||||
state.bytes = &state.bytes[width..];
|
||||
Ok((MadeProgress, (), state))
|
||||
}
|
||||
Some(_) => Err((NoProgress, if_error(state.line, state.column), state)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1589,6 +1715,46 @@ macro_rules! collection {
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! collection_e {
|
||||
($opening_brace:expr, $elem:expr, $delimiter:expr, $closing_brace:expr, $min_indent:expr, $space_problem:expr, $indent_problem:expr) => {
|
||||
skip_first!(
|
||||
$opening_brace,
|
||||
skip_first!(
|
||||
// We specifically allow space characters inside here, so that
|
||||
// `[ ]` can be successfully parsed as an empty list, and then
|
||||
// changed by the formatter back into `[]`.
|
||||
//
|
||||
// We don't allow newlines or comments in the middle of empty
|
||||
// roc_collections because those are normally stored in an Expr,
|
||||
// and there's no Expr in which to store them in an empty collection!
|
||||
//
|
||||
// We could change the AST to add extra storage specifically to
|
||||
// support empty literals containing newlines or comments, but this
|
||||
// does not seem worth even the tiniest regression in compiler performance.
|
||||
zero_or_more!($crate::parser::word1(b' ', |row, col| $space_problem(
|
||||
crate::parser::BadInputError::LineTooLong,
|
||||
row,
|
||||
col
|
||||
))),
|
||||
skip_second!(
|
||||
$crate::parser::sep_by0(
|
||||
$delimiter,
|
||||
$crate::blankspace::space0_around_ee(
|
||||
$elem,
|
||||
$min_indent,
|
||||
$space_problem,
|
||||
$indent_problem,
|
||||
$indent_problem
|
||||
)
|
||||
),
|
||||
$closing_brace
|
||||
)
|
||||
)
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
/// Parse zero or more elements between two braces (e.g. square braces).
|
||||
/// Elements can be optionally surrounded by spaces, and are separated by a
|
||||
/// delimiter (e.g comma-separated) with optionally a trailing delimiter.
|
||||
@ -1719,6 +1885,19 @@ macro_rules! one_of {
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! maybe {
|
||||
($p1:expr) => {
|
||||
move |arena: &'a bumpalo::Bump, state: $crate::parser::State<'a>| match $p1
|
||||
.parse(arena, state)
|
||||
{
|
||||
Ok((progress, value, state)) => Ok((progress, Some(value), state)),
|
||||
Err((MadeProgress, fail, state)) => Err((MadeProgress, fail, state)),
|
||||
Err((NoProgress, _, state)) => Ok((NoProgress, None, state)),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! one_of_with_error {
|
||||
($toerror:expr; $p1:expr) => {
|
||||
|
@ -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