Merge branch 'trunk' into editor_ideas

This commit is contained in:
Anton-4 2021-02-09 16:16:44 +01:00 committed by GitHub
commit b78a7a94b2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 1762 additions and 989 deletions

2
Cargo.lock generated
View file

@ -2960,6 +2960,7 @@ dependencies = [
"roc_parse", "roc_parse",
"roc_problem", "roc_problem",
"roc_region", "roc_region",
"roc_reporting",
"roc_solve", "roc_solve",
"roc_types", "roc_types",
"roc_unify", "roc_unify",
@ -3061,7 +3062,6 @@ dependencies = [
"roc_can", "roc_can",
"roc_collections", "roc_collections",
"roc_constrain", "roc_constrain",
"roc_load",
"roc_module", "roc_module",
"roc_mono", "roc_mono",
"roc_parse", "roc_parse",

View file

@ -19,16 +19,16 @@ fn report_timing(buf: &mut String, label: &str, duration: Duration) {
)); ));
} }
pub fn build_file( pub fn build_file<'a>(
arena: &'a Bump,
target: &Triple, target: &Triple,
src_dir: PathBuf, src_dir: PathBuf,
roc_file_path: PathBuf, roc_file_path: PathBuf,
opt_level: OptLevel, opt_level: OptLevel,
emit_debug_info: bool, emit_debug_info: bool,
link_type: LinkType, link_type: LinkType,
) -> Result<PathBuf, LoadingProblem> { ) -> Result<PathBuf, LoadingProblem<'a>> {
let compilation_start = SystemTime::now(); let compilation_start = SystemTime::now();
let arena = Bump::new();
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
// Step 1: compile the app and generate the .o file // Step 1: compile the app and generate the .o file
@ -36,13 +36,14 @@ pub fn build_file(
// Release builds use uniqueness optimizations // Release builds use uniqueness optimizations
let stdlib = match opt_level { let stdlib = match opt_level {
OptLevel::Normal => roc_builtins::std::standard_stdlib(), OptLevel::Normal => arena.alloc(roc_builtins::std::standard_stdlib()),
OptLevel::Optimize => roc_builtins::std::standard_stdlib(), OptLevel::Optimize => arena.alloc(roc_builtins::std::standard_stdlib()),
}; };
let loaded = roc_load::file::load_and_monomorphize( let loaded = roc_load::file::load_and_monomorphize(
&arena, &arena,
roc_file_path.clone(), roc_file_path.clone(),
&stdlib, stdlib,
src_dir.as_path(), src_dir.as_path(),
subs_by_module, subs_by_module,
ptr_bytes, ptr_bytes,

View file

@ -1,10 +1,12 @@
#[macro_use] #[macro_use]
extern crate clap; extern crate clap;
use bumpalo::Bump;
use clap::ArgMatches; use clap::ArgMatches;
use clap::{App, Arg}; use clap::{App, Arg};
use roc_build::link::LinkType; use roc_build::link::LinkType;
use roc_gen::llvm::build::OptLevel; use roc_gen::llvm::build::OptLevel;
use roc_load::file::LoadingProblem;
use std::io; use std::io;
use std::path::Path; use std::path::Path;
use std::process; use std::process;
@ -77,7 +79,9 @@ pub fn build_app<'a>() -> App<'a> {
} }
pub fn build(target: &Triple, matches: &ArgMatches, run_after_build: bool) -> io::Result<()> { pub fn build(target: &Triple, matches: &ArgMatches, run_after_build: bool) -> io::Result<()> {
let arena = Bump::new();
let filename = matches.value_of(FLAG_ROC_FILE).unwrap(); let filename = matches.value_of(FLAG_ROC_FILE).unwrap();
let opt_level = if matches.is_present(FLAG_OPTIMIZE) { let opt_level = if matches.is_present(FLAG_OPTIMIZE) {
OptLevel::Optimize OptLevel::Optimize
} else { } else {
@ -107,16 +111,18 @@ pub fn build(target: &Triple, matches: &ArgMatches, run_after_build: bool) -> io
} }
}); });
let binary_path = build::build_file( let res_binary_path = build::build_file(
&arena,
target, target,
src_dir, src_dir,
path, path,
opt_level, opt_level,
emit_debug_info, emit_debug_info,
LinkType::Executable, LinkType::Executable,
) );
.expect("TODO gracefully handle build_file failing");
match res_binary_path {
Ok(binary_path) => {
if run_after_build { if run_after_build {
// Run the compiled app // Run the compiled app
Command::new(binary_path) Command::new(binary_path)
@ -125,6 +131,14 @@ pub fn build(target: &Triple, matches: &ArgMatches, run_after_build: bool) -> io
.wait() .wait()
.expect("TODO gracefully handle block_on failing"); .expect("TODO gracefully handle block_on failing");
} }
}
Err(LoadingProblem::ParsingFailedReport(report)) => {
print!("{}", report);
}
Err(other) => {
panic!("build_file failed with error:\n{:?}", other);
}
}
Ok(()) Ok(())
} }

View file

@ -1,12 +1,12 @@
use const_format::concatcp; use const_format::concatcp;
use gen::{gen_and_eval, ReplOutput}; use gen::{gen_and_eval, ReplOutput};
use roc_gen::llvm::build::OptLevel; use roc_gen::llvm::build::OptLevel;
use roc_parse::parser::{Fail, FailReason}; use roc_parse::parser::Bag;
use rustyline::error::ReadlineError; use rustyline::error::ReadlineError;
use rustyline::validate::{self, ValidationContext, ValidationResult, Validator}; use rustyline::validate::{self, ValidationContext, ValidationResult, Validator};
use rustyline::Editor; use rustyline::Editor;
use rustyline_derive::{Completer, Helper, Highlighter, Hinter}; use rustyline_derive::{Completer, Helper, Highlighter, Hinter};
use std::io::{self}; use std::io;
use target_lexicon::Triple; use target_lexicon::Triple;
const BLUE: &str = "\u{001b}[36m"; const BLUE: &str = "\u{001b}[36m";
@ -148,10 +148,10 @@ pub fn main() -> io::Result<()> {
println!("{}", output); println!("{}", output);
pending_src.clear(); pending_src.clear();
} }
Err(Fail { // Err(Fail {
reason: FailReason::Eof(_), // reason: FailReason::Eof(_),
.. // ..
}) => {} // }) => {}
Err(fail) => { Err(fail) => {
report_parse_error(fail); report_parse_error(fail);
pending_src.clear(); pending_src.clear();
@ -191,11 +191,11 @@ pub fn main() -> io::Result<()> {
Ok(()) Ok(())
} }
fn report_parse_error(fail: Fail) { fn report_parse_error(fail: Bag<'_>) {
println!("TODO Gracefully report parse error in repl: {:?}", fail); println!("TODO Gracefully report parse error in repl: {:?}", fail);
} }
fn eval_and_format(src: &str) -> Result<String, Fail> { fn eval_and_format<'a>(src: &str) -> Result<String, Bag<'a>> {
gen_and_eval(src.as_bytes(), Triple::host(), OptLevel::Normal).map(|output| match output { gen_and_eval(src.as_bytes(), Triple::host(), OptLevel::Normal).map(|output| match output {
ReplOutput::NoProblems { expr, expr_type } => { ReplOutput::NoProblems { expr, expr_type } => {
format!("\n{} {}:{} {}", expr, PINK, END_COL, expr_type) format!("\n{} {}:{} {}", expr, PINK, END_COL, expr_type)

View file

@ -7,7 +7,7 @@ use roc_collections::all::{MutMap, MutSet};
use roc_fmt::annotation::Formattable; use roc_fmt::annotation::Formattable;
use roc_fmt::annotation::{Newlines, Parens}; use roc_fmt::annotation::{Newlines, Parens};
use roc_gen::llvm::build::{build_proc, build_proc_header, OptLevel}; use roc_gen::llvm::build::{build_proc, build_proc_header, OptLevel};
use roc_parse::parser::Fail; use roc_parse::parser::Bag;
use roc_types::pretty_print::{content_to_string, name_all_type_vars}; use roc_types::pretty_print::{content_to_string, name_all_type_vars};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::str::from_utf8_unchecked; use std::str::from_utf8_unchecked;
@ -18,7 +18,11 @@ pub enum ReplOutput {
NoProblems { expr: String, expr_type: String }, NoProblems { expr: String, expr_type: String },
} }
pub fn gen_and_eval(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<ReplOutput, Fail> { pub fn gen_and_eval<'a>(
src: &[u8],
target: Triple,
opt_level: OptLevel,
) -> Result<ReplOutput, Bag<'a>> {
use roc_reporting::report::{ use roc_reporting::report::{
can_problem, mono_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE, can_problem, mono_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE,
}; };

View file

@ -10,7 +10,7 @@ use roc_collections::all::MutMap;
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds}; use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds};
use roc_parse::ast::{self, Attempting}; use roc_parse::ast::{self, Attempting};
use roc_parse::blankspace::space0_before; use roc_parse::blankspace::space0_before;
use roc_parse::parser::{loc, Fail, Parser, State}; use roc_parse::parser::{loc, Bag, Parser, State};
use roc_problem::can::Problem; use roc_problem::can::Problem;
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
use roc_types::subs::{VarStore, Variable}; use roc_types::subs::{VarStore, Variable};
@ -21,19 +21,22 @@ pub fn test_home() -> ModuleId {
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Expr<'a>, Fail> { pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Expr<'a>, Bag<'a>> {
parse_loc_with(arena, input).map(|loc_expr| loc_expr.value) parse_loc_with(arena, input).map(|loc_expr| loc_expr.value)
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Located<ast::Expr<'a>>, Fail> { pub fn parse_loc_with<'a>(
let state = State::new(input.trim().as_bytes(), Attempting::Module); arena: &'a Bump,
input: &'a str,
) -> Result<Located<ast::Expr<'a>>, Bag<'a>> {
let state = State::new_in(arena, input.trim().as_bytes(), Attempting::Module);
let parser = space0_before(loc(roc_parse::expr::expr(0)), 0); let parser = space0_before(loc(roc_parse::expr::expr(0)), 0);
let answer = parser.parse(&arena, state); let answer = parser.parse(&arena, state);
answer answer
.map(|(loc_expr, _)| loc_expr) .map(|(_, loc_expr, _)| loc_expr)
.map_err(|(fail, _)| fail) .map_err(|(_, fail, _)| fail)
} }
#[allow(dead_code)] #[allow(dead_code)]

View file

@ -17,16 +17,16 @@ mod test_fmt {
use roc_parse::ast::{Attempting, Expr}; use roc_parse::ast::{Attempting, Expr};
use roc_parse::blankspace::space0_before; use roc_parse::blankspace::space0_before;
use roc_parse::module::{self, module_defs}; use roc_parse::module::{self, module_defs};
use roc_parse::parser::{Fail, Parser, State}; use roc_parse::parser::{Bag, Parser, State};
fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Expr<'a>, Fail> { fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Expr<'a>, Bag<'a>> {
let state = State::new(input.trim().as_bytes(), Attempting::Module); let state = State::new_in(arena, input.trim().as_bytes(), Attempting::Module);
let parser = space0_before(loc!(roc_parse::expr::expr(0)), 0); let parser = space0_before(loc!(roc_parse::expr::expr(0)), 0);
let answer = parser.parse(&arena, state); let answer = parser.parse(&arena, state);
answer answer
.map(|(loc_expr, _)| loc_expr.value) .map(|(_, loc_expr, _)| loc_expr.value)
.map_err(|(fail, _)| fail) .map_err(|(_, fail, _)| fail)
} }
fn expr_formats_to(input: &str, expected: &str) { fn expr_formats_to(input: &str, expected: &str) {
@ -55,14 +55,14 @@ mod test_fmt {
let src = src.trim_end(); let src = src.trim_end();
let expected = expected.trim_end(); let expected = expected.trim_end();
match module::header().parse(&arena, State::new(src.as_bytes(), Attempting::Module)) { match module::header().parse(&arena, State::new_in(&arena, src.as_bytes(), Attempting::Module)) {
Ok((actual, state)) => { Ok((_, actual, state)) => {
let mut buf = String::new_in(&arena); let mut buf = String::new_in(&arena);
fmt_module(&mut buf, &actual); fmt_module(&mut buf, &actual);
match module_defs().parse(&arena, state) { match module_defs().parse(&arena, state) {
Ok((loc_defs, _)) => { Ok((_, loc_defs, _)) => {
for loc_def in loc_defs { for loc_def in loc_defs {
fmt_def(&mut buf, arena.alloc(loc_def.value), 0); fmt_def(&mut buf, arena.alloc(loc_def.value), 0);
} }
@ -839,6 +839,7 @@ mod test_fmt {
} }
#[test] #[test]
#[ignore]
fn final_comment_in_empty_record_type_definition() { fn final_comment_in_empty_record_type_definition() {
expr_formats_to( expr_formats_to(
indoc!( indoc!(
@ -862,6 +863,7 @@ mod test_fmt {
} }
#[test] #[test]
#[ignore]
fn multiline_inside_empty_record_annotation() { fn multiline_inside_empty_record_annotation() {
expr_formats_same(indoc!( expr_formats_same(indoc!(
r#" r#"
@ -1296,6 +1298,7 @@ mod test_fmt {
} }
#[test] #[test]
#[ignore]
fn empty_record_with_comment() { fn empty_record_with_comment() {
expr_formats_same(indoc!( expr_formats_same(indoc!(
r#" r#"
@ -1306,6 +1309,7 @@ mod test_fmt {
} }
#[test] #[test]
#[ignore]
fn empty_record_with_newline() { fn empty_record_with_newline() {
expr_formats_to( expr_formats_to(
indoc!( indoc!(

View file

@ -51,6 +51,9 @@ mod gen_num {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
app "test" provides [ main ] to "./platform"
main =
i : I64 i : I64
i = 64 i = 64
@ -446,24 +449,6 @@ mod gen_num {
-1, -1,
i64 i64
); );
assert_evals_to!(
indoc!(
r#"
limitedNegate = \num ->
if num == 1 then
-1
else if num == -1 then
1
else
num
limitedNegate 1
"#
),
-1,
i64
);
} }
#[test] #[test]

View file

@ -18,6 +18,7 @@ roc_unify = { path = "../unify" }
roc_parse = { path = "../parse" } roc_parse = { path = "../parse" }
roc_solve = { path = "../solve" } roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" } roc_mono = { path = "../mono" }
roc_reporting = { path = "../reporting" }
bumpalo = { version = "3.2", features = ["collections"] } bumpalo = { version = "3.2", features = ["collections"] }
inlinable_string = "0.1" inlinable_string = "0.1"
parking_lot = { version = "0.11", features = ["deadlock_detection"] } parking_lot = { version = "0.11", features = ["deadlock_detection"] }

View file

@ -27,7 +27,7 @@ use roc_parse::header::{
ExposesEntry, ImportsEntry, PackageEntry, PackageOrPath, PlatformHeader, To, TypedIdent, ExposesEntry, ImportsEntry, PackageEntry, PackageOrPath, PlatformHeader, To, TypedIdent,
}; };
use roc_parse::module::module_defs; use roc_parse::module::module_defs;
use roc_parse::parser::{self, Fail, Parser}; use roc_parse::parser::{self, ParseProblem, Parser};
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
use roc_solve::module::SolvedModule; use roc_solve::module::SolvedModule;
use roc_solve::solve; use roc_solve::solve;
@ -762,6 +762,8 @@ enum Msg<'a> {
subs: Subs, subs: Subs,
exposed_to_host: MutMap<Symbol, Variable>, exposed_to_host: MutMap<Symbol, Variable>,
}, },
FailedToParse(ParseProblem<'a>),
} }
#[derive(Debug)] #[derive(Debug)]
@ -968,20 +970,20 @@ enum WorkerMsg {
} }
#[derive(Debug)] #[derive(Debug)]
pub enum LoadingProblem { pub enum LoadingProblem<'a> {
FileProblem { FileProblem {
filename: PathBuf, filename: PathBuf,
error: io::ErrorKind, error: io::ErrorKind,
msg: &'static str, msg: &'static str,
}, },
ParsingFailed { ParsingFailed(ParseProblem<'a>),
filename: PathBuf,
fail: Fail,
},
UnexpectedHeader(String), UnexpectedHeader(String),
MsgChannelDied, MsgChannelDied,
ErrJoiningWorkerThreads, ErrJoiningWorkerThreads,
TriedToImportAppModule, TriedToImportAppModule,
/// a formatted report of parsing failure
ParsingFailedReport(String),
} }
pub enum Phases { pub enum Phases {
@ -998,7 +1000,7 @@ fn enqueue_task<'a>(
injector: &Injector<BuildTask<'a>>, injector: &Injector<BuildTask<'a>>,
listeners: &[Sender<WorkerMsg>], listeners: &[Sender<WorkerMsg>],
task: BuildTask<'a>, task: BuildTask<'a>,
) -> Result<(), LoadingProblem> { ) -> Result<(), LoadingProblem<'a>> {
injector.push(task); injector.push(task);
for listener in listeners { for listener in listeners {
@ -1010,14 +1012,14 @@ fn enqueue_task<'a>(
Ok(()) Ok(())
} }
pub fn load_and_typecheck( pub fn load_and_typecheck<'a>(
arena: &Bump, arena: &'a Bump,
filename: PathBuf, filename: PathBuf,
stdlib: &StdLib, stdlib: &'a StdLib,
src_dir: &Path, src_dir: &Path,
exposed_types: SubsByModule, exposed_types: SubsByModule,
ptr_bytes: u32, ptr_bytes: u32,
) -> Result<LoadedModule, LoadingProblem> { ) -> Result<LoadedModule, LoadingProblem<'a>> {
use LoadResult::*; use LoadResult::*;
let load_start = LoadStart::from_path(arena, filename, stdlib.mode)?; let load_start = LoadStart::from_path(arena, filename, stdlib.mode)?;
@ -1043,7 +1045,7 @@ pub fn load_and_monomorphize<'a>(
src_dir: &Path, src_dir: &Path,
exposed_types: SubsByModule, exposed_types: SubsByModule,
ptr_bytes: u32, ptr_bytes: u32,
) -> Result<MonomorphizedModule<'a>, LoadingProblem> { ) -> Result<MonomorphizedModule<'a>, LoadingProblem<'a>> {
use LoadResult::*; use LoadResult::*;
let load_start = LoadStart::from_path(arena, filename, stdlib.mode)?; let load_start = LoadStart::from_path(arena, filename, stdlib.mode)?;
@ -1070,7 +1072,7 @@ pub fn load_and_monomorphize_from_str<'a>(
src_dir: &Path, src_dir: &Path,
exposed_types: SubsByModule, exposed_types: SubsByModule,
ptr_bytes: u32, ptr_bytes: u32,
) -> Result<MonomorphizedModule<'a>, LoadingProblem> { ) -> Result<MonomorphizedModule<'a>, LoadingProblem<'a>> {
use LoadResult::*; use LoadResult::*;
let load_start = LoadStart::from_str(arena, filename, src, stdlib.mode)?; let load_start = LoadStart::from_str(arena, filename, src, stdlib.mode)?;
@ -1101,7 +1103,7 @@ impl<'a> LoadStart<'a> {
arena: &'a Bump, arena: &'a Bump,
filename: PathBuf, filename: PathBuf,
mode: Mode, mode: Mode,
) -> Result<Self, LoadingProblem> { ) -> Result<Self, LoadingProblem<'a>> {
let arc_modules = Arc::new(Mutex::new(PackageModuleIds::default())); let arc_modules = Arc::new(Mutex::new(PackageModuleIds::default()));
let root_exposed_ident_ids = IdentIds::exposed_builtins(0); let root_exposed_ident_ids = IdentIds::exposed_builtins(0);
let ident_ids_by_module = Arc::new(Mutex::new(root_exposed_ident_ids)); let ident_ids_by_module = Arc::new(Mutex::new(root_exposed_ident_ids));
@ -1134,7 +1136,7 @@ impl<'a> LoadStart<'a> {
filename: PathBuf, filename: PathBuf,
src: &'a str, src: &'a str,
mode: Mode, mode: Mode,
) -> Result<Self, LoadingProblem> { ) -> Result<Self, LoadingProblem<'a>> {
let arc_modules = Arc::new(Mutex::new(PackageModuleIds::default())); let arc_modules = Arc::new(Mutex::new(PackageModuleIds::default()));
let root_exposed_ident_ids = IdentIds::exposed_builtins(0); let root_exposed_ident_ids = IdentIds::exposed_builtins(0);
let ident_ids_by_module = Arc::new(Mutex::new(root_exposed_ident_ids)); let ident_ids_by_module = Arc::new(Mutex::new(root_exposed_ident_ids));
@ -1220,7 +1222,7 @@ fn load<'a>(
exposed_types: SubsByModule, exposed_types: SubsByModule,
goal_phase: Phase, goal_phase: Phase,
ptr_bytes: u32, ptr_bytes: u32,
) -> Result<LoadResult<'a>, LoadingProblem> ) -> Result<LoadResult<'a>, LoadingProblem<'a>>
where where
{ {
let LoadStart { let LoadStart {
@ -1310,7 +1312,7 @@ where
let injector = &injector; let injector = &injector;
// Record this thread's handle so the main thread can join it later. // Record this thread's handle so the main thread can join it later.
thread_scope let res_join_handle = thread_scope
.builder() .builder()
.stack_size(EXPANDED_STACK_SIZE) .stack_size(EXPANDED_STACK_SIZE)
.spawn(move |_| { .spawn(move |_| {
@ -1322,7 +1324,7 @@ where
// shut down the thread, so when the main thread // shut down the thread, so when the main thread
// blocks on joining with all the worker threads, // blocks on joining with all the worker threads,
// it can finally exit too! // it can finally exit too!
return; return Ok(());
} }
WorkerMsg::TaskAdded => { WorkerMsg::TaskAdded => {
// Find a task - either from this thread's queue, // Find a task - either from this thread's queue,
@ -1335,14 +1337,26 @@ where
// added. In that case, do nothing, and keep waiting // added. In that case, do nothing, and keep waiting
// until we receive a Shutdown message. // until we receive a Shutdown message.
if let Some(task) = find_task(&worker, injector, stealers) { if let Some(task) = find_task(&worker, injector, stealers) {
run_task( let result = run_task(
task, task,
worker_arena, worker_arena,
src_dir, src_dir,
msg_tx.clone(), msg_tx.clone(),
ptr_bytes, ptr_bytes,
) );
.expect("Msg channel closed unexpectedly.");
match result {
Ok(()) => {}
Err(LoadingProblem::MsgChannelDied) => {
panic!("Msg channel closed unexpectedly.")
}
Err(LoadingProblem::ParsingFailed(problem)) => {
msg_tx.send(Msg::FailedToParse(problem)).unwrap();
}
Err(other) => {
return Err(other);
}
}
} }
} }
} }
@ -1351,8 +1365,11 @@ where
// Needed to prevent a borrow checker error about this closure // Needed to prevent a borrow checker error about this closure
// outliving its enclosing function. // outliving its enclosing function.
drop(worker_msg_rx); drop(worker_msg_rx);
})
.unwrap(); Ok(())
});
res_join_handle.unwrap();
} }
let mut state = State { let mut state = State {
@ -1440,6 +1457,51 @@ where
exposed_to_host, exposed_to_host,
))); )));
} }
Msg::FailedToParse(problem) => {
// Shut down all the worker threads.
for listener in worker_listeners {
listener
.send(WorkerMsg::Shutdown)
.map_err(|_| LoadingProblem::MsgChannelDied)?;
}
use roc_reporting::report::{
parse_problem, RocDocAllocator, DEFAULT_PALETTE,
};
// TODO this is not in fact safe
let src = unsafe { from_utf8_unchecked(problem.bytes) };
let src_lines: Vec<&str> = src.split('\n').collect();
let palette = DEFAULT_PALETTE;
let mut module_ids = Arc::try_unwrap(state.arc_modules)
.unwrap_or_else(|_| {
panic!("There were still outstanding Arc references to module_ids")
})
.into_inner()
.into_module_ids();
let module_id =
module_ids.get_or_insert(&"find module name somehow?".into());
let interns = Interns {
module_ids,
all_ident_ids: state.constrained_ident_ids,
};
// Report parsing and canonicalization problems
let alloc = RocDocAllocator::new(&src_lines, module_id, &interns);
let starting_line = 0;
let report =
parse_problem(&alloc, problem.filename.clone(), starting_line, problem);
let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette);
return Err(LoadingProblem::ParsingFailedReport(buf));
}
msg => { msg => {
// This is where most of the main thread's work gets done. // This is where most of the main thread's work gets done.
// Everything up to this point has been setting up the threading // Everything up to this point has been setting up the threading
@ -1468,7 +1530,7 @@ fn start_tasks<'a>(
state: &mut State<'a>, state: &mut State<'a>,
injector: &Injector<BuildTask<'a>>, injector: &Injector<BuildTask<'a>>,
worker_listeners: &'a [Sender<WorkerMsg>], worker_listeners: &'a [Sender<WorkerMsg>],
) -> Result<(), LoadingProblem> { ) -> Result<(), LoadingProblem<'a>> {
for (module_id, phase) in work { for (module_id, phase) in work {
for task in start_phase(module_id, phase, state) { for task in start_phase(module_id, phase, state) {
enqueue_task(&injector, worker_listeners, task)? enqueue_task(&injector, worker_listeners, task)?
@ -1485,7 +1547,7 @@ fn update<'a>(
injector: &Injector<BuildTask<'a>>, injector: &Injector<BuildTask<'a>>,
worker_listeners: &'a [Sender<WorkerMsg>], worker_listeners: &'a [Sender<WorkerMsg>],
arena: &'a Bump, arena: &'a Bump,
) -> Result<State<'a>, LoadingProblem> { ) -> Result<State<'a>, LoadingProblem<'a>> {
use self::Msg::*; use self::Msg::*;
match msg { match msg {
@ -1942,6 +2004,9 @@ fn update<'a>(
Msg::FinishedAllSpecialization { .. } => { Msg::FinishedAllSpecialization { .. } => {
unreachable!(); unreachable!();
} }
Msg::FailedToParse(_) => {
unreachable!();
}
} }
} }
@ -2064,7 +2129,7 @@ fn load_pkg_config<'a>(
module_ids: Arc<Mutex<PackageModuleIds<'a>>>, module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>, ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
mode: Mode, mode: Mode,
) -> Result<Msg<'a>, LoadingProblem> { ) -> Result<Msg<'a>, LoadingProblem<'a>> {
let module_start_time = SystemTime::now(); let module_start_time = SystemTime::now();
let filename = PathBuf::from(src_dir); let filename = PathBuf::from(src_dir);
@ -2074,9 +2139,10 @@ fn load_pkg_config<'a>(
let file_io_duration = file_io_start.elapsed().unwrap(); let file_io_duration = file_io_start.elapsed().unwrap();
match file { match file {
Ok(bytes) => { Ok(bytes_vec) => {
let parse_start = SystemTime::now(); let parse_start = SystemTime::now();
let parse_state = parser::State::new(arena.alloc(bytes), Attempting::Module); 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 parsed = roc_parse::module::header().parse(&arena, parse_state);
let parse_header_duration = parse_start.elapsed().unwrap(); let parse_header_duration = parse_start.elapsed().unwrap();
@ -2091,19 +2157,19 @@ fn load_pkg_config<'a>(
effect_module_timing.parse_header = parse_header_duration; effect_module_timing.parse_header = parse_header_duration;
match parsed { match parsed {
Ok((ast::Module::Interface { header }, _parse_state)) => { Ok((_, ast::Module::Interface { header }, _parse_state)) => {
Err(LoadingProblem::UnexpectedHeader(format!( Err(LoadingProblem::UnexpectedHeader(format!(
"expected platform/package module, got Interface with header\n{:?}", "expected platform/package module, got Interface with header\n{:?}",
header header
))) )))
} }
Ok((ast::Module::App { header }, _parse_state)) => { Ok((_, ast::Module::App { header }, _parse_state)) => {
Err(LoadingProblem::UnexpectedHeader(format!( Err(LoadingProblem::UnexpectedHeader(format!(
"expected platform/package module, got App with header\n{:?}", "expected platform/package module, got App with header\n{:?}",
header 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 // make a Pkg-Config module that ultimately exposes `main` to the host
let pkg_config_module_msg = fabricate_pkg_config_module( let pkg_config_module_msg = fabricate_pkg_config_module(
arena, arena,
@ -2131,7 +2197,9 @@ fn load_pkg_config<'a>(
Ok(Msg::Many(vec![effects_module_msg, pkg_config_module_msg])) Ok(Msg::Many(vec![effects_module_msg, pkg_config_module_msg]))
} }
Err((fail, _)) => Err(LoadingProblem::ParsingFailed { filename, fail }), Err((_, fail, _)) => Err(LoadingProblem::ParsingFailed(
fail.into_parse_problem(filename, bytes),
)),
} }
} }
@ -2152,7 +2220,7 @@ fn load_module<'a>(
arc_shorthands: Arc<Mutex<MutMap<&'a str, PackageOrPath<'a>>>>, arc_shorthands: Arc<Mutex<MutMap<&'a str, PackageOrPath<'a>>>>,
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>, ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
mode: Mode, mode: Mode,
) -> Result<(ModuleId, Msg<'a>), LoadingProblem> { ) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> {
let module_start_time = SystemTime::now(); let module_start_time = SystemTime::now();
let mut filename = PathBuf::new(); let mut filename = PathBuf::new();
@ -2240,9 +2308,9 @@ fn parse_header<'a>(
mode: Mode, mode: Mode,
src_bytes: &'a [u8], src_bytes: &'a [u8],
start_time: SystemTime, start_time: SystemTime,
) -> Result<(ModuleId, Msg<'a>), LoadingProblem> { ) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> {
let parse_start = SystemTime::now(); let parse_start = SystemTime::now();
let parse_state = parser::State::new(src_bytes, Attempting::Module); let parse_state = parser::State::new_in(arena, src_bytes, Attempting::Module);
let parsed = roc_parse::module::header().parse(&arena, parse_state); let parsed = roc_parse::module::header().parse(&arena, parse_state);
let parse_header_duration = parse_start.elapsed().unwrap(); let parse_header_duration = parse_start.elapsed().unwrap();
@ -2253,7 +2321,7 @@ fn parse_header<'a>(
module_timing.parse_header = parse_header_duration; module_timing.parse_header = parse_header_duration;
match parsed { match parsed {
Ok((ast::Module::Interface { header }, parse_state)) => Ok(send_header( Ok((_, ast::Module::Interface { header }, parse_state)) => Ok(send_header(
Located { Located {
region: header.name.region, region: header.name.region,
value: ModuleNameEnum::Interface(header.name.value), value: ModuleNameEnum::Interface(header.name.value),
@ -2269,7 +2337,7 @@ fn parse_header<'a>(
ident_ids_by_module, ident_ids_by_module,
module_timing, module_timing,
)), )),
Ok((ast::Module::App { header }, parse_state)) => { Ok((_, ast::Module::App { header }, parse_state)) => {
let mut pkg_config_dir = filename.clone(); let mut pkg_config_dir = filename.clone();
pkg_config_dir.pop(); pkg_config_dir.pop();
@ -2367,7 +2435,7 @@ fn parse_header<'a>(
}, },
} }
} }
Ok((ast::Module::Platform { header }, _parse_state)) => fabricate_effects_module( Ok((_, ast::Module::Platform { header }, _parse_state)) => fabricate_effects_module(
arena, arena,
&"", &"",
module_ids, module_ids,
@ -2376,7 +2444,9 @@ fn parse_header<'a>(
header, header,
module_timing, module_timing,
), ),
Err((fail, _)) => Err(LoadingProblem::ParsingFailed { filename, fail }), Err((_, fail, _)) => Err(LoadingProblem::ParsingFailed(
fail.into_parse_problem(filename, src_bytes),
)),
} }
} }
@ -2389,7 +2459,7 @@ fn load_filename<'a>(
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>, ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
module_start_time: SystemTime, module_start_time: SystemTime,
mode: Mode, mode: Mode,
) -> Result<(ModuleId, Msg<'a>), LoadingProblem> { ) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> {
let file_io_start = SystemTime::now(); let file_io_start = SystemTime::now();
let file = fs::read(&filename); let file = fs::read(&filename);
let file_io_duration = file_io_start.elapsed().unwrap(); let file_io_duration = file_io_start.elapsed().unwrap();
@ -2425,7 +2495,7 @@ fn load_from_str<'a>(
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>, ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
module_start_time: SystemTime, module_start_time: SystemTime,
mode: Mode, mode: Mode,
) -> Result<(ModuleId, Msg<'a>), LoadingProblem> { ) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> {
let file_io_start = SystemTime::now(); let file_io_start = SystemTime::now();
let file_io_duration = file_io_start.elapsed().unwrap(); let file_io_duration = file_io_start.elapsed().unwrap();
@ -2993,7 +3063,7 @@ fn fabricate_pkg_config_module<'a>(
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>, ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
header: &PlatformHeader<'a>, header: &PlatformHeader<'a>,
module_timing: ModuleTiming, module_timing: ModuleTiming,
) -> Result<(ModuleId, Msg<'a>), LoadingProblem> { ) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> {
let provides: &'a [Located<ExposesEntry<'a, &'a str>>] = let provides: &'a [Located<ExposesEntry<'a, &'a str>>] =
header.provides.clone().into_bump_slice(); header.provides.clone().into_bump_slice();
@ -3022,7 +3092,7 @@ fn fabricate_effects_module<'a>(
mode: Mode, mode: Mode,
header: PlatformHeader<'a>, header: PlatformHeader<'a>,
module_timing: ModuleTiming, module_timing: ModuleTiming,
) -> Result<(ModuleId, Msg<'a>), LoadingProblem> { ) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> {
let num_exposes = header.provides.len() + 1; let num_exposes = header.provides.len() + 1;
let mut exposed: Vec<Symbol> = Vec::with_capacity(num_exposes); let mut exposed: Vec<Symbol> = Vec::with_capacity(num_exposes);
@ -3300,7 +3370,7 @@ fn canonicalize_and_constrain<'a>(
aliases: MutMap<Symbol, Alias>, aliases: MutMap<Symbol, Alias>,
mode: Mode, mode: Mode,
parsed: ParsedModule<'a>, parsed: ParsedModule<'a>,
) -> Result<Msg<'a>, LoadingProblem> { ) -> Result<Msg<'a>, LoadingProblem<'a>> {
let canonicalize_start = SystemTime::now(); let canonicalize_start = SystemTime::now();
let ParsedModule { let ParsedModule {
@ -3381,13 +3451,18 @@ fn canonicalize_and_constrain<'a>(
} }
} }
fn parse<'a>(arena: &'a Bump, header: ModuleHeader<'a>) -> Result<Msg<'a>, LoadingProblem> { fn parse<'a>(arena: &'a Bump, header: ModuleHeader<'a>) -> Result<Msg<'a>, LoadingProblem<'a>> {
let mut module_timing = header.module_timing; let mut module_timing = header.module_timing;
let parse_start = SystemTime::now(); let parse_start = SystemTime::now();
let parse_state = parser::State::new(&header.src, Attempting::Module); let parse_state = parser::State::new_in(arena, &header.src, Attempting::Module);
let (parsed_defs, _) = module_defs() let parsed_defs = match module_defs().parse(&arena, parse_state) {
.parse(&arena, parse_state) Ok((_, success, _state)) => success,
.expect("TODO gracefully handle parse error on module defs. IMPORTANT: Bail out entirely if there are any BadUtf8 problems! That means the whole source file is not valid UTF-8 and any other errors we report may get mis-reported. We rely on this for safety in an `unsafe` block later on in this function."); Err((_, fail, _)) => {
return Err(LoadingProblem::ParsingFailed(
fail.into_parse_problem(header.module_path, header.src),
));
}
};
let parsed_defs = parsed_defs.into_bump_slice(); let parsed_defs = parsed_defs.into_bump_slice();
@ -3767,7 +3842,7 @@ fn run_task<'a>(
src_dir: &Path, src_dir: &Path,
msg_tx: MsgSender<'a>, msg_tx: MsgSender<'a>,
ptr_bytes: u32, ptr_bytes: u32,
) -> Result<(), LoadingProblem> { ) -> Result<(), LoadingProblem<'a>> {
use BuildTask::*; use BuildTask::*;
let msg = match task { let msg = match task {

View file

@ -15,7 +15,7 @@ use roc_module::ident::Ident;
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol}; use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol};
use roc_parse::ast::{self, Attempting}; use roc_parse::ast::{self, Attempting};
use roc_parse::blankspace::space0_before; use roc_parse::blankspace::space0_before;
use roc_parse::parser::{loc, Fail, Parser, State}; use roc_parse::parser::{loc, Bag, Parser, State};
use roc_problem::can::Problem; use roc_problem::can::Problem;
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
use roc_solve::solve; use roc_solve::solve;
@ -62,19 +62,22 @@ where
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Expr<'a>, Fail> { pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Expr<'a>, Bag<'a>> {
parse_loc_with(arena, input).map(|loc_expr| loc_expr.value) parse_loc_with(arena, input).map(|loc_expr| loc_expr.value)
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Located<ast::Expr<'a>>, Fail> { pub fn parse_loc_with<'a>(
let state = State::new(input.trim().as_bytes(), Attempting::Module); arena: &'a Bump,
input: &'a str,
) -> Result<Located<ast::Expr<'a>>, Bag<'a>> {
let state = State::new_in(arena, input.trim().as_bytes(), Attempting::Module);
let parser = space0_before(loc(roc_parse::expr::expr(0)), 0); let parser = space0_before(loc(roc_parse::expr::expr(0)), 0);
let answer = parser.parse(&arena, state); let answer = parser.parse(&arena, state);
answer answer
.map(|(loc_expr, _)| loc_expr) .map(|(_, loc_expr, _)| loc_expr)
.map_err(|(fail, _)| fail) .map_err(|(_, fail, _)| fail)
} }
#[allow(dead_code)] #[allow(dead_code)]

View file

@ -124,7 +124,7 @@ mod test_load {
let loaded = roc_load::file::load_and_typecheck( let loaded = roc_load::file::load_and_typecheck(
&arena, &arena,
filename, filename,
&roc_builtins::std::standard_stdlib(), arena.alloc(roc_builtins::std::standard_stdlib()),
src_dir.as_path(), src_dir.as_path(),
subs_by_module, subs_by_module,
8, 8,
@ -287,7 +287,7 @@ mod test_load {
let loaded = roc_load::file::load_and_typecheck( let loaded = roc_load::file::load_and_typecheck(
&arena, &arena,
filename, filename,
&roc_builtins::std::standard_stdlib(), arena.alloc(roc_builtins::std::standard_stdlib()),
src_dir.as_path(), src_dir.as_path(),
subs_by_module, subs_by_module,
8, 8,

View file

@ -15,7 +15,7 @@ use roc_module::ident::Ident;
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol}; use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol};
use roc_parse::ast::{self, Attempting}; use roc_parse::ast::{self, Attempting};
use roc_parse::blankspace::space0_before; use roc_parse::blankspace::space0_before;
use roc_parse::parser::{loc, Fail, Parser, State}; use roc_parse::parser::{loc, Bag, Parser, State};
use roc_problem::can::Problem; use roc_problem::can::Problem;
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
use roc_solve::solve; use roc_solve::solve;
@ -47,19 +47,22 @@ pub fn infer_expr(
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Expr<'a>, Fail> { pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Expr<'a>, Bag<'a>> {
parse_loc_with(arena, input).map(|loc_expr| loc_expr.value) parse_loc_with(arena, input).map(|loc_expr| loc_expr.value)
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Located<ast::Expr<'a>>, Fail> { pub fn parse_loc_with<'a>(
let state = State::new(input.as_bytes(), Attempting::Module); arena: &'a Bump,
input: &'a str,
) -> Result<Located<ast::Expr<'a>>, Bag<'a>> {
let state = State::new_in(arena, input.as_bytes(), Attempting::Module);
let parser = space0_before(loc(roc_parse::expr::expr(0)), 0); let parser = space0_before(loc(roc_parse::expr::expr(0)), 0);
let answer = parser.parse(&arena, state); let answer = parser.parse(&arena, state);
answer answer
.map(|(loc_expr, _)| loc_expr) .map(|(_, loc_expr, _)| loc_expr)
.map_err(|(fail, _)| fail) .map_err(|(_, fail, _)| fail)
} }
#[allow(dead_code)] #[allow(dead_code)]

View file

@ -611,6 +611,7 @@ pub enum Attempting {
TypeVariable, TypeVariable,
WhenCondition, WhenCondition,
WhenBranch, WhenBranch,
TODO,
} }
impl<'a> Expr<'a> { impl<'a> Expr<'a> {

View file

@ -1,8 +1,10 @@
use crate::ast::CommentOrNewline::{self, *}; use crate::ast::CommentOrNewline::{self, *};
use crate::ast::{Attempting, Spaceable}; use crate::ast::{Attempting, Spaceable};
use crate::parser::{ use crate::parser::{
self, and, ascii_char, ascii_string, optional, parse_utf8, peek_utf8_char, then, unexpected, self, and, ascii_char, ascii_string, backtrackable, optional, parse_utf8, peek_utf8_char, then,
unexpected_eof, FailReason, Parser, State, unexpected, unexpected_eof, FailReason, Parser,
Progress::{self, *},
State,
}; };
use bumpalo::collections::string::String; use bumpalo::collections::string::String;
use bumpalo::collections::vec::Vec; use bumpalo::collections::vec::Vec;
@ -130,7 +132,7 @@ where
P: 'a, P: 'a,
{ {
parser::map_with_arena( parser::map_with_arena(
and!(space1(min_indent), parser), and!(backtrackable(space1(min_indent)), parser),
|arena, (space_list, loc_expr)| { |arena, (space_list, loc_expr)| {
if space_list.is_empty() { if space_list.is_empty() {
loc_expr loc_expr
@ -215,9 +217,9 @@ enum LineState {
pub fn line_comment<'a>() -> impl Parser<'a, &'a str> { pub fn line_comment<'a>() -> impl Parser<'a, &'a str> {
then( then(
and!(ascii_char(b'#'), optional(ascii_string("# "))), and!(ascii_char(b'#'), optional(ascii_string("# "))),
|_arena: &'a Bump, state: State<'a>, (_, opt_doc)| { |arena: &'a Bump, state: State<'a>, _, (_, opt_doc)| {
if opt_doc != None { if opt_doc != None {
return Err(unexpected(3, state, Attempting::LineComment)); return Err(unexpected(arena, 3, Attempting::LineComment, state));
} }
let mut length = 0; let mut length = 0;
@ -230,10 +232,10 @@ pub fn line_comment<'a>() -> impl Parser<'a, &'a str> {
} }
let comment = &state.bytes[..length]; let comment = &state.bytes[..length];
let state = state.advance_without_indenting(length + 1)?; let state = state.advance_without_indenting(arena, length + 1)?;
match parse_utf8(comment) { match parse_utf8(comment) {
Ok(comment_str) => Ok((comment_str, state)), Ok(comment_str) => Ok((MadeProgress, comment_str, state)),
Err(reason) => state.fail(reason), Err(reason) => state.fail(arena, MadeProgress, reason),
} }
}, },
) )
@ -241,9 +243,9 @@ pub fn line_comment<'a>() -> impl Parser<'a, &'a str> {
#[inline(always)] #[inline(always)]
pub fn spaces_exactly<'a>(spaces_expected: u16) -> impl Parser<'a, ()> { pub fn spaces_exactly<'a>(spaces_expected: u16) -> impl Parser<'a, ()> {
move |_arena: &'a Bump, state: State<'a>| { move |arena: &'a Bump, state: State<'a>| {
if spaces_expected == 0 { if spaces_expected == 0 {
return Ok(((), state)); return Ok((NoProgress, (), state));
} }
let mut state = state; let mut state = state;
@ -253,31 +255,34 @@ pub fn spaces_exactly<'a>(spaces_expected: u16) -> impl Parser<'a, ()> {
match peek_utf8_char(&state) { match peek_utf8_char(&state) {
Ok((' ', _)) => { Ok((' ', _)) => {
spaces_seen += 1; spaces_seen += 1;
state = state.advance_spaces(1)?; state = state.advance_spaces(arena, 1)?;
if spaces_seen == spaces_expected { if spaces_seen == spaces_expected {
return Ok(((), state)); return Ok((MadeProgress, (), state));
} }
} }
Ok(_) => { Ok(_) => {
return Err(unexpected( return Err(unexpected(
arena,
spaces_seen.into(), spaces_seen.into(),
Attempting::TODO,
state.clone(), state.clone(),
state.attempting,
)); ));
} }
Err(FailReason::BadUtf8) => { Err(FailReason::BadUtf8) => {
// If we hit an invalid UTF-8 character, bail out immediately. // If we hit an invalid UTF-8 character, bail out immediately.
return state.fail(FailReason::BadUtf8); let progress = Progress::progress_when(spaces_seen != 0);
return state.fail(arena, progress, FailReason::BadUtf8);
} }
Err(_) => { Err(_) => {
if spaces_seen == 0 { if spaces_seen == 0 {
return Err(unexpected_eof(0, state.attempting, state)); return Err(unexpected_eof(arena, state, 0));
} else { } else {
return Err(unexpected( return Err(unexpected(
arena,
spaces_seen.into(), spaces_seen.into(),
Attempting::TODO,
state.clone(), state.clone(),
state.attempting,
)); ));
} }
} }
@ -285,12 +290,13 @@ pub fn spaces_exactly<'a>(spaces_expected: u16) -> impl Parser<'a, ()> {
} }
if spaces_seen == 0 { if spaces_seen == 0 {
Err(unexpected_eof(0, state.attempting, state)) Err(unexpected_eof(arena, state, 0))
} else { } else {
Err(unexpected( Err(unexpected(
arena,
spaces_seen.into(), spaces_seen.into(),
state.clone(), Attempting::TODO,
state.attempting, state,
)) ))
} }
} }
@ -310,6 +316,8 @@ fn spaces<'a>(
let mut state = state; let mut state = state;
let mut any_newlines = false; let mut any_newlines = false;
let start_bytes_len = state.bytes.len();
while !state.bytes.is_empty() { while !state.bytes.is_empty() {
match peek_utf8_char(&state) { match peek_utf8_char(&state) {
Ok((ch, utf8_len)) => { Ok((ch, utf8_len)) => {
@ -321,15 +329,17 @@ fn spaces<'a>(
' ' => { ' ' => {
// Don't check indentation here; it might not be enough // Don't check indentation here; it might not be enough
// indentation yet, but maybe it will be after more spaces happen! // indentation yet, but maybe it will be after more spaces happen!
state = state.advance_spaces(1)?; state = state.advance_spaces(arena, 1)?;
} }
'\r' => { '\r' => {
// Ignore carriage returns. // Ignore carriage returns.
state = state.advance_spaces(1)?; state = state.advance_spaces(arena, 1)?;
} }
'\n' => { '\n' => {
// No need to check indentation because we're about to reset it anyway. // don't need to check the indent here since we'll reset it
state = state.newline()?; // anyway
state = state.newline(arena)?;
// Newlines only get added to the list when they're outside comments. // Newlines only get added to the list when they're outside comments.
space_list.push(Newline); space_list.push(Newline);
@ -339,10 +349,14 @@ fn spaces<'a>(
'#' => { '#' => {
// Check indentation to make sure we were indented enough // Check indentation to make sure we were indented enough
// before this comment began. // before this comment began.
let progress =
Progress::from_lengths(start_bytes_len, state.bytes.len());
state = state state = state
.check_indent(min_indent) .check_indent(arena, min_indent)
.map_err(|(fail, _)| (fail, original_state.clone()))? .map_err(|(fail, _)| {
.advance_without_indenting(1)?; (progress, fail, original_state.clone())
})?
.advance_without_indenting(arena, 1)?;
// We're now parsing a line comment! // We're now parsing a line comment!
line_state = LineState::Comment; line_state = LineState::Comment;
@ -351,7 +365,7 @@ fn spaces<'a>(
return if require_at_least_one && bytes_parsed <= 1 { return if require_at_least_one && bytes_parsed <= 1 {
// We've parsed 1 char and it was not a space, // We've parsed 1 char and it was not a space,
// but we require parsing at least one space! // but we require parsing at least one space!
Err(unexpected(0, state.clone(), state.attempting)) Err(unexpected(arena, 0, Attempting::TODO, state.clone()))
} else { } else {
// First make sure we were indented enough! // First make sure we were indented enough!
// //
@ -360,13 +374,19 @@ fn spaces<'a>(
// It's actively important for correctness that we skip // It's actively important for correctness that we skip
// this check if there are no newlines, because otherwise // this check if there are no newlines, because otherwise
// we would have false positives for single-line defs.) // we would have false positives for single-line defs.)
let progress = Progress::from_lengths(
start_bytes_len,
state.bytes.len(),
);
if any_newlines { if any_newlines {
state = state state = state.check_indent(arena, min_indent).map_err(
.check_indent(min_indent) |(fail, _)| {
.map_err(|(fail, _)| (fail, original_state))?; (progress, fail, original_state.clone())
},
)?;
} }
Ok((space_list.into_bump_slice(), state)) Ok((progress, space_list.into_bump_slice(), state))
}; };
} }
} }
@ -375,7 +395,7 @@ fn spaces<'a>(
match ch { match ch {
' ' => { ' ' => {
// If we're in a line comment, this won't affect indentation anyway. // If we're in a line comment, this won't affect indentation anyway.
state = state.advance_without_indenting(1)?; state = state.advance_without_indenting(arena, 1)?;
if comment_line_buf.len() == 1 { if comment_line_buf.len() == 1 {
match comment_line_buf.chars().next() { match comment_line_buf.chars().next() {
@ -400,7 +420,7 @@ fn spaces<'a>(
} }
} }
'\n' => { '\n' => {
state = state.newline()?; state = state.newline(arena)?;
match (comment_line_buf.len(), comment_line_buf.chars().next()) match (comment_line_buf.len(), comment_line_buf.chars().next())
{ {
@ -425,7 +445,8 @@ fn spaces<'a>(
} }
nonblank => { nonblank => {
// Chars can have btye lengths of more than 1! // Chars can have btye lengths of more than 1!
state = state.advance_without_indenting(nonblank.len_utf8())?; state = state
.advance_without_indenting(arena, nonblank.len_utf8())?;
comment_line_buf.push(nonblank); comment_line_buf.push(nonblank);
} }
@ -435,12 +456,12 @@ fn spaces<'a>(
match ch { match ch {
' ' => { ' ' => {
// If we're in a doc comment, this won't affect indentation anyway. // If we're in a doc comment, this won't affect indentation anyway.
state = state.advance_without_indenting(1)?; state = state.advance_without_indenting(arena, 1)?;
comment_line_buf.push(ch); comment_line_buf.push(ch);
} }
'\n' => { '\n' => {
state = state.newline()?; state = state.newline(arena)?;
// This was a newline, so end this doc comment. // This was a newline, so end this doc comment.
space_list.push(DocComment(comment_line_buf.into_bump_str())); space_list.push(DocComment(comment_line_buf.into_bump_str()));
@ -449,7 +470,7 @@ fn spaces<'a>(
line_state = LineState::Normal; line_state = LineState::Normal;
} }
nonblank => { nonblank => {
state = state.advance_without_indenting(utf8_len)?; state = state.advance_without_indenting(arena, utf8_len)?;
comment_line_buf.push(nonblank); comment_line_buf.push(nonblank);
} }
@ -459,11 +480,12 @@ fn spaces<'a>(
} }
Err(FailReason::BadUtf8) => { Err(FailReason::BadUtf8) => {
// If we hit an invalid UTF-8 character, bail out immediately. // If we hit an invalid UTF-8 character, bail out immediately.
return state.fail(FailReason::BadUtf8); let progress = Progress::from_lengths(start_bytes_len, state.bytes.len());
return state.fail(arena, progress, FailReason::BadUtf8);
} }
Err(_) => { Err(_) => {
if require_at_least_one && bytes_parsed == 0 { if require_at_least_one && bytes_parsed == 0 {
return Err(unexpected_eof(0, state.attempting, state)); return Err(unexpected_eof(arena, state, 0));
} else { } else {
let space_slice = space_list.into_bump_slice(); let space_slice = space_list.into_bump_slice();
@ -474,16 +496,18 @@ fn spaces<'a>(
// It's actively important for correctness that we skip // It's actively important for correctness that we skip
// this check if there are no newlines, because otherwise // this check if there are no newlines, because otherwise
// we would have false positives for single-line defs.) // we would have false positives for single-line defs.)
let progress = Progress::from_lengths(start_bytes_len, state.bytes.len());
if any_newlines { if any_newlines {
return Ok(( return Ok((
progress,
space_slice, space_slice,
state state
.check_indent(min_indent) .check_indent(arena, min_indent)
.map_err(|(fail, _)| (fail, original_state))?, .map_err(|(fail, _)| (progress, fail, original_state))?,
)); ));
} }
return Ok((space_slice, state)); return Ok((progress, space_slice, state));
} }
} }
}; };
@ -491,7 +515,7 @@ fn spaces<'a>(
// If we didn't parse anything, return unexpected EOF // If we didn't parse anything, return unexpected EOF
if require_at_least_one && original_state.bytes.len() == state.bytes.len() { if require_at_least_one && original_state.bytes.len() == state.bytes.len() {
Err(unexpected_eof(0, state.attempting, state)) Err(unexpected_eof(arena, state, 0))
} else { } else {
// First make sure we were indented enough! // First make sure we were indented enough!
// //
@ -500,13 +524,14 @@ fn spaces<'a>(
// It's actively important for correctness that we skip // It's actively important for correctness that we skip
// this check if there are no newlines, because otherwise // this check if there are no newlines, because otherwise
// we would have false positives for single-line defs.) // we would have false positives for single-line defs.)
let progress = Progress::from_lengths(start_bytes_len, state.bytes.len());
if any_newlines { if any_newlines {
state = state state = state
.check_indent(min_indent) .check_indent(arena, min_indent)
.map_err(|(fail, _)| (fail, original_state))?; .map_err(|(fail, _)| (progress, fail, original_state))?;
} }
Ok((space_list.into_bump_slice(), state)) Ok((progress, space_list.into_bump_slice(), state))
} }
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,7 @@ use crate::ast::{CommentOrNewline, Spaceable, StrLiteral, TypeAnnotation};
use crate::blankspace::space0; use crate::blankspace::space0;
use crate::ident::lowercase_ident; use crate::ident::lowercase_ident;
use crate::module::package_name; use crate::module::package_name;
use crate::parser::{ascii_char, optional, Either, Parser}; use crate::parser::{ascii_char, optional, Either, Parser, Progress::*, State};
use crate::string_literal; use crate::string_literal;
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use inlinable_string::InlinableString; use inlinable_string::InlinableString;
@ -245,12 +245,13 @@ pub fn package_entry<'a>() -> impl Parser<'a, PackageEntry<'a>> {
// e.g. "uc" in `uc: roc/unicode 1.0.0` // e.g. "uc" in `uc: roc/unicode 1.0.0`
// //
// (Indirect dependencies don't have a shorthand.) // (Indirect dependencies don't have a shorthand.)
let (opt_shorthand, state) = optional(and!( let (_, opt_shorthand, state) = optional(and!(
skip_second!(lowercase_ident(), ascii_char(b':')), skip_second!(lowercase_ident(), ascii_char(b':')),
space0(1) space0(1)
)) ))
.parse(arena, state)?; .parse(arena, state)?;
let (package_or_path, state) = loc!(package_or_path()).parse(arena, state)?; let (_, package_or_path, state) = loc!(package_or_path()).parse(arena, state)?;
let entry = match opt_shorthand { let entry = match opt_shorthand {
Some((shorthand, spaces_after_shorthand)) => PackageEntry::Entry { Some((shorthand, spaces_after_shorthand)) => PackageEntry::Entry {
shorthand, shorthand,
@ -264,7 +265,7 @@ pub fn package_entry<'a>() -> impl Parser<'a, PackageEntry<'a>> {
}, },
}; };
Ok((entry, state)) Ok((MadeProgress, entry, state))
} }
} }

View file

@ -1,6 +1,7 @@
use crate::ast::Attempting; use crate::ast::Attempting;
use crate::keyword; use crate::keyword;
use crate::parser::{peek_utf8_char, unexpected, Fail, FailReason, ParseResult, Parser, State}; use crate::parser::Progress::{self, *};
use crate::parser::{peek_utf8_char, unexpected, Bag, FailReason, ParseResult, Parser, State};
use bumpalo::collections::string::String; use bumpalo::collections::string::String;
use bumpalo::collections::vec::Vec; use bumpalo::collections::vec::Vec;
use bumpalo::Bump; use bumpalo::Bump;
@ -78,6 +79,8 @@ pub fn parse_ident<'a>(
let is_accessor_fn; let is_accessor_fn;
let mut is_private_tag = false; let mut is_private_tag = false;
let start_bytes_len = state.bytes.len();
// Identifiers and accessor functions must start with either a letter or a dot. // Identifiers and accessor functions must start with either a letter or a dot.
// If this starts with neither, it must be something else! // If this starts with neither, it must be something else!
match peek_utf8_char(&state) { match peek_utf8_char(&state) {
@ -88,20 +91,20 @@ pub fn parse_ident<'a>(
is_capitalized = first_ch.is_uppercase(); is_capitalized = first_ch.is_uppercase();
is_accessor_fn = false; is_accessor_fn = false;
state = state.advance_without_indenting(bytes_parsed)?; state = state.advance_without_indenting(arena, bytes_parsed)?;
} else if first_ch == '.' { } else if first_ch == '.' {
is_capitalized = false; is_capitalized = false;
is_accessor_fn = true; is_accessor_fn = true;
state = state.advance_without_indenting(bytes_parsed)?; state = state.advance_without_indenting(arena, bytes_parsed)?;
} else if first_ch == '@' { } else if first_ch == '@' {
state = state.advance_without_indenting(bytes_parsed)?; state = state.advance_without_indenting(arena, bytes_parsed)?;
// '@' must always be followed by a capital letter! // '@' must always be followed by a capital letter!
match peek_utf8_char(&state) { match peek_utf8_char(&state) {
Ok((next_ch, next_bytes_parsed)) => { Ok((next_ch, next_bytes_parsed)) => {
if next_ch.is_uppercase() { if next_ch.is_uppercase() {
state = state.advance_without_indenting(next_bytes_parsed)?; state = state.advance_without_indenting(arena, next_bytes_parsed)?;
part_buf.push('@'); part_buf.push('@');
part_buf.push(next_ch); part_buf.push(next_ch);
@ -111,19 +114,26 @@ pub fn parse_ident<'a>(
is_accessor_fn = false; is_accessor_fn = false;
} else { } else {
return Err(unexpected( return Err(unexpected(
arena,
bytes_parsed + next_bytes_parsed, bytes_parsed + next_bytes_parsed,
state,
Attempting::Identifier, Attempting::Identifier,
state,
)); ));
} }
} }
Err(reason) => return state.fail(reason), Err(reason) => {
let progress = Progress::from_lengths(start_bytes_len, state.bytes.len());
return state.fail(arena, progress, reason);
}
} }
} else { } else {
return Err(unexpected(0, state, Attempting::Identifier)); return Err(unexpected(arena, 0, Attempting::Identifier, state));
} }
} }
Err(reason) => return state.fail(reason), Err(reason) => {
let progress = Progress::from_lengths(start_bytes_len, state.bytes.len());
return state.fail(arena, progress, reason);
}
} }
while !state.bytes.is_empty() { while !state.bytes.is_empty() {
@ -183,9 +193,12 @@ pub fn parse_ident<'a>(
break; break;
} }
state = state.advance_without_indenting(bytes_parsed)?; state = state.advance_without_indenting(arena, bytes_parsed)?;
}
Err(reason) => {
let progress = Progress::from_lengths(start_bytes_len, state.bytes.len());
return state.fail(arena, progress, reason);
} }
Err(reason) => return state.fail(reason),
} }
} }
@ -241,7 +254,7 @@ pub fn parse_ident<'a>(
// We had neither capitalized nor noncapitalized parts, // We had neither capitalized nor noncapitalized parts,
// yet we made it this far. The only explanation is that this was // yet we made it this far. The only explanation is that this was
// a stray '.' drifting through the cosmos. // a stray '.' drifting through the cosmos.
return Err(unexpected(1, state, Attempting::Identifier)); return Err(unexpected(arena, 1, Attempting::Identifier, state));
} }
} }
} else if is_private_tag { } else if is_private_tag {
@ -255,7 +268,9 @@ pub fn parse_ident<'a>(
} }
}; };
Ok(((answer, None), state)) let progress = Progress::from_lengths(start_bytes_len, state.bytes.len());
debug_assert_eq!(progress, Progress::MadeProgress,);
Ok((Progress::MadeProgress, (answer, None), state))
} }
fn malformed<'a>( fn malformed<'a>(
@ -293,13 +308,14 @@ fn malformed<'a>(
break; break;
} }
state = state.advance_without_indenting(bytes_parsed)?; state = state.advance_without_indenting(arena, bytes_parsed)?;
} }
Err(reason) => return state.fail(reason), Err(reason) => return state.fail(arena, MadeProgress, reason),
} }
} }
Ok(( Ok((
MadeProgress,
(Ident::Malformed(full_string.into_bump_str()), next_char), (Ident::Malformed(full_string.into_bump_str()), next_char),
state, state,
)) ))
@ -308,9 +324,9 @@ fn malformed<'a>(
pub fn ident<'a>() -> impl Parser<'a, Ident<'a>> { pub fn ident<'a>() -> impl Parser<'a, Ident<'a>> {
move |arena: &'a Bump, state: State<'a>| { move |arena: &'a Bump, state: State<'a>| {
// Discard next_char; we don't need it. // Discard next_char; we don't need it.
let ((string, _), state) = parse_ident(arena, state)?; let (progress, (string, _), state) = parse_ident(arena, state)?;
Ok((string, state)) Ok((progress, string, state))
} }
} }
@ -323,19 +339,19 @@ where
let (first_letter, bytes_parsed) = match peek_utf8_char(&state) { let (first_letter, bytes_parsed) = match peek_utf8_char(&state) {
Ok((first_letter, bytes_parsed)) => { Ok((first_letter, bytes_parsed)) => {
if !pred(first_letter) { if !pred(first_letter) {
return Err(unexpected(0, state, Attempting::RecordFieldLabel)); return Err(unexpected(arena, 0, Attempting::RecordFieldLabel, state));
} }
(first_letter, bytes_parsed) (first_letter, bytes_parsed)
} }
Err(reason) => return state.fail(reason), Err(reason) => return state.fail(arena, NoProgress, reason),
}; };
let mut buf = String::with_capacity_in(1, arena); let mut buf = String::with_capacity_in(1, arena);
buf.push(first_letter); buf.push(first_letter);
state = state.advance_without_indenting(bytes_parsed)?; state = state.advance_without_indenting(arena, bytes_parsed)?;
while !state.bytes.is_empty() { while !state.bytes.is_empty() {
match peek_utf8_char(&state) { match peek_utf8_char(&state) {
@ -348,17 +364,17 @@ where
if ch.is_alphabetic() || ch.is_ascii_digit() { if ch.is_alphabetic() || ch.is_ascii_digit() {
buf.push(ch); buf.push(ch);
state = state.advance_without_indenting(bytes_parsed)?; state = state.advance_without_indenting(arena, bytes_parsed)?;
} else { } else {
// This is the end of the field. We're done! // This is the end of the field. We're done!
break; break;
} }
} }
Err(reason) => return state.fail(reason), Err(reason) => return state.fail(arena, MadeProgress, reason),
}; };
} }
Ok((buf.into_bump_str(), state)) Ok((MadeProgress, buf.into_bump_str(), state))
} }
} }
@ -368,9 +384,12 @@ where
/// * A named pattern match, e.g. "foo" in `foo =` or `foo ->` or `\foo ->` /// * A named pattern match, e.g. "foo" in `foo =` or `foo ->` or `\foo ->`
pub fn lowercase_ident<'a>() -> impl Parser<'a, &'a str> { pub fn lowercase_ident<'a>() -> impl Parser<'a, &'a str> {
move |arena, state| { move |arena, state| {
let (ident, state) = let (progress, ident, state) =
global_tag_or_ident(|first_char| first_char.is_lowercase()).parse(arena, state)?; global_tag_or_ident(|first_char| first_char.is_lowercase()).parse(arena, state)?;
// to parse a valid ident, progress must be made
debug_assert_eq!(progress, MadeProgress);
if (ident == keyword::IF) if (ident == keyword::IF)
|| (ident == keyword::THEN) || (ident == keyword::THEN)
|| (ident == keyword::ELSE) || (ident == keyword::ELSE)
@ -381,14 +400,12 @@ pub fn lowercase_ident<'a>() -> impl Parser<'a, &'a str> {
// TODO Calculate the correct region based on state // TODO Calculate the correct region based on state
let region = Region::zero(); let region = Region::zero();
Err(( Err((
Fail { MadeProgress,
reason: FailReason::ReservedKeyword(region), Bag::from_state(arena, &state, FailReason::ReservedKeyword(region)),
attempting: Attempting::Identifier,
},
state, state,
)) ))
} else { } else {
Ok((ident, state)) Ok((MadeProgress, ident, state))
} }
} }
} }

View file

@ -7,9 +7,10 @@ use crate::header::{
TypedIdent, TypedIdent,
}; };
use crate::ident::{lowercase_ident, unqualified_ident, uppercase_ident}; use crate::ident::{lowercase_ident, unqualified_ident, uppercase_ident};
use crate::parser::Progress::{self, *};
use crate::parser::{ use crate::parser::{
self, ascii_char, ascii_string, loc, optional, peek_utf8_char, peek_utf8_char_at, unexpected, self, ascii_char, ascii_string, backtrackable, end_of_file, loc, optional, peek_utf8_char,
unexpected_eof, Either, ParseResult, Parser, State, peek_utf8_char_at, unexpected, unexpected_eof, Either, ParseResult, Parser, State,
}; };
use crate::string_literal; use crate::string_literal;
use crate::type_annotation; use crate::type_annotation;
@ -95,16 +96,20 @@ pub fn parse_package_part<'a>(arena: &'a Bump, mut state: State<'a>) -> ParseRes
if ch == '-' || ch.is_ascii_alphanumeric() { if ch == '-' || ch.is_ascii_alphanumeric() {
part_buf.push(ch); part_buf.push(ch);
state = state.advance_without_indenting(bytes_parsed)?; state = state.advance_without_indenting(arena, bytes_parsed)?;
} else { } else {
return Ok((part_buf.into_bump_str(), state)); let progress = Progress::progress_when(!part_buf.is_empty());
return Ok((progress, part_buf.into_bump_str(), state));
} }
} }
Err(reason) => return state.fail(reason), Err(reason) => {
let progress = Progress::progress_when(!part_buf.is_empty());
return state.fail(arena, progress, reason);
}
} }
} }
Err(unexpected_eof(0, state.attempting, state)) Err(unexpected_eof(arena, state, 0))
} }
#[inline(always)] #[inline(always)]
@ -113,14 +118,14 @@ pub fn module_name<'a>() -> impl Parser<'a, ModuleName<'a>> {
match peek_utf8_char(&state) { match peek_utf8_char(&state) {
Ok((first_letter, bytes_parsed)) => { Ok((first_letter, bytes_parsed)) => {
if !first_letter.is_uppercase() { if !first_letter.is_uppercase() {
return Err(unexpected(0, state, Attempting::Module)); return Err(unexpected(arena, 0, Attempting::Module, state));
}; };
let mut buf = String::with_capacity_in(4, arena); let mut buf = String::with_capacity_in(4, arena);
buf.push(first_letter); buf.push(first_letter);
state = state.advance_without_indenting(bytes_parsed)?; state = state.advance_without_indenting(arena, bytes_parsed)?;
while !state.bytes.is_empty() { while !state.bytes.is_empty() {
match peek_utf8_char(&state) { match peek_utf8_char(&state) {
@ -131,7 +136,7 @@ pub fn module_name<'a>() -> impl Parser<'a, ModuleName<'a>> {
// * ASCII digits - e.g. `1` but not `¾`, both of which pass .is_numeric() // * ASCII digits - e.g. `1` but not `¾`, both of which pass .is_numeric()
// * A '.' separating module parts // * A '.' separating module parts
if ch.is_alphabetic() || ch.is_ascii_digit() { if ch.is_alphabetic() || ch.is_ascii_digit() {
state = state.advance_without_indenting(bytes_parsed)?; state = state.advance_without_indenting(arena, bytes_parsed)?;
buf.push(ch); buf.push(ch);
} else if ch == '.' { } else if ch == '.' {
@ -143,6 +148,7 @@ pub fn module_name<'a>() -> impl Parser<'a, ModuleName<'a>> {
buf.push(next); buf.push(next);
state = state.advance_without_indenting( state = state.advance_without_indenting(
arena,
bytes_parsed + next_bytes_parsed, bytes_parsed + next_bytes_parsed,
)?; )?;
} else { } else {
@ -151,25 +157,26 @@ pub fn module_name<'a>() -> impl Parser<'a, ModuleName<'a>> {
// There may be an identifier after this '.', // There may be an identifier after this '.',
// e.g. "baz" in `Foo.Bar.baz` // e.g. "baz" in `Foo.Bar.baz`
return Ok(( return Ok((
MadeProgress,
ModuleName::new(buf.into_bump_str()), ModuleName::new(buf.into_bump_str()),
state, state,
)); ));
} }
} }
Err(reason) => return state.fail(reason), Err(reason) => return state.fail(arena, MadeProgress, reason),
} }
} else { } else {
// This is the end of the module name. We're done! // This is the end of the module name. We're done!
break; break;
} }
} }
Err(reason) => return state.fail(reason), Err(reason) => return state.fail(arena, MadeProgress, reason),
} }
} }
Ok((ModuleName::new(buf.into_bump_str()), state)) Ok((MadeProgress, ModuleName::new(buf.into_bump_str()), state))
} }
Err(reason) => state.fail(reason), Err(reason) => state.fail(arena, MadeProgress, reason),
} }
} }
} }
@ -290,7 +297,8 @@ pub fn platform_header<'a>() -> impl Parser<'a, PlatformHeader<'a>> {
#[inline(always)] #[inline(always)]
pub fn module_defs<'a>() -> impl Parser<'a, Vec<'a, Located<Def<'a>>>> { pub fn module_defs<'a>() -> impl Parser<'a, Vec<'a, Located<Def<'a>>>> {
zero_or_more!(space0_around(loc(def(0)), 0)) // force that we pare until the end of the input
skip_second!(zero_or_more!(space0_around(loc(def(0)), 0)), end_of_file())
} }
struct ProvidesTo<'a> { struct ProvidesTo<'a> {
@ -307,7 +315,10 @@ struct ProvidesTo<'a> {
fn provides_to<'a>() -> impl Parser<'a, ProvidesTo<'a>> { fn provides_to<'a>() -> impl Parser<'a, ProvidesTo<'a>> {
map!( map!(
and!( and!(
and!(skip_second!(space1(1), ascii_string("provides")), space1(1)), and!(
skip_second!(backtrackable(space1(1)), ascii_string("provides")),
space1(1)
),
and!( and!(
collection!( collection!(
ascii_char(b'['), ascii_char(b'['),
@ -434,6 +445,7 @@ fn exposes_modules<'a>() -> impl Parser<
) )
} }
#[derive(Debug)]
struct Packages<'a> { struct Packages<'a> {
entries: Vec<'a, Located<PackageEntry<'a>>>, entries: Vec<'a, Located<PackageEntry<'a>>>,
@ -445,7 +457,10 @@ struct Packages<'a> {
fn packages<'a>() -> impl Parser<'a, Packages<'a>> { fn packages<'a>() -> impl Parser<'a, Packages<'a>> {
map!( map!(
and!( and!(
and!(skip_second!(space1(1), ascii_string("packages")), space1(1)), and!(
skip_second!(backtrackable(space1(1)), ascii_string("packages")),
space1(1)
),
collection!( collection!(
ascii_char(b'{'), ascii_char(b'{'),
loc!(package_entry()), loc!(package_entry()),
@ -473,7 +488,10 @@ fn imports<'a>() -> impl Parser<
), ),
> { > {
and!( and!(
and!(skip_second!(space1(1), ascii_string("imports")), space1(1)), and!(
skip_second!(backtrackable(space1(1)), ascii_string("imports")),
space1(1)
),
collection!( collection!(
ascii_char(b'['), ascii_char(b'['),
loc!(imports_entry()), loc!(imports_entry()),
@ -487,17 +505,17 @@ fn imports<'a>() -> impl Parser<
#[inline(always)] #[inline(always)]
fn effects<'a>() -> impl Parser<'a, Effects<'a>> { fn effects<'a>() -> impl Parser<'a, Effects<'a>> {
move |arena, state| { move |arena, state| {
let (spaces_before_effects_keyword, state) = let (_, spaces_before_effects_keyword, state) =
skip_second!(space1(0), ascii_string("effects")).parse(arena, state)?; skip_second!(space1(0), ascii_string("effects")).parse(arena, state)?;
let (spaces_after_effects_keyword, state) = space1(0).parse(arena, state)?; let (_, spaces_after_effects_keyword, state) = space1(0).parse(arena, state)?;
// e.g. `fx.` // e.g. `fx.`
let (type_shortname, state) = let (_, type_shortname, state) =
skip_second!(lowercase_ident(), ascii_char(b'.')).parse(arena, state)?; skip_second!(lowercase_ident(), ascii_char(b'.')).parse(arena, state)?;
let ((type_name, spaces_after_type_name), state) = let (_, (type_name, spaces_after_type_name), state) =
and!(uppercase_ident(), space1(0)).parse(arena, state)?; and!(uppercase_ident(), space1(0)).parse(arena, state)?;
let (entries, state) = collection!( let (_, entries, state) = collection!(
ascii_char(b'{'), ascii_char(b'{'),
loc!(typed_ident()), loc!(typed_ident()),
ascii_char(b','), ascii_char(b','),
@ -507,6 +525,7 @@ fn effects<'a>() -> impl Parser<'a, Effects<'a>> {
.parse(arena, state)?; .parse(arena, state)?;
Ok(( Ok((
MadeProgress,
Effects { Effects {
spaces_before_effects_keyword, spaces_before_effects_keyword,
spaces_after_effects_keyword, spaces_after_effects_keyword,
@ -524,11 +543,11 @@ fn effects<'a>() -> impl Parser<'a, Effects<'a>> {
fn typed_ident<'a>() -> impl Parser<'a, TypedIdent<'a>> { fn typed_ident<'a>() -> impl Parser<'a, TypedIdent<'a>> {
move |arena, state| { move |arena, state| {
// You must have a field name, e.g. "email" // You must have a field name, e.g. "email"
let (ident, state) = loc!(lowercase_ident()).parse(arena, state)?; let (_, ident, state) = loc!(lowercase_ident()).parse(arena, state)?;
let (spaces_before_colon, state) = space0(0).parse(arena, state)?; let (_, spaces_before_colon, state) = space0(0).parse(arena, state)?;
let (ann, state) = skip_first!( let (_, ann, state) = skip_first!(
ascii_char(b':'), ascii_char(b':'),
space0_before(type_annotation::located(0), 0) space0_before(type_annotation::located(0), 0)
) )
@ -539,6 +558,7 @@ fn typed_ident<'a>() -> impl Parser<'a, TypedIdent<'a>> {
// printLine : Str -> Effect {} // printLine : Str -> Effect {}
Ok(( Ok((
MadeProgress,
TypedIdent::Entry { TypedIdent::Entry {
ident, ident,
spaces_before_colon, spaces_before_colon,

View file

@ -1,22 +1,23 @@
use crate::ast::{Attempting, Base, Expr}; use crate::ast::{Attempting, Base, Expr};
use crate::parser::{parse_utf8, unexpected, unexpected_eof, ParseResult, Parser, State}; use crate::parser::{parse_utf8, unexpected, unexpected_eof, ParseResult, Parser, Progress, State};
use bumpalo::Bump;
use std::char; use std::char;
use std::str::from_utf8_unchecked; use std::str::from_utf8_unchecked;
pub fn number_literal<'a>() -> impl Parser<'a, Expr<'a>> { pub fn number_literal<'a>() -> impl Parser<'a, Expr<'a>> {
move |_arena, state: State<'a>| { move |arena, state: State<'a>| {
let bytes = &mut state.bytes.iter(); let bytes = &mut state.bytes.iter();
match bytes.next() { match bytes.next() {
Some(&first_byte) => { Some(&first_byte) => {
// Number literals must start with either an '-' or a digit. // Number literals must start with either an '-' or a digit.
if first_byte == b'-' || (first_byte as char).is_ascii_digit() { if first_byte == b'-' || (first_byte as char).is_ascii_digit() {
parse_number_literal(first_byte as char, bytes, state) parse_number_literal(first_byte as char, bytes, arena, state)
} else { } else {
Err(unexpected(1, state, Attempting::NumberLiteral)) Err(unexpected(arena, 1, Attempting::NumberLiteral, state))
} }
} }
None => Err(unexpected_eof(0, state.attempting, state)), None => Err(unexpected_eof(arena, state, 0)),
} }
} }
} }
@ -25,6 +26,7 @@ pub fn number_literal<'a>() -> impl Parser<'a, Expr<'a>> {
fn parse_number_literal<'a, I>( fn parse_number_literal<'a, I>(
first_ch: char, first_ch: char,
bytes: &mut I, bytes: &mut I,
arena: &'a Bump,
state: State<'a>, state: State<'a>,
) -> ParseResult<'a, Expr<'a>> ) -> ParseResult<'a, Expr<'a>>
where where
@ -42,9 +44,10 @@ where
for &next_byte in bytes { for &next_byte in bytes {
let err_unexpected = || { let err_unexpected = || {
Err(unexpected( Err(unexpected(
arena,
bytes_parsed, bytes_parsed,
state.clone(),
Attempting::NumberLiteral, Attempting::NumberLiteral,
state.clone(),
)) ))
}; };
@ -126,21 +129,23 @@ where
// we'll succeed with an appropriate Expr which records that. // we'll succeed with an appropriate Expr which records that.
match typ { match typ {
Num => Ok(( Num => Ok((
Progress::from_consumed(bytes_parsed),
// SAFETY: it's safe to use from_utf8_unchecked here, because we've // SAFETY: it's safe to use from_utf8_unchecked here, because we've
// already validated that this range contains only ASCII digits // already validated that this range contains only ASCII digits
Expr::Num(unsafe { from_utf8_unchecked(&state.bytes[0..bytes_parsed]) }), Expr::Num(unsafe { from_utf8_unchecked(&state.bytes[0..bytes_parsed]) }),
state.advance_without_indenting(bytes_parsed)?, state.advance_without_indenting(arena, bytes_parsed)?,
)), )),
Float => Ok(( Float => Ok((
Progress::from_consumed(bytes_parsed),
// SAFETY: it's safe to use from_utf8_unchecked here, because we've // SAFETY: it's safe to use from_utf8_unchecked here, because we've
// already validated that this range contains only ASCII digits // already validated that this range contains only ASCII digits
Expr::Float(unsafe { from_utf8_unchecked(&state.bytes[0..bytes_parsed]) }), Expr::Float(unsafe { from_utf8_unchecked(&state.bytes[0..bytes_parsed]) }),
state.advance_without_indenting(bytes_parsed)?, state.advance_without_indenting(arena, bytes_parsed)?,
)), )),
// For these we trim off the 0x/0o/0b part // For these we trim off the 0x/0o/0b part
Hex => from_base(Base::Hex, first_ch, bytes_parsed, state), Hex => from_base(Base::Hex, first_ch, bytes_parsed, arena, state),
Octal => from_base(Base::Octal, first_ch, bytes_parsed, state), Octal => from_base(Base::Octal, first_ch, bytes_parsed, arena, state),
Binary => from_base(Base::Binary, first_ch, bytes_parsed, state), Binary => from_base(Base::Binary, first_ch, bytes_parsed, arena, state),
} }
} }
@ -153,12 +158,13 @@ enum LiteralType {
Binary, Binary,
} }
fn from_base( fn from_base<'a>(
base: Base, base: Base,
first_ch: char, first_ch: char,
bytes_parsed: usize, bytes_parsed: usize,
state: State<'_>, arena: &'a Bump,
) -> ParseResult<'_, Expr<'_>> { state: State<'a>,
) -> ParseResult<'a, Expr<'a>> {
let is_negative = first_ch == '-'; let is_negative = first_ch == '-';
let bytes = if is_negative { let bytes = if is_negative {
&state.bytes[3..bytes_parsed] &state.bytes[3..bytes_parsed]
@ -168,13 +174,14 @@ fn from_base(
match parse_utf8(bytes) { match parse_utf8(bytes) {
Ok(string) => Ok(( Ok(string) => Ok((
Progress::from_consumed(bytes_parsed),
Expr::NonBase10Int { Expr::NonBase10Int {
is_negative, is_negative,
string, string,
base, base,
}, },
state.advance_without_indenting(bytes_parsed)?, state.advance_without_indenting(arena, bytes_parsed)?,
)), )),
Err(reason) => state.fail(reason), Err(reason) => state.fail(arena, Progress::from_consumed(bytes_parsed), reason),
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,8 @@
use crate::ast::{Attempting, EscapedChar, StrLiteral, StrSegment}; use crate::ast::{Attempting, EscapedChar, StrLiteral, StrSegment};
use crate::expr; use crate::expr;
use crate::parser::Progress::*;
use crate::parser::{ use crate::parser::{
allocated, ascii_char, ascii_hex_digits, loc, parse_utf8, unexpected, unexpected_eof, Fail, allocated, ascii_char, ascii_hex_digits, loc, parse_utf8, unexpected, unexpected_eof, Bag,
FailReason, ParseResult, Parser, State, FailReason, ParseResult, Parser, State,
}; };
use bumpalo::collections::vec::Vec; use bumpalo::collections::vec::Vec;
@ -17,16 +18,16 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>> {
match bytes.next() { match bytes.next() {
Some(&byte) => { Some(&byte) => {
if byte != b'"' { if byte != b'"' {
return Err(unexpected(0, state, Attempting::StrLiteral)); return Err(unexpected(arena, 0, Attempting::StrLiteral, state));
} }
} }
None => { None => {
return Err(unexpected_eof(0, Attempting::StrLiteral, state)); return Err(unexpected_eof(arena, state, 0));
} }
} }
// Advance past the opening quotation mark. // Advance past the opening quotation mark.
state = state.advance_without_indenting(1)?; state = state.advance_without_indenting(arena, 1)?;
// At the parsing stage we keep the entire raw string, because the formatter // At the parsing stage we keep the entire raw string, because the formatter
// needs the raw string. (For example, so it can "remember" whether you // needs the raw string. (For example, so it can "remember" whether you
@ -43,7 +44,7 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>> {
segments.push(StrSegment::EscapedChar($ch)); segments.push(StrSegment::EscapedChar($ch));
// Advance past the segment we just added // Advance past the segment we just added
state = state.advance_without_indenting(segment_parsed_bytes)?; state = state.advance_without_indenting(arena, segment_parsed_bytes)?;
// Reset the segment // Reset the segment
segment_parsed_bytes = 0; segment_parsed_bytes = 0;
@ -62,12 +63,12 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>> {
match parse_utf8(string_bytes) { match parse_utf8(string_bytes) {
Ok(string) => { Ok(string) => {
state = state.advance_without_indenting(string.len())?; state = state.advance_without_indenting(arena, string.len())?;
segments.push($transform(string)); segments.push($transform(string));
} }
Err(reason) => { Err(reason) => {
return state.fail(reason); return state.fail(arena, MadeProgress, reason);
} }
} }
} }
@ -101,7 +102,11 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>> {
} }
_ => { _ => {
// Advance 1 for the close quote // Advance 1 for the close quote
return Ok((PlainLine(""), state.advance_without_indenting(1)?)); return Ok((
MadeProgress,
PlainLine(""),
state.advance_without_indenting(arena, 1)?,
));
} }
} }
} else { } else {
@ -123,7 +128,11 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>> {
}; };
// Advance the state 1 to account for the closing `"` // Advance the state 1 to account for the closing `"`
return Ok((expr, state.advance_without_indenting(1)?)); return Ok((
MadeProgress,
expr,
state.advance_without_indenting(arena, 1)?,
));
}; };
} }
b'\n' => { b'\n' => {
@ -133,9 +142,10 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>> {
// it should make it easiest to debug; the file will be a giant // it should make it easiest to debug; the file will be a giant
// error starting from where the open quote appeared. // error starting from where the open quote appeared.
return Err(unexpected( return Err(unexpected(
arena,
state.bytes.len() - 1, state.bytes.len() - 1,
state,
Attempting::StrLiteral, Attempting::StrLiteral,
state,
)); ));
} }
b'\\' => { b'\\' => {
@ -153,7 +163,7 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>> {
match bytes.next() { match bytes.next() {
Some(b'(') => { Some(b'(') => {
// Advance past the `\(` before using the expr parser // Advance past the `\(` before using the expr parser
state = state.advance_without_indenting(2)?; state = state.advance_without_indenting(arena, 2)?;
let original_byte_count = state.bytes.len(); let original_byte_count = state.bytes.len();
@ -161,7 +171,7 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>> {
// Parse an arbitrary expression, then give a // Parse an arbitrary expression, then give a
// canonicalization error if that expression variant // canonicalization error if that expression variant
// is not allowed inside a string interpolation. // is not allowed inside a string interpolation.
let (loc_expr, new_state) = let (_progress, loc_expr, new_state) =
skip_second!(loc(allocated(expr::expr(0))), ascii_char(b')')) skip_second!(loc(allocated(expr::expr(0))), ascii_char(b')'))
.parse(arena, state)?; .parse(arena, state)?;
@ -178,14 +188,14 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>> {
} }
Some(b'u') => { Some(b'u') => {
// Advance past the `\u` before using the expr parser // Advance past the `\u` before using the expr parser
state = state.advance_without_indenting(2)?; state = state.advance_without_indenting(arena, 2)?;
let original_byte_count = state.bytes.len(); let original_byte_count = state.bytes.len();
// Parse the hex digits, surrounded by parens, then // Parse the hex digits, surrounded by parens, then
// give a canonicalization error if the digits form // give a canonicalization error if the digits form
// an invalid unicode code point. // an invalid unicode code point.
let (loc_digits, new_state) = between!( let (_progress, loc_digits, new_state) = between!(
ascii_char(b'('), ascii_char(b'('),
loc(ascii_hex_digits()), loc(ascii_hex_digits()),
ascii_char(b')') ascii_char(b')')
@ -223,9 +233,10 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>> {
// by either an open paren or else one of the // by either an open paren or else one of the
// escapable characters (\n, \t, \", \\, etc) // escapable characters (\n, \t, \", \\, etc)
return Err(unexpected( return Err(unexpected(
arena,
state.bytes.len() - 1, state.bytes.len() - 1,
state,
Attempting::StrLiteral, Attempting::StrLiteral,
state,
)); ));
} }
} }
@ -237,11 +248,7 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>> {
} }
// We ran out of characters before finding a closed quote // We ran out of characters before finding a closed quote
Err(unexpected_eof( Err(unexpected_eof(arena, state.clone(), state.bytes.len()))
state.bytes.len(),
Attempting::StrLiteral,
state.clone(),
))
} }
} }
@ -283,17 +290,19 @@ where
// Ok((StrLiteral::Block(lines.into_bump_slice()), state)) // Ok((StrLiteral::Block(lines.into_bump_slice()), state))
Err(( Err((
Fail { MadeProgress,
attempting: state.attempting, Bag::from_state(
reason: FailReason::NotYetImplemented(format!( arena,
&state,
FailReason::NotYetImplemented(format!(
"TODO parse this line in a block string: {:?}", "TODO parse this line in a block string: {:?}",
line line
)), )),
}, ),
state, state,
)) ))
} }
Err(reason) => state.fail(reason), Err(reason) => state.fail(arena, MadeProgress, reason),
}; };
} }
quotes_seen += 1; quotes_seen += 1;
@ -310,7 +319,7 @@ where
line_start = parsed_chars; line_start = parsed_chars;
} }
Err(reason) => { Err(reason) => {
return state.fail(reason); return state.fail(arena, MadeProgress, reason);
} }
} }
} }
@ -323,10 +332,5 @@ where
} }
// We ran out of characters before finding 3 closing quotes // We ran out of characters before finding 3 closing quotes
Err(unexpected_eof( Err(unexpected_eof(arena, state, parsed_chars))
parsed_chars,
// TODO custom BlockStrLiteral?
Attempting::StrLiteral,
state,
))
} }

View file

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

View file

@ -4,8 +4,10 @@ use crate::expr::{global_tag, private_tag};
use crate::ident::join_module_parts; use crate::ident::join_module_parts;
use crate::keyword; use crate::keyword;
use crate::parser::{ use crate::parser::{
allocated, ascii_char, ascii_string, not, optional, peek_utf8_char, unexpected, Either, Fail, allocated, ascii_char, ascii_string, not, optional, peek_utf8_char, unexpected, Bag, Either,
FailReason, ParseResult, Parser, State, FailReason, ParseResult, Parser,
Progress::{self, *},
State,
}; };
use bumpalo::collections::string::String; use bumpalo::collections::string::String;
use bumpalo::collections::vec::Vec; use bumpalo::collections::vec::Vec;
@ -30,7 +32,8 @@ macro_rules! tag_union {
), ),
optional( optional(
// This could be an open tag union, e.g. `[ Foo, Bar ]a` // This could be an open tag union, e.g. `[ Foo, Bar ]a`
move |arena, state| allocated(term($min_indent)).parse(arena, state) move |arena: &'a Bump, state: State<'a>| allocated(term($min_indent))
.parse(arena, state)
) )
), ),
|((tags, final_comments), ext): ( |((tags, final_comments), ext): (
@ -57,16 +60,19 @@ pub fn term<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>>
loc!(applied_type(min_indent)), loc!(applied_type(min_indent)),
loc!(parse_type_variable) loc!(parse_type_variable)
), ),
|a, s| {
optional( optional(
// Inline type annotation, e.g. [ Nil, Cons a (List a) ] as List a // Inline type annotation, e.g. [ Nil, Cons a (List a) ] as List a
and!( and!(
space1(min_indent), space1(min_indent),
skip_first!( skip_first!(
ascii_string(keyword::AS), crate::parser::keyword(keyword::AS, min_indent),
space1_before(term(min_indent), min_indent) space1_before(term(min_indent), min_indent)
) )
),
) )
) .parse(a, s)
}
), ),
|arena: &'a Bump, |arena: &'a Bump,
(loc_ann, opt_as): ( (loc_ann, opt_as): (
@ -95,10 +101,15 @@ fn loc_wildcard<'a>() -> impl Parser<'a, Located<TypeAnnotation<'a>>> {
}) })
} }
pub fn loc_applied_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>> { fn loc_applied_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>> {
skip_first!( skip_first!(
// Once we hit an "as", stop parsing args // Once we hit an "as", stop parsing args
not(ascii_string(keyword::AS)), // and roll back parsing of preceding spaces
not(and!(
space1(min_indent),
crate::parser::keyword(keyword::AS, min_indent)
)),
space1_before(
one_of!( one_of!(
loc_wildcard(), loc_wildcard(),
loc_parenthetical_type(min_indent), loc_parenthetical_type(min_indent),
@ -106,10 +117,16 @@ pub fn loc_applied_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnot
loc!(tag_union!(min_indent)), loc!(tag_union!(min_indent)),
loc!(parse_concrete_type), loc!(parse_concrete_type),
loc!(parse_type_variable) loc!(parse_type_variable)
),
min_indent
) )
) )
} }
fn loc_applied_args<'a>(min_indent: u16) -> impl Parser<'a, Vec<'a, Located<TypeAnnotation<'a>>>> {
zero_or_more!(loc_applied_arg(min_indent))
}
#[inline(always)] #[inline(always)]
fn loc_parenthetical_type<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>> { fn loc_parenthetical_type<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>> {
between!( between!(
@ -130,10 +147,7 @@ fn tag_type<'a>(min_indent: u16) -> impl Parser<'a, Tag<'a>> {
either!(loc!(private_tag()), loc!(global_tag())), either!(loc!(private_tag()), loc!(global_tag())),
// Optionally parse space-separated arguments for the constructor, // Optionally parse space-separated arguments for the constructor,
// e.g. `ok err` in `Result ok err` // e.g. `ok err` in `Result ok err`
zero_or_more!(space1_before( loc_applied_args(min_indent)
move |arena, state| loc_applied_arg(min_indent).parse(arena, state),
min_indent,
))
), ),
|(either_name, args): ( |(either_name, args): (
Either<Located<&'a str>, Located<&'a str>>, Either<Located<&'a str>, Located<&'a str>>,
@ -185,10 +199,7 @@ fn applied_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>> {
parse_concrete_type, parse_concrete_type,
// Optionally parse space-separated arguments for the constructor, // Optionally parse space-separated arguments for the constructor,
// e.g. `Str Float` in `Map Str Float` // e.g. `Str Float` in `Map Str Float`
zero_or_more!(space1_before( loc_applied_args(min_indent)
move |arena, state| loc_applied_arg(min_indent).parse(arena, state),
min_indent,
))
), ),
|(ctor, args): (TypeAnnotation<'a>, Vec<'a, Located<TypeAnnotation<'a>>>)| { |(ctor, args): (TypeAnnotation<'a>, Vec<'a, Located<TypeAnnotation<'a>>>)| {
match &ctor { match &ctor {
@ -210,8 +221,8 @@ fn applied_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>> {
fn expression<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>> { fn expression<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>> {
use crate::blankspace::space0; use crate::blankspace::space0;
move |arena, state: State<'a>| { move |arena, state: State<'a>| {
let (first, state) = space0_before(term(min_indent), min_indent).parse(arena, state)?; let (p1, first, state) = space0_before(term(min_indent), min_indent).parse(arena, state)?;
let (rest, state) = zero_or_more!(skip_first!( let (p2, rest, state) = zero_or_more!(skip_first!(
ascii_char(b','), ascii_char(b','),
space0_around(term(min_indent), min_indent) space0_around(term(min_indent), min_indent)
)) ))
@ -219,11 +230,11 @@ fn expression<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>
// TODO this space0 is dropped, so newlines just before the function arrow when there // TODO this space0 is dropped, so newlines just before the function arrow when there
// is only one argument are not seen by the formatter. Can we do better? // is only one argument are not seen by the formatter. Can we do better?
let (is_function, state) = let (p3, is_function, state) =
optional(skip_first!(space0(min_indent), ascii_string("->"))).parse(arena, state)?; optional(skip_first!(space0(min_indent), ascii_string("->"))).parse(arena, state)?;
if is_function.is_some() { if is_function.is_some() {
let (return_type, state) = let (p4, return_type, state) =
space0_before(term(min_indent), min_indent).parse(arena, state)?; space0_before(term(min_indent), min_indent).parse(arena, state)?;
// prepare arguments // prepare arguments
@ -236,18 +247,21 @@ fn expression<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>
region: return_type.region, region: return_type.region,
value: TypeAnnotation::Function(output, arena.alloc(return_type)), value: TypeAnnotation::Function(output, arena.alloc(return_type)),
}; };
Ok((result, state)) let progress = p1.or(p2).or(p3).or(p4);
Ok((progress, result, state))
} else { } else {
let progress = p1.or(p2).or(p3);
// if there is no function arrow, there cannot be more than 1 "argument" // if there is no function arrow, there cannot be more than 1 "argument"
if rest.is_empty() { if rest.is_empty() {
Ok((first, state)) Ok((progress, first, state))
} else { } else {
// e.g. `Int,Int` without an arrow and return type // e.g. `Int,Int` without an arrow and return type
let msg =
"TODO: Decide the correct error to return for 'Invalid function signature'"
.to_string();
Err(( Err((
Fail { progress,
attempting: state.attempting, Bag::from_state(arena, &state, FailReason::NotYetImplemented(msg)),
reason: FailReason::NotYetImplemented("TODO: Decide the correct error to return for 'Invalid function signature'".to_string()),
},
state, state,
)) ))
} }
@ -278,18 +292,20 @@ fn parse_concrete_type<'a>(
let mut part_buf = String::new_in(arena); // The current "part" (parts are dot-separated.) let mut part_buf = String::new_in(arena); // The current "part" (parts are dot-separated.)
let mut parts: Vec<&'a str> = Vec::new_in(arena); let mut parts: Vec<&'a str> = Vec::new_in(arena);
let start_bytes_len = state.bytes.len();
// Qualified types must start with a capitalized letter. // Qualified types must start with a capitalized letter.
match peek_utf8_char(&state) { match peek_utf8_char(&state) {
Ok((first_letter, bytes_parsed)) => { Ok((first_letter, bytes_parsed)) => {
if first_letter.is_alphabetic() && first_letter.is_uppercase() { if first_letter.is_alphabetic() && first_letter.is_uppercase() {
part_buf.push(first_letter); part_buf.push(first_letter);
} else { } else {
return Err(unexpected(0, state, Attempting::ConcreteType)); return Err(unexpected(arena, 0, Attempting::ConcreteType, state));
} }
state = state.advance_without_indenting(bytes_parsed)?; state = state.advance_without_indenting(arena, bytes_parsed)?;
} }
Err(reason) => return state.fail(reason), Err(reason) => return state.fail(arena, NoProgress, reason),
} }
let mut next_char = None; let mut next_char = None;
@ -333,9 +349,13 @@ fn parse_concrete_type<'a>(
break; break;
} }
state = state.advance_without_indenting(bytes_parsed)?; state = state.advance_without_indenting(arena, bytes_parsed)?;
}
Err(reason) => {
let progress = Progress::from_lengths(start_bytes_len, state.bytes.len());
return state.fail(arena, progress, reason);
} }
Err(reason) => return state.fail(reason),
} }
} }
@ -353,7 +373,7 @@ fn parse_concrete_type<'a>(
// We had neither capitalized nor noncapitalized parts, // We had neither capitalized nor noncapitalized parts,
// yet we made it this far. The only explanation is that this was // yet we made it this far. The only explanation is that this was
// a stray '.' drifting through the cosmos. // a stray '.' drifting through the cosmos.
return Err(unexpected(1, state, Attempting::Identifier)); return Err(unexpected(arena, 1, Attempting::Identifier, state));
} }
let answer = TypeAnnotation::Apply( let answer = TypeAnnotation::Apply(
@ -362,7 +382,8 @@ fn parse_concrete_type<'a>(
&[], &[],
); );
Ok((answer, state)) let progress = Progress::from_lengths(start_bytes_len, state.bytes.len());
Ok((progress, answer, state))
} }
fn parse_type_variable<'a>( fn parse_type_variable<'a>(
@ -371,18 +392,23 @@ fn parse_type_variable<'a>(
) -> ParseResult<'a, TypeAnnotation<'a>> { ) -> ParseResult<'a, TypeAnnotation<'a>> {
let mut buf = String::new_in(arena); let mut buf = String::new_in(arena);
let start_bytes_len = state.bytes.len();
match peek_utf8_char(&state) { match peek_utf8_char(&state) {
Ok((first_letter, bytes_parsed)) => { Ok((first_letter, bytes_parsed)) => {
// Type variables must start with a lowercase letter. // Type variables must start with a lowercase letter.
if first_letter.is_alphabetic() && first_letter.is_lowercase() { if first_letter.is_alphabetic() && first_letter.is_lowercase() {
buf.push(first_letter); buf.push(first_letter);
} else { } else {
return Err(unexpected(0, state, Attempting::TypeVariable)); return Err(unexpected(arena, 0, Attempting::TypeVariable, state));
} }
state = state.advance_without_indenting(bytes_parsed)?; state = state.advance_without_indenting(arena, bytes_parsed)?;
}
Err(reason) => {
let progress = Progress::from_lengths(start_bytes_len, state.bytes.len());
return state.fail(arena, progress, reason);
} }
Err(reason) => return state.fail(reason),
} }
while !state.bytes.is_empty() { while !state.bytes.is_empty() {
@ -399,15 +425,19 @@ fn parse_type_variable<'a>(
break; break;
} }
state = state.advance_without_indenting(bytes_parsed)?; state = state.advance_without_indenting(arena, bytes_parsed)?;
}
Err(reason) => {
let progress = Progress::from_lengths(start_bytes_len, state.bytes.len());
return state.fail(arena, progress, reason);
} }
Err(reason) => return state.fail(reason),
} }
} }
let answer = TypeAnnotation::BoundVariable(buf.into_bump_str()); let answer = TypeAnnotation::BoundVariable(buf.into_bump_str());
Ok((answer, state)) let progress = Progress::from_lengths(start_bytes_len, state.bytes.len());
Ok((progress, answer, state))
} }
fn malformed<'a>( fn malformed<'a>(
@ -416,6 +446,8 @@ fn malformed<'a>(
mut state: State<'a>, mut state: State<'a>,
parts: Vec<&'a str>, parts: Vec<&'a str>,
) -> ParseResult<'a, TypeAnnotation<'a>> { ) -> ParseResult<'a, TypeAnnotation<'a>> {
// assumption: progress was made to conclude that the annotation is malformed
// Reconstruct the original string that we've been parsing. // Reconstruct the original string that we've been parsing.
let mut full_string = String::new_in(arena); let mut full_string = String::new_in(arena);
@ -437,13 +469,14 @@ fn malformed<'a>(
break; break;
} }
state = state.advance_without_indenting(bytes_parsed)?; state = state.advance_without_indenting(arena, bytes_parsed)?;
} }
Err(reason) => return state.fail(reason), Err(reason) => return state.fail(arena, MadeProgress, reason),
} }
} }
Ok(( Ok((
MadeProgress,
TypeAnnotation::Malformed(full_string.into_bump_str()), TypeAnnotation::Malformed(full_string.into_bump_str()),
state, state,
)) ))

View file

@ -31,7 +31,7 @@ mod test_parse {
PackageName, PackageOrPath, PlatformHeader, To, PackageName, PackageOrPath, PlatformHeader, To,
}; };
use roc_parse::module::{app_header, interface_header, module_defs, platform_header}; use roc_parse::module::{app_header, interface_header, module_defs, platform_header};
use roc_parse::parser::{Fail, FailReason, Parser, State}; use roc_parse::parser::{FailReason, Parser, State};
use roc_parse::test_helpers::parse_expr_with; use roc_parse::test_helpers::parse_expr_with;
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
use std::{f64, i64}; use std::{f64, i64};
@ -43,12 +43,12 @@ mod test_parse {
assert_eq!(Ok(expected_expr), actual); assert_eq!(Ok(expected_expr), actual);
} }
fn assert_parsing_fails<'a>(input: &'a str, reason: FailReason, attempting: Attempting) { fn assert_parsing_fails<'a>(input: &'a str, _reason: FailReason, _attempting: Attempting) {
let arena = Bump::new(); let arena = Bump::new();
let actual = parse_expr_with(&arena, input); let actual = parse_expr_with(&arena, input);
let expected_fail = Fail { reason, attempting }; // let expected_fail = Fail { reason, attempting };
assert_eq!(Err(expected_fail), actual); assert!(actual.is_err());
} }
fn assert_segments<E: Fn(&Bump) -> Vec<'_, ast::StrSegment<'_>>>(input: &str, to_expected: E) { fn assert_segments<E: Fn(&Bump) -> Vec<'_, ast::StrSegment<'_>>>(input: &str, to_expected: E) {
@ -2410,8 +2410,11 @@ mod test_parse {
"# "#
); );
let actual = app_header() let actual = app_header()
.parse(&arena, State::new(src.as_bytes(), Attempting::Module)) .parse(
.map(|tuple| tuple.0); &arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -2448,8 +2451,11 @@ mod test_parse {
"# "#
); );
let actual = app_header() let actual = app_header()
.parse(&arena, State::new(src.as_bytes(), Attempting::Module)) .parse(
.map(|tuple| tuple.0); &arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -2499,9 +2505,13 @@ mod test_parse {
provides [ quicksort ] to base provides [ quicksort ] to base
"# "#
); );
let actual = app_header() let actual = app_header()
.parse(&arena, State::new(src.as_bytes(), Attempting::Module)) .parse(
.map(|tuple| tuple.0); &arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -2544,8 +2554,11 @@ mod test_parse {
let src = "platform rtfeldman/blah requires {} exposes [] packages {} imports [] provides [] effects fx.Blah {}"; let src = "platform rtfeldman/blah requires {} exposes [] packages {} imports [] provides [] effects fx.Blah {}";
let actual = platform_header() let actual = platform_header()
.parse(&arena, State::new(src.as_bytes(), Attempting::Module)) .parse(
.map(|tuple| tuple.0); &arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -2612,8 +2625,11 @@ mod test_parse {
"# "#
); );
let actual = platform_header() let actual = platform_header()
.parse(&arena, State::new(src.as_bytes(), Attempting::Module)) .parse(
.map(|tuple| tuple.0); &arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -2641,8 +2657,11 @@ mod test_parse {
"# "#
); );
let actual = interface_header() let actual = interface_header()
.parse(&arena, State::new(src.as_bytes(), Attempting::Module)) .parse(
.map(|tuple| tuple.0); &arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -2670,8 +2689,11 @@ mod test_parse {
"# "#
); );
let actual = interface_header() let actual = interface_header()
.parse(&arena, State::new(src.as_bytes(), Attempting::Module)) .parse(
.map(|tuple| tuple.0); &arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -2697,8 +2719,11 @@ mod test_parse {
"# "#
); );
let actual = module_defs() let actual = module_defs()
.parse(&arena, State::new(src.as_bytes(), Attempting::Module)) .parse(
.map(|tuple| tuple.0); &arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
// It should occur twice in the debug output - once for the pattern, // It should occur twice in the debug output - once for the pattern,
// and then again for the lookup. // and then again for the lookup.
@ -2745,6 +2770,7 @@ mod test_parse {
Located::new(2, 2, 0, 10, def2), Located::new(2, 2, 0, 10, def2),
Located::new(3, 3, 0, 13, def3), Located::new(3, 3, 0, 13, def3),
]; ];
let src = indoc!( let src = indoc!(
r#" r#"
foo = 1 foo = 1
@ -2753,13 +2779,97 @@ mod test_parse {
baz = "stuff" baz = "stuff"
"# "#
); );
let actual = module_defs() let actual = module_defs()
.parse(&arena, State::new(src.as_bytes(), Attempting::Module)) .parse(
.map(|tuple| tuple.0); &arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
#[test]
fn module_def_newline() {
use roc_parse::ast::Def::*;
let arena = Bump::new();
let src = indoc!(
r#"
main =
i = 64
i
"#
);
let actual = module_defs()
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
assert!(actual.is_ok());
}
#[test]
fn nested_def_annotation() {
use roc_parse::ast::Def::*;
let arena = Bump::new();
let src = indoc!(
r#"
main =
wrappedNotEq : a, a -> Bool
wrappedNotEq = \num1, num2 ->
num1 != num2
wrappedNotEq 2 3
"#
);
let actual = module_defs()
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
assert!(actual.is_ok());
}
#[test]
fn outdenting_newline_after_else() {
use roc_parse::ast::Def::*;
let arena = Bump::new();
// highlights a problem with the else branch demanding a newline after its expression
let src = indoc!(
r#"
main =
v = \y -> if x then y else z
1
"#
);
let actual = module_defs()
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
dbg!(&actual);
assert!(actual.is_ok());
}
#[test] #[test]
fn newline_after_equals() { fn newline_after_equals() {
// Regression test for https://github.com/rtfeldman/roc/issues/51 // Regression test for https://github.com/rtfeldman/roc/issues/51

View file

@ -12,7 +12,6 @@ roc_module = { path = "../module" }
roc_parse = { path = "../parse" } roc_parse = { path = "../parse" }
roc_problem = { path = "../problem" } roc_problem = { path = "../problem" }
roc_types = { path = "../types" } roc_types = { path = "../types" }
roc_load = { path = "../load" }
roc_can = { path = "../can" } roc_can = { path = "../can" }
roc_solve = { path = "../solve" } roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" } roc_mono = { path = "../mono" }

View file

@ -1,18 +1,69 @@
use roc_parse::parser::{Fail, FailReason}; use roc_parse::parser::{ContextItem, FailReason, ParseProblem};
use roc_region::all::Region;
use std::path::PathBuf; use std::path::PathBuf;
use crate::report::{Report, RocDocAllocator}; use crate::report::{Report, RocDocAllocator, RocDocBuilder};
use ven_pretty::DocAllocator; use ven_pretty::DocAllocator;
fn context<'a>(
alloc: &'a RocDocAllocator<'a>,
context_stack: &[ContextItem],
default: &'a str,
) -> RocDocBuilder<'a> {
match context_stack.last() {
Some(context_item) => {
// assign string to `Attempting`
use roc_parse::ast::Attempting::*;
match context_item.context {
Def => alloc.text("while parsing a definition"),
_ => {
// use the default
alloc.text(default)
}
}
}
None => {
// use the default
alloc.text(default)
}
}
}
pub fn parse_problem<'b>( pub fn parse_problem<'b>(
alloc: &'b RocDocAllocator<'b>, alloc: &'b RocDocAllocator<'b>,
filename: PathBuf, filename: PathBuf,
problem: Fail, starting_line: u32,
parse_problem: ParseProblem,
) -> Report<'b> { ) -> Report<'b> {
use FailReason::*; let line = starting_line + parse_problem.line;
let region = Region {
start_line: line,
end_line: line,
start_col: parse_problem.column,
end_col: parse_problem.column + 1,
};
match problem.reason { let report = |doc| Report {
ArgumentsBeforeEquals(region) => { filename: filename.clone(),
doc,
title: "PARSE PROBLEM".to_string(),
};
use FailReason::*;
match parse_problem.problem {
FailReason::ConditionFailed => {
let doc = alloc.stack(vec![
alloc.reflow("A condition failed:"),
alloc.region(region),
]);
Report {
filename,
doc,
title: "PARSE PROBLEM".to_string(),
}
}
FailReason::ArgumentsBeforeEquals(region) => {
let doc = alloc.stack(vec![ let doc = alloc.stack(vec![
alloc.reflow("Unexpected tokens in front of the `=` symbol:"), alloc.reflow("Unexpected tokens in front of the `=` symbol:"),
alloc.region(region), alloc.region(region),
@ -24,19 +75,22 @@ pub fn parse_problem<'b>(
title: "PARSE PROBLEM".to_string(), title: "PARSE PROBLEM".to_string(),
} }
} }
other => { Unexpected(mut region) => {
// if region.start_col == region.end_col {
// Unexpected(char, Region), region.end_col += 1;
// OutdentedTooFar, }
// ConditionFailed,
// LineTooLong(u32 /* which line was too long */), let doc = alloc.stack(vec![
// TooManyLines, alloc.concat(vec![
// Eof(Region), alloc.reflow("Unexpected token "),
// InvalidPattern, context(alloc, &parse_problem.context_stack, "here"),
// ReservedKeyword(Region), alloc.text(":"),
// ArgumentsBeforeEquals, ]),
//} alloc.region(region),
todo!("unhandled parse error: {:?}", other) ]);
}
report(doc)
}
_ => todo!("unhandled parse error: {:?}", parse_problem.problem),
} }
} }

View file

@ -526,6 +526,7 @@ impl<'a> RocDocAllocator<'a> {
if error_highlight_line { if error_highlight_line {
let highlight_text = let highlight_text =
ERROR_UNDERLINE.repeat((sub_region.end_col - sub_region.start_col) as usize); ERROR_UNDERLINE.repeat((sub_region.end_col - sub_region.start_col) as usize);
let highlight_line = self let highlight_line = self
.line() .line()
// Omit the gutter bar when we know there are no further // Omit the gutter bar when we know there are no further

View file

@ -13,7 +13,7 @@ use roc_constrain::module::{constrain_imported_values, Import};
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds}; use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds};
use roc_parse::ast::{self, Attempting}; use roc_parse::ast::{self, Attempting};
use roc_parse::blankspace::space0_before; use roc_parse::blankspace::space0_before;
use roc_parse::parser::{loc, Fail, Parser, State}; use roc_parse::parser::{loc, Bag, Parser, State};
use roc_problem::can::Problem; use roc_problem::can::Problem;
use roc_region::all::Located; use roc_region::all::Located;
use roc_solve::solve; use roc_solve::solve;
@ -85,24 +85,8 @@ where
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Expr<'a>, Fail> { pub fn can_expr<'a>(arena: &'a Bump, expr_str: &'a str) -> Result<CanExprOut, ParseErrOut<'a>> {
parse_loc_with(arena, input).map(|loc_expr| loc_expr.value) can_expr_with(arena, test_home(), expr_str)
}
#[allow(dead_code)]
pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Located<ast::Expr<'a>>, Fail> {
let state = State::new(input.as_bytes(), Attempting::Module);
let parser = space0_before(loc(roc_parse::expr::expr(0)), 0);
let answer = parser.parse(&arena, state);
answer
.map(|(loc_expr, _)| loc_expr)
.map_err(|(fail, _)| fail)
}
#[allow(dead_code)]
pub fn can_expr(expr_str: &str) -> Result<CanExprOut, ParseErrOut> {
can_expr_with(&Bump::new(), test_home(), expr_str)
} }
pub struct CanExprOut { pub struct CanExprOut {
@ -116,19 +100,38 @@ pub struct CanExprOut {
pub constraint: Constraint, pub constraint: Constraint,
} }
#[allow(dead_code)]
pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Expr<'a>, Bag<'a>> {
parse_loc_with(arena, input).map(|loc_expr| loc_expr.value)
}
#[allow(dead_code)]
pub fn parse_loc_with<'a>(
arena: &'a Bump,
input: &'a str,
) -> Result<Located<ast::Expr<'a>>, Bag<'a>> {
let state = State::new_in(arena, input.trim().as_bytes(), Attempting::Module);
let parser = space0_before(loc(roc_parse::expr::expr(0)), 0);
let answer = parser.parse(&arena, state);
answer
.map(|(_, loc_expr, _)| loc_expr)
.map_err(|(_, fail, _)| fail)
}
#[derive(Debug)] #[derive(Debug)]
pub struct ParseErrOut { pub struct ParseErrOut<'a> {
pub fail: Fail, pub fail: Bag<'a>,
pub home: ModuleId, pub home: ModuleId,
pub interns: Interns, pub interns: Interns,
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn can_expr_with( pub fn can_expr_with<'a>(
arena: &Bump, arena: &'a Bump,
home: ModuleId, home: ModuleId,
expr_str: &str, expr_str: &'a str,
) -> Result<CanExprOut, ParseErrOut> { ) -> Result<CanExprOut, ParseErrOut<'a>> {
let loc_expr = match parse_loc_with(&arena, expr_str) { let loc_expr = match parse_loc_with(&arena, expr_str) {
Ok(e) => e, Ok(e) => e,
Err(fail) => { Err(fail) => {

View file

@ -41,8 +41,9 @@ mod test_reporting {
} }
} }
fn infer_expr_help( fn infer_expr_help<'a>(
expr_src: &str, arena: &'a Bump,
expr_src: &'a str,
) -> Result< ) -> Result<
( (
Vec<solve::TypeError>, Vec<solve::TypeError>,
@ -51,7 +52,7 @@ mod test_reporting {
ModuleId, ModuleId,
Interns, Interns,
), ),
ParseErrOut, ParseErrOut<'a>,
> { > {
let CanExprOut { let CanExprOut {
loc_expr, loc_expr,
@ -63,7 +64,7 @@ mod test_reporting {
mut interns, mut interns,
problems: can_problems, problems: can_problems,
.. ..
} = can_expr(expr_src)?; } = can_expr(arena, expr_src)?;
let mut subs = Subs::new(var_store.into()); let mut subs = Subs::new(var_store.into());
for (var, name) in output.introduced_variables.name_by_var { for (var, name) in output.introduced_variables.name_by_var {
@ -108,7 +109,7 @@ mod test_reporting {
Ok((unify_problems, can_problems, mono_problems, home, interns)) Ok((unify_problems, can_problems, mono_problems, home, interns))
} }
fn list_reports<F>(src: &str, buf: &mut String, callback: F) fn list_reports<F>(arena: &Bump, src: &str, buf: &mut String, callback: F)
where where
F: FnOnce(RocDocBuilder<'_>, &mut String), F: FnOnce(RocDocBuilder<'_>, &mut String),
{ {
@ -118,7 +119,7 @@ mod test_reporting {
let filename = filename_from_string(r"\code\proj\Main.roc"); let filename = filename_from_string(r"\code\proj\Main.roc");
match infer_expr_help(src) { match infer_expr_help(arena, src) {
Err(parse_err) => { Err(parse_err) => {
let ParseErrOut { let ParseErrOut {
fail, fail,
@ -128,7 +129,8 @@ mod test_reporting {
let alloc = RocDocAllocator::new(&src_lines, home, &interns); let alloc = RocDocAllocator::new(&src_lines, home, &interns);
let doc = parse_problem(&alloc, filename, fail); 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) callback(doc.pretty(&alloc).append(alloc.line()), buf)
} }
@ -169,6 +171,7 @@ mod test_reporting {
fn report_problem_as(src: &str, expected_rendering: &str) { fn report_problem_as(src: &str, expected_rendering: &str) {
let mut buf: String = String::new(); let mut buf: String = String::new();
let arena = Bump::new();
let callback = |doc: RocDocBuilder<'_>, buf: &mut String| { let callback = |doc: RocDocBuilder<'_>, buf: &mut String| {
doc.1 doc.1
@ -176,13 +179,23 @@ mod test_reporting {
.expect("list_reports") .expect("list_reports")
}; };
list_reports(src, &mut buf, callback); list_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); assert_eq!(buf, expected_rendering);
} }
fn color_report_problem_as(src: &str, expected_rendering: &str) { fn color_report_problem_as(src: &str, expected_rendering: &str) {
let mut buf: String = String::new(); let mut buf: String = String::new();
let arena = Bump::new();
let callback = |doc: RocDocBuilder<'_>, buf: &mut String| { let callback = |doc: RocDocBuilder<'_>, buf: &mut String| {
doc.1 doc.1
@ -196,7 +209,7 @@ mod test_reporting {
.expect("list_reports") .expect("list_reports")
}; };
list_reports(src, &mut buf, callback); list_reports(&arena, src, &mut buf, callback);
let readable = human_readable(&buf); let readable = human_readable(&buf);
@ -572,8 +585,9 @@ mod test_reporting {
"# "#
); );
let arena = Bump::new();
let (_type_problems, _can_problems, _mono_problems, home, interns) = let (_type_problems, _can_problems, _mono_problems, home, interns) =
infer_expr_help(src).expect("parse error"); infer_expr_help(&arena, src).expect("parse error");
let mut buf = String::new(); let mut buf = String::new();
let src_lines: Vec<&str> = src.split('\n').collect(); let src_lines: Vec<&str> = src.split('\n').collect();
@ -602,8 +616,9 @@ mod test_reporting {
"# "#
); );
let arena = Bump::new();
let (_type_problems, _can_problems, _mono_problems, home, mut interns) = let (_type_problems, _can_problems, _mono_problems, home, mut interns) =
infer_expr_help(src).expect("parse error"); infer_expr_help(&arena, src).expect("parse error");
let mut buf = String::new(); let mut buf = String::new();
let src_lines: Vec<&str> = src.split('\n').collect(); let src_lines: Vec<&str> = src.split('\n').collect();
@ -3304,16 +3319,15 @@ mod test_reporting {
#[test] #[test]
fn float_out_of_range() { fn float_out_of_range() {
// have to deal with some whitespace issues because of the format! macro
report_problem_as( report_problem_as(
&format!( indoc!(
r#" r#"
overflow = 1{:e} overflow = 11.7976931348623157e308
underflow = -1{:e} underflow = -11.7976931348623157e308
overflow + underflow overflow + underflow
"#, "#
f64::MAX,
f64::MAX,
), ),
indoc!( indoc!(
r#" r#"
@ -3321,11 +3335,11 @@ mod test_reporting {
This float literal is too big: This float literal is too big:
2 overflow = 11.7976931348623157e308 1 overflow = 11.7976931348623157e308
^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^
Roc uses signed 64-bit floating points, allowing values Roc uses signed 64-bit floating points, allowing values between
between-1.7976931348623157e308 and 1.7976931348623157e308 -1.7976931348623157e308 and 1.7976931348623157e308
Tip: Learn more about number literals at TODO Tip: Learn more about number literals at TODO
@ -3333,11 +3347,11 @@ mod test_reporting {
This float literal is too small: This float literal is too small:
3 underflow = -11.7976931348623157e308 2 underflow = -11.7976931348623157e308
^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^
Roc uses signed 64-bit floating points, allowing values Roc uses signed 64-bit floating points, allowing values between
between-1.7976931348623157e308 and 1.7976931348623157e308 -1.7976931348623157e308 and 1.7976931348623157e308
Tip: Learn more about number literals at TODO Tip: Learn more about number literals at TODO
"# "#
@ -4011,4 +4025,83 @@ mod test_reporting {
), ),
) )
} }
#[test]
fn type_annotation_dubble_colon() {
report_problem_as(
indoc!(
r#"
f :: I64
f = 42
f
"#
),
indoc!(
r#"
PARSE PROBLEM
Unexpected token while parsing a definition:
1 f :: I64
^
"#
),
)
}
#[test]
fn double_equals_in_def() {
// NOTE: VERY BAD ERROR MESSAGE
//
// looks like `x y` are considered argument to the add, even though they are
// on a lower indentation level
report_problem_as(
indoc!(
r#"
x = 3
y =
x == 5
Num.add 1 2
x y
"#
),
indoc!(
r#"
TOO MANY ARGS
The `add` function expects 2 arguments, but it got 4 instead:
4 Num.add 1 2
^^^^^^^
Are there any missing commas? Or missing parentheses?
"#
),
)
}
#[test]
fn invalid_operator() {
// NOTE: VERY BAD ERROR MESSAGE
report_problem_as(
indoc!(
r#"
main =
5 ** 3
"#
),
indoc!(
r#"
PARSE PROBLEM
Unexpected token here:
2 5 ** 3
^
"#
),
)
}
} }

View file

@ -15,7 +15,7 @@ use roc_module::ident::Ident;
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol}; use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol};
use roc_parse::ast::{self, Attempting}; use roc_parse::ast::{self, Attempting};
use roc_parse::blankspace::space0_before; use roc_parse::blankspace::space0_before;
use roc_parse::parser::{loc, Fail, Parser, State}; use roc_parse::parser::{loc, Bag, Parser, State};
use roc_problem::can::Problem; use roc_problem::can::Problem;
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
use roc_solve::solve; use roc_solve::solve;
@ -87,19 +87,22 @@ where
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Expr<'a>, Fail> { pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Expr<'a>, Bag<'a>> {
parse_loc_with(arena, input).map(|loc_expr| loc_expr.value) parse_loc_with(arena, input).map(|loc_expr| loc_expr.value)
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Located<ast::Expr<'a>>, Fail> { pub fn parse_loc_with<'a>(
let state = State::new(input.trim().as_bytes(), Attempting::Module); arena: &'a Bump,
input: &'a str,
) -> Result<Located<ast::Expr<'a>>, Bag<'a>> {
let state = State::new_in(arena, input.trim().as_bytes(), Attempting::Module);
let parser = space0_before(loc(roc_parse::expr::expr(0)), 0); let parser = space0_before(loc(roc_parse::expr::expr(0)), 0);
let answer = parser.parse(&arena, state); let answer = parser.parse(&arena, state);
answer answer
.map(|(loc_expr, _)| loc_expr) .map(|(_, loc_expr, _)| loc_expr)
.map_err(|(fail, _)| fail) .map_err(|(_, fail, _)| fail)
} }
#[allow(dead_code)] #[allow(dead_code)]

View file

@ -854,7 +854,7 @@ mod solve_expr {
infer_eq( infer_eq(
indoc!( indoc!(
r#" r#"
\f -> (\a, b -> f b a), \f -> (\a, b -> f b a)
"# "#
), ),
"(a, b -> c) -> (b, a -> c)", "(a, b -> c) -> (b, a -> c)",

View file

@ -15,7 +15,7 @@ use roc_module::ident::Ident;
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol}; use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol};
use roc_parse::ast::{self, Attempting}; use roc_parse::ast::{self, Attempting};
use roc_parse::blankspace::space0_before; use roc_parse::blankspace::space0_before;
use roc_parse::parser::{loc, Fail, Parser, State}; use roc_parse::parser::{loc, Bag, Parser, State};
use roc_problem::can::Problem; use roc_problem::can::Problem;
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
use roc_solve::solve; use roc_solve::solve;
@ -87,19 +87,22 @@ where
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Expr<'a>, Fail> { pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Expr<'a>, Bag<'a>> {
parse_loc_with(arena, input).map(|loc_expr| loc_expr.value) parse_loc_with(arena, input).map(|loc_expr| loc_expr.value)
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Located<ast::Expr<'a>>, Fail> { pub fn parse_loc_with<'a>(
let state = State::new(input.trim().as_bytes(), Attempting::Module); arena: &'a Bump,
input: &'a str,
) -> Result<Located<ast::Expr<'a>>, Bag<'a>> {
let state = State::new_in(arena, input.trim().as_bytes(), Attempting::Module);
let parser = space0_before(loc(roc_parse::expr::expr(0)), 0); let parser = space0_before(loc(roc_parse::expr::expr(0)), 0);
let answer = parser.parse(&arena, state); let answer = parser.parse(&arena, state);
answer answer
.map(|(loc_expr, _)| loc_expr) .map(|(_, loc_expr, _)| loc_expr)
.map_err(|(fail, _)| fail) .map_err(|(_, fail, _)| fail)
} }
#[allow(dead_code)] #[allow(dead_code)]

View file

@ -20,7 +20,7 @@ use roc_parse::ast::StrLiteral;
use roc_parse::ast::{self, Attempting}; use roc_parse::ast::{self, Attempting};
use roc_parse::blankspace::space0_before; use roc_parse::blankspace::space0_before;
use roc_parse::expr::expr; use roc_parse::expr::expr;
use roc_parse::parser::{loc, Fail, Parser, State}; use roc_parse::parser::{loc, Bag, Parser, State};
use roc_problem::can::{Problem, RuntimeError}; use roc_problem::can::{Problem, RuntimeError};
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
use roc_types::subs::{VarStore, Variable}; use roc_types::subs::{VarStore, Variable};
@ -233,15 +233,15 @@ pub fn str_to_expr2<'a>(
env: &mut Env<'a>, env: &mut Env<'a>,
scope: &mut Scope, scope: &mut Scope,
region: Region, region: Region,
) -> Result<(Expr2, self::Output), Fail> { ) -> Result<(Expr2, self::Output), Bag<'a>> {
let state = State::new(input.trim().as_bytes(), Attempting::Module); let state = State::new_in(arena, input.trim().as_bytes(), Attempting::Module);
let parser = space0_before(loc(expr(0)), 0); let parser = space0_before(loc(expr(0)), 0);
let parse_res = parser.parse(&arena, state); let parse_res = parser.parse(&arena, state);
parse_res parse_res
.map(|(loc_expr, _)| arena.alloc(loc_expr.value)) .map(|(_, loc_expr, _)| arena.alloc(loc_expr.value))
.map(|loc_expr_val_ref| to_expr2(env, scope, loc_expr_val_ref, region)) .map(|loc_expr_val_ref| to_expr2(env, scope, loc_expr_val_ref, region))
.map_err(|(fail, _)| fail) .map_err(|(_, fail, _)| fail)
} }
pub fn to_expr2<'a>( pub fn to_expr2<'a>(

View file

@ -19,15 +19,15 @@ pub struct File<'a> {
} }
#[derive(Debug)] #[derive(Debug)]
pub enum ReadError { pub enum ReadError<'a> {
Read(std::io::Error), Read(std::io::Error),
ParseDefs(parser::Fail), ParseDefs(parser::Bag<'a>),
ParseHeader(parser::Fail), ParseHeader(parser::Bag<'a>),
DoesntHaveRocExtension, DoesntHaveRocExtension,
} }
impl<'a> File<'a> { impl<'a> File<'a> {
pub fn read(path: &'a Path, arena: &'a Bump) -> Result<File<'a>, ReadError> { pub fn read(path: &'a Path, arena: &'a Bump) -> Result<File<'a>, ReadError<'a>> {
if path.extension() != Some(OsStr::new("roc")) { if path.extension() != Some(OsStr::new("roc")) {
return Err(ReadError::DoesntHaveRocExtension); return Err(ReadError::DoesntHaveRocExtension);
} }
@ -36,23 +36,23 @@ impl<'a> File<'a> {
let allocation = arena.alloc(bytes); let allocation = arena.alloc(bytes);
let module_parse_state = parser::State::new(allocation, Attempting::Module); 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 parsed_module = roc_parse::module::header().parse(&arena, module_parse_state);
match parsed_module { match parsed_module {
Ok((module, state)) => { Ok((_, module, state)) => {
let parsed_defs = module_defs().parse(&arena, state); let parsed_defs = module_defs().parse(&arena, state);
match parsed_defs { match parsed_defs {
Ok((defs, _)) => Ok(File { Ok((_, defs, _)) => Ok(File {
path, path,
module_header: module, module_header: module,
content: defs, content: defs,
}), }),
Err((error, _)) => Err(ReadError::ParseDefs(error)), Err((_, error, _)) => Err(ReadError::ParseDefs(error)),
} }
} }
Err((error, _)) => Err(ReadError::ParseHeader(error)), Err((_, error, _)) => Err(ReadError::ParseHeader(error)),
} }
} }