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_problem",
"roc_region",
"roc_reporting",
"roc_solve",
"roc_types",
"roc_unify",
@ -3061,7 +3062,6 @@ dependencies = [
"roc_can",
"roc_collections",
"roc_constrain",
"roc_load",
"roc_module",
"roc_mono",
"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,
src_dir: PathBuf,
roc_file_path: PathBuf,
opt_level: OptLevel,
emit_debug_info: bool,
link_type: LinkType,
) -> Result<PathBuf, LoadingProblem> {
) -> Result<PathBuf, LoadingProblem<'a>> {
let compilation_start = SystemTime::now();
let arena = Bump::new();
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
// Step 1: compile the app and generate the .o file
@ -36,13 +36,14 @@ pub fn build_file(
// Release builds use uniqueness optimizations
let stdlib = match opt_level {
OptLevel::Normal => roc_builtins::std::standard_stdlib(),
OptLevel::Optimize => roc_builtins::std::standard_stdlib(),
OptLevel::Normal => arena.alloc(roc_builtins::std::standard_stdlib()),
OptLevel::Optimize => arena.alloc(roc_builtins::std::standard_stdlib()),
};
let loaded = roc_load::file::load_and_monomorphize(
&arena,
roc_file_path.clone(),
&stdlib,
stdlib,
src_dir.as_path(),
subs_by_module,
ptr_bytes,

View file

@ -1,10 +1,12 @@
#[macro_use]
extern crate clap;
use bumpalo::Bump;
use clap::ArgMatches;
use clap::{App, Arg};
use roc_build::link::LinkType;
use roc_gen::llvm::build::OptLevel;
use roc_load::file::LoadingProblem;
use std::io;
use std::path::Path;
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<()> {
let arena = Bump::new();
let filename = matches.value_of(FLAG_ROC_FILE).unwrap();
let opt_level = if matches.is_present(FLAG_OPTIMIZE) {
OptLevel::Optimize
} else {
@ -107,23 +111,33 @@ 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,
src_dir,
path,
opt_level,
emit_debug_info,
LinkType::Executable,
)
.expect("TODO gracefully handle build_file failing");
);
if run_after_build {
// Run the compiled app
Command::new(binary_path)
.spawn()
.unwrap_or_else(|err| panic!("Failed to run app after building it: {:?}", err))
.wait()
.expect("TODO gracefully handle block_on failing");
match res_binary_path {
Ok(binary_path) => {
if run_after_build {
// Run the compiled app
Command::new(binary_path)
.spawn()
.unwrap_or_else(|err| panic!("Failed to run app after building it: {:?}", err))
.wait()
.expect("TODO gracefully handle block_on failing");
}
}
Err(LoadingProblem::ParsingFailedReport(report)) => {
print!("{}", report);
}
Err(other) => {
panic!("build_file failed with error:\n{:?}", other);
}
}
Ok(())

View file

@ -1,12 +1,12 @@
use const_format::concatcp;
use gen::{gen_and_eval, ReplOutput};
use roc_gen::llvm::build::OptLevel;
use roc_parse::parser::{Fail, FailReason};
use roc_parse::parser::Bag;
use rustyline::error::ReadlineError;
use rustyline::validate::{self, ValidationContext, ValidationResult, Validator};
use rustyline::Editor;
use rustyline_derive::{Completer, Helper, Highlighter, Hinter};
use std::io::{self};
use std::io;
use target_lexicon::Triple;
const BLUE: &str = "\u{001b}[36m";
@ -148,10 +148,10 @@ pub fn main() -> io::Result<()> {
println!("{}", output);
pending_src.clear();
}
Err(Fail {
reason: FailReason::Eof(_),
..
}) => {}
// Err(Fail {
// reason: FailReason::Eof(_),
// ..
// }) => {}
Err(fail) => {
report_parse_error(fail);
pending_src.clear();
@ -191,11 +191,11 @@ pub fn main() -> io::Result<()> {
Ok(())
}
fn report_parse_error(fail: Fail) {
fn report_parse_error(fail: Bag<'_>) {
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 {
ReplOutput::NoProblems { expr, 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::{Newlines, Parens};
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 std::path::{Path, PathBuf};
use std::str::from_utf8_unchecked;
@ -18,7 +18,11 @@ pub enum ReplOutput {
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::{
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_parse::ast::{self, Attempting};
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_region::all::{Located, Region};
use roc_types::subs::{VarStore, Variable};
@ -21,19 +21,22 @@ pub fn test_home() -> ModuleId {
}
#[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)
}
#[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.trim().as_bytes(), Attempting::Module);
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)
.map(|(_, loc_expr, _)| loc_expr)
.map_err(|(_, fail, _)| fail)
}
#[allow(dead_code)]

View file

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

View file

@ -51,6 +51,9 @@ mod gen_num {
assert_evals_to!(
indoc!(
r#"
app "test" provides [ main ] to "./platform"
main =
i : I64
i = 64
@ -446,24 +449,6 @@ mod gen_num {
-1,
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]

View file

@ -18,6 +18,7 @@ roc_unify = { path = "../unify" }
roc_parse = { path = "../parse" }
roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" }
roc_reporting = { path = "../reporting" }
bumpalo = { version = "3.2", features = ["collections"] }
inlinable_string = "0.1"
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,
};
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_solve::module::SolvedModule;
use roc_solve::solve;
@ -762,6 +762,8 @@ enum Msg<'a> {
subs: Subs,
exposed_to_host: MutMap<Symbol, Variable>,
},
FailedToParse(ParseProblem<'a>),
}
#[derive(Debug)]
@ -968,20 +970,20 @@ enum WorkerMsg {
}
#[derive(Debug)]
pub enum LoadingProblem {
pub enum LoadingProblem<'a> {
FileProblem {
filename: PathBuf,
error: io::ErrorKind,
msg: &'static str,
},
ParsingFailed {
filename: PathBuf,
fail: Fail,
},
ParsingFailed(ParseProblem<'a>),
UnexpectedHeader(String),
MsgChannelDied,
ErrJoiningWorkerThreads,
TriedToImportAppModule,
/// a formatted report of parsing failure
ParsingFailedReport(String),
}
pub enum Phases {
@ -998,7 +1000,7 @@ fn enqueue_task<'a>(
injector: &Injector<BuildTask<'a>>,
listeners: &[Sender<WorkerMsg>],
task: BuildTask<'a>,
) -> Result<(), LoadingProblem> {
) -> Result<(), LoadingProblem<'a>> {
injector.push(task);
for listener in listeners {
@ -1010,14 +1012,14 @@ fn enqueue_task<'a>(
Ok(())
}
pub fn load_and_typecheck(
arena: &Bump,
pub fn load_and_typecheck<'a>(
arena: &'a Bump,
filename: PathBuf,
stdlib: &StdLib,
stdlib: &'a StdLib,
src_dir: &Path,
exposed_types: SubsByModule,
ptr_bytes: u32,
) -> Result<LoadedModule, LoadingProblem> {
) -> Result<LoadedModule, LoadingProblem<'a>> {
use LoadResult::*;
let load_start = LoadStart::from_path(arena, filename, stdlib.mode)?;
@ -1043,7 +1045,7 @@ pub fn load_and_monomorphize<'a>(
src_dir: &Path,
exposed_types: SubsByModule,
ptr_bytes: u32,
) -> Result<MonomorphizedModule<'a>, LoadingProblem> {
) -> Result<MonomorphizedModule<'a>, LoadingProblem<'a>> {
use LoadResult::*;
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,
exposed_types: SubsByModule,
ptr_bytes: u32,
) -> Result<MonomorphizedModule<'a>, LoadingProblem> {
) -> Result<MonomorphizedModule<'a>, LoadingProblem<'a>> {
use LoadResult::*;
let load_start = LoadStart::from_str(arena, filename, src, stdlib.mode)?;
@ -1101,7 +1103,7 @@ impl<'a> LoadStart<'a> {
arena: &'a Bump,
filename: PathBuf,
mode: Mode,
) -> Result<Self, LoadingProblem> {
) -> Result<Self, LoadingProblem<'a>> {
let arc_modules = Arc::new(Mutex::new(PackageModuleIds::default()));
let root_exposed_ident_ids = IdentIds::exposed_builtins(0);
let ident_ids_by_module = Arc::new(Mutex::new(root_exposed_ident_ids));
@ -1134,7 +1136,7 @@ impl<'a> LoadStart<'a> {
filename: PathBuf,
src: &'a str,
mode: Mode,
) -> Result<Self, LoadingProblem> {
) -> Result<Self, LoadingProblem<'a>> {
let arc_modules = Arc::new(Mutex::new(PackageModuleIds::default()));
let root_exposed_ident_ids = IdentIds::exposed_builtins(0);
let ident_ids_by_module = Arc::new(Mutex::new(root_exposed_ident_ids));
@ -1220,7 +1222,7 @@ fn load<'a>(
exposed_types: SubsByModule,
goal_phase: Phase,
ptr_bytes: u32,
) -> Result<LoadResult<'a>, LoadingProblem>
) -> Result<LoadResult<'a>, LoadingProblem<'a>>
where
{
let LoadStart {
@ -1310,7 +1312,7 @@ where
let injector = &injector;
// Record this thread's handle so the main thread can join it later.
thread_scope
let res_join_handle = thread_scope
.builder()
.stack_size(EXPANDED_STACK_SIZE)
.spawn(move |_| {
@ -1322,7 +1324,7 @@ where
// shut down the thread, so when the main thread
// blocks on joining with all the worker threads,
// it can finally exit too!
return;
return Ok(());
}
WorkerMsg::TaskAdded => {
// Find a task - either from this thread's queue,
@ -1335,14 +1337,26 @@ where
// added. In that case, do nothing, and keep waiting
// until we receive a Shutdown message.
if let Some(task) = find_task(&worker, injector, stealers) {
run_task(
let result = run_task(
task,
worker_arena,
src_dir,
msg_tx.clone(),
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
// outliving its enclosing function.
drop(worker_msg_rx);
})
.unwrap();
Ok(())
});
res_join_handle.unwrap();
}
let mut state = State {
@ -1440,6 +1457,51 @@ where
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 => {
// This is where most of the main thread's work gets done.
// Everything up to this point has been setting up the threading
@ -1468,7 +1530,7 @@ fn start_tasks<'a>(
state: &mut State<'a>,
injector: &Injector<BuildTask<'a>>,
worker_listeners: &'a [Sender<WorkerMsg>],
) -> Result<(), LoadingProblem> {
) -> Result<(), LoadingProblem<'a>> {
for (module_id, phase) in work {
for task in start_phase(module_id, phase, state) {
enqueue_task(&injector, worker_listeners, task)?
@ -1485,7 +1547,7 @@ fn update<'a>(
injector: &Injector<BuildTask<'a>>,
worker_listeners: &'a [Sender<WorkerMsg>],
arena: &'a Bump,
) -> Result<State<'a>, LoadingProblem> {
) -> Result<State<'a>, LoadingProblem<'a>> {
use self::Msg::*;
match msg {
@ -1942,6 +2004,9 @@ fn update<'a>(
Msg::FinishedAllSpecialization { .. } => {
unreachable!();
}
Msg::FailedToParse(_) => {
unreachable!();
}
}
}
@ -2064,7 +2129,7 @@ fn load_pkg_config<'a>(
module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
mode: Mode,
) -> Result<Msg<'a>, LoadingProblem> {
) -> Result<Msg<'a>, LoadingProblem<'a>> {
let module_start_time = SystemTime::now();
let filename = PathBuf::from(src_dir);
@ -2074,9 +2139,10 @@ fn load_pkg_config<'a>(
let file_io_duration = file_io_start.elapsed().unwrap();
match file {
Ok(bytes) => {
Ok(bytes_vec) => {
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 parse_header_duration = parse_start.elapsed().unwrap();
@ -2091,19 +2157,19 @@ fn load_pkg_config<'a>(
effect_module_timing.parse_header = parse_header_duration;
match parsed {
Ok((ast::Module::Interface { header }, _parse_state)) => {
Ok((_, ast::Module::Interface { header }, _parse_state)) => {
Err(LoadingProblem::UnexpectedHeader(format!(
"expected platform/package module, got Interface with header\n{:?}",
header
)))
}
Ok((ast::Module::App { header }, _parse_state)) => {
Ok((_, ast::Module::App { header }, _parse_state)) => {
Err(LoadingProblem::UnexpectedHeader(format!(
"expected platform/package module, got App with header\n{:?}",
header
)))
}
Ok((ast::Module::Platform { header }, parser_state)) => {
Ok((_, ast::Module::Platform { header }, parser_state)) => {
// make a Pkg-Config module that ultimately exposes `main` to the host
let pkg_config_module_msg = fabricate_pkg_config_module(
arena,
@ -2131,7 +2197,9 @@ fn load_pkg_config<'a>(
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>>>>,
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
mode: Mode,
) -> Result<(ModuleId, Msg<'a>), LoadingProblem> {
) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> {
let module_start_time = SystemTime::now();
let mut filename = PathBuf::new();
@ -2240,9 +2308,9 @@ fn parse_header<'a>(
mode: Mode,
src_bytes: &'a [u8],
start_time: SystemTime,
) -> Result<(ModuleId, Msg<'a>), LoadingProblem> {
) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> {
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 parse_header_duration = parse_start.elapsed().unwrap();
@ -2253,7 +2321,7 @@ fn parse_header<'a>(
module_timing.parse_header = parse_header_duration;
match parsed {
Ok((ast::Module::Interface { header }, parse_state)) => Ok(send_header(
Ok((_, ast::Module::Interface { header }, parse_state)) => Ok(send_header(
Located {
region: header.name.region,
value: ModuleNameEnum::Interface(header.name.value),
@ -2269,7 +2337,7 @@ fn parse_header<'a>(
ident_ids_by_module,
module_timing,
)),
Ok((ast::Module::App { header }, parse_state)) => {
Ok((_, ast::Module::App { header }, parse_state)) => {
let mut pkg_config_dir = filename.clone();
pkg_config_dir.pop();
@ -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,
&"",
module_ids,
@ -2376,7 +2444,9 @@ fn parse_header<'a>(
header,
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>>>,
module_start_time: SystemTime,
mode: Mode,
) -> Result<(ModuleId, Msg<'a>), LoadingProblem> {
) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> {
let file_io_start = SystemTime::now();
let file = fs::read(&filename);
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>>>,
module_start_time: SystemTime,
mode: Mode,
) -> Result<(ModuleId, Msg<'a>), LoadingProblem> {
) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> {
let file_io_start = SystemTime::now();
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>>>,
header: &PlatformHeader<'a>,
module_timing: ModuleTiming,
) -> Result<(ModuleId, Msg<'a>), LoadingProblem> {
) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> {
let provides: &'a [Located<ExposesEntry<'a, &'a str>>] =
header.provides.clone().into_bump_slice();
@ -3022,7 +3092,7 @@ fn fabricate_effects_module<'a>(
mode: Mode,
header: PlatformHeader<'a>,
module_timing: ModuleTiming,
) -> Result<(ModuleId, Msg<'a>), LoadingProblem> {
) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> {
let num_exposes = header.provides.len() + 1;
let mut exposed: Vec<Symbol> = Vec::with_capacity(num_exposes);
@ -3300,7 +3370,7 @@ fn canonicalize_and_constrain<'a>(
aliases: MutMap<Symbol, Alias>,
mode: Mode,
parsed: ParsedModule<'a>,
) -> Result<Msg<'a>, LoadingProblem> {
) -> Result<Msg<'a>, LoadingProblem<'a>> {
let canonicalize_start = SystemTime::now();
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 parse_start = SystemTime::now();
let parse_state = parser::State::new(&header.src, Attempting::Module);
let (parsed_defs, _) = module_defs()
.parse(&arena, parse_state)
.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.");
let parse_state = parser::State::new_in(arena, &header.src, Attempting::Module);
let parsed_defs = match module_defs().parse(&arena, parse_state) {
Ok((_, success, _state)) => success,
Err((_, fail, _)) => {
return Err(LoadingProblem::ParsingFailed(
fail.into_parse_problem(header.module_path, header.src),
));
}
};
let parsed_defs = parsed_defs.into_bump_slice();
@ -3767,7 +3842,7 @@ fn run_task<'a>(
src_dir: &Path,
msg_tx: MsgSender<'a>,
ptr_bytes: u32,
) -> Result<(), LoadingProblem> {
) -> Result<(), LoadingProblem<'a>> {
use BuildTask::*;
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_parse::ast::{self, Attempting};
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_region::all::{Located, Region};
use roc_solve::solve;
@ -62,19 +62,22 @@ where
}
#[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)
}
#[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.trim().as_bytes(), Attempting::Module);
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)
.map(|(_, loc_expr, _)| loc_expr)
.map_err(|(_, fail, _)| fail)
}
#[allow(dead_code)]

View file

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

View file

@ -15,7 +15,7 @@ use roc_module::ident::Ident;
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol};
use roc_parse::ast::{self, Attempting};
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_region::all::{Located, Region};
use roc_solve::solve;
@ -47,19 +47,22 @@ pub fn infer_expr(
}
#[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)
}
#[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);
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.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)
.map(|(_, loc_expr, _)| loc_expr)
.map_err(|(_, fail, _)| fail)
}
#[allow(dead_code)]

View file

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

View file

@ -1,8 +1,10 @@
use crate::ast::CommentOrNewline::{self, *};
use crate::ast::{Attempting, Spaceable};
use crate::parser::{
self, and, ascii_char, ascii_string, optional, parse_utf8, peek_utf8_char, then, unexpected,
unexpected_eof, FailReason, Parser, State,
self, and, ascii_char, ascii_string, backtrackable, optional, parse_utf8, peek_utf8_char, then,
unexpected, unexpected_eof, FailReason, Parser,
Progress::{self, *},
State,
};
use bumpalo::collections::string::String;
use bumpalo::collections::vec::Vec;
@ -130,7 +132,7 @@ where
P: 'a,
{
parser::map_with_arena(
and!(space1(min_indent), parser),
and!(backtrackable(space1(min_indent)), parser),
|arena, (space_list, loc_expr)| {
if space_list.is_empty() {
loc_expr
@ -215,9 +217,9 @@ enum LineState {
pub fn line_comment<'a>() -> impl Parser<'a, &'a str> {
then(
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 {
return Err(unexpected(3, state, Attempting::LineComment));
return Err(unexpected(arena, 3, Attempting::LineComment, state));
}
let mut length = 0;
@ -230,10 +232,10 @@ pub fn line_comment<'a>() -> impl Parser<'a, &'a str> {
}
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) {
Ok(comment_str) => Ok((comment_str, state)),
Err(reason) => state.fail(reason),
Ok(comment_str) => Ok((MadeProgress, comment_str, state)),
Err(reason) => state.fail(arena, MadeProgress, reason),
}
},
)
@ -241,9 +243,9 @@ pub fn line_comment<'a>() -> impl Parser<'a, &'a str> {
#[inline(always)]
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 {
return Ok(((), state));
return Ok((NoProgress, (), 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) {
Ok((' ', _)) => {
spaces_seen += 1;
state = state.advance_spaces(1)?;
state = state.advance_spaces(arena, 1)?;
if spaces_seen == spaces_expected {
return Ok(((), state));
return Ok((MadeProgress, (), state));
}
}
Ok(_) => {
return Err(unexpected(
arena,
spaces_seen.into(),
Attempting::TODO,
state.clone(),
state.attempting,
));
}
Err(FailReason::BadUtf8) => {
// 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(_) => {
if spaces_seen == 0 {
return Err(unexpected_eof(0, state.attempting, state));
return Err(unexpected_eof(arena, state, 0));
} else {
return Err(unexpected(
arena,
spaces_seen.into(),
Attempting::TODO,
state.clone(),
state.attempting,
));
}
}
@ -285,12 +290,13 @@ pub fn spaces_exactly<'a>(spaces_expected: u16) -> impl Parser<'a, ()> {
}
if spaces_seen == 0 {
Err(unexpected_eof(0, state.attempting, state))
Err(unexpected_eof(arena, state, 0))
} else {
Err(unexpected(
arena,
spaces_seen.into(),
state.clone(),
state.attempting,
Attempting::TODO,
state,
))
}
}
@ -310,6 +316,8 @@ fn spaces<'a>(
let mut state = state;
let mut any_newlines = false;
let start_bytes_len = state.bytes.len();
while !state.bytes.is_empty() {
match peek_utf8_char(&state) {
Ok((ch, utf8_len)) => {
@ -321,15 +329,17 @@ fn spaces<'a>(
' ' => {
// Don't check indentation here; it might not be enough
// indentation yet, but maybe it will be after more spaces happen!
state = state.advance_spaces(1)?;
state = state.advance_spaces(arena, 1)?;
}
'\r' => {
// Ignore carriage returns.
state = state.advance_spaces(1)?;
state = state.advance_spaces(arena, 1)?;
}
'\n' => {
// No need to check indentation because we're about to reset it anyway.
state = state.newline()?;
// don't need to check the indent here since we'll reset it
// anyway
state = state.newline(arena)?;
// Newlines only get added to the list when they're outside comments.
space_list.push(Newline);
@ -339,10 +349,14 @@ fn spaces<'a>(
'#' => {
// Check indentation to make sure we were indented enough
// before this comment began.
let progress =
Progress::from_lengths(start_bytes_len, state.bytes.len());
state = state
.check_indent(min_indent)
.map_err(|(fail, _)| (fail, original_state.clone()))?
.advance_without_indenting(1)?;
.check_indent(arena, min_indent)
.map_err(|(fail, _)| {
(progress, fail, original_state.clone())
})?
.advance_without_indenting(arena, 1)?;
// We're now parsing a line comment!
line_state = LineState::Comment;
@ -351,7 +365,7 @@ fn spaces<'a>(
return if require_at_least_one && bytes_parsed <= 1 {
// We've parsed 1 char and it was not a 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 {
// First make sure we were indented enough!
//
@ -360,13 +374,19 @@ fn spaces<'a>(
// It's actively important for correctness that we skip
// this check if there are no newlines, because otherwise
// we would have false positives for single-line defs.)
let progress = Progress::from_lengths(
start_bytes_len,
state.bytes.len(),
);
if any_newlines {
state = state
.check_indent(min_indent)
.map_err(|(fail, _)| (fail, original_state))?;
state = state.check_indent(arena, min_indent).map_err(
|(fail, _)| {
(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 {
' ' => {
// 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 {
match comment_line_buf.chars().next() {
@ -400,7 +420,7 @@ fn spaces<'a>(
}
}
'\n' => {
state = state.newline()?;
state = state.newline(arena)?;
match (comment_line_buf.len(), comment_line_buf.chars().next())
{
@ -425,7 +445,8 @@ fn spaces<'a>(
}
nonblank => {
// 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);
}
@ -435,12 +456,12 @@ fn spaces<'a>(
match ch {
' ' => {
// 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);
}
'\n' => {
state = state.newline()?;
state = state.newline(arena)?;
// This was a newline, so end this doc comment.
space_list.push(DocComment(comment_line_buf.into_bump_str()));
@ -449,7 +470,7 @@ fn spaces<'a>(
line_state = LineState::Normal;
}
nonblank => {
state = state.advance_without_indenting(utf8_len)?;
state = state.advance_without_indenting(arena, utf8_len)?;
comment_line_buf.push(nonblank);
}
@ -459,11 +480,12 @@ fn spaces<'a>(
}
Err(FailReason::BadUtf8) => {
// 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(_) => {
if require_at_least_one && bytes_parsed == 0 {
return Err(unexpected_eof(0, state.attempting, state));
return Err(unexpected_eof(arena, state, 0));
} else {
let space_slice = space_list.into_bump_slice();
@ -474,16 +496,18 @@ fn spaces<'a>(
// It's actively important for correctness that we skip
// this check if there are no newlines, because otherwise
// we would have false positives for single-line defs.)
let progress = Progress::from_lengths(start_bytes_len, state.bytes.len());
if any_newlines {
return Ok((
progress,
space_slice,
state
.check_indent(min_indent)
.map_err(|(fail, _)| (fail, original_state))?,
.check_indent(arena, min_indent)
.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 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 {
// First make sure we were indented enough!
//
@ -500,13 +524,14 @@ fn spaces<'a>(
// It's actively important for correctness that we skip
// this check if there are no newlines, because otherwise
// we would have false positives for single-line defs.)
let progress = Progress::from_lengths(start_bytes_len, state.bytes.len());
if any_newlines {
state = state
.check_indent(min_indent)
.map_err(|(fail, _)| (fail, original_state))?;
.check_indent(arena, min_indent)
.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::ident::lowercase_ident;
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 bumpalo::collections::Vec;
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`
//
// (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':')),
space0(1)
))
.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 {
Some((shorthand, spaces_after_shorthand)) => PackageEntry::Entry {
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::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::vec::Vec;
use bumpalo::Bump;
@ -78,6 +79,8 @@ pub fn parse_ident<'a>(
let is_accessor_fn;
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.
// If this starts with neither, it must be something else!
match peek_utf8_char(&state) {
@ -88,20 +91,20 @@ pub fn parse_ident<'a>(
is_capitalized = first_ch.is_uppercase();
is_accessor_fn = false;
state = state.advance_without_indenting(bytes_parsed)?;
state = state.advance_without_indenting(arena, bytes_parsed)?;
} else if first_ch == '.' {
is_capitalized = false;
is_accessor_fn = true;
state = state.advance_without_indenting(bytes_parsed)?;
state = state.advance_without_indenting(arena, bytes_parsed)?;
} 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!
match peek_utf8_char(&state) {
Ok((next_ch, next_bytes_parsed)) => {
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(next_ch);
@ -111,19 +114,26 @@ pub fn parse_ident<'a>(
is_accessor_fn = false;
} else {
return Err(unexpected(
arena,
bytes_parsed + next_bytes_parsed,
state,
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 {
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() {
@ -183,9 +193,12 @@ pub fn parse_ident<'a>(
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,
// yet we made it this far. The only explanation is that this was
// 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 {
@ -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>(
@ -293,13 +308,14 @@ fn malformed<'a>(
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((
MadeProgress,
(Ident::Malformed(full_string.into_bump_str()), next_char),
state,
))
@ -308,9 +324,9 @@ fn malformed<'a>(
pub fn ident<'a>() -> impl Parser<'a, Ident<'a>> {
move |arena: &'a Bump, state: State<'a>| {
// 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) {
Ok((first_letter, bytes_parsed)) => {
if !pred(first_letter) {
return Err(unexpected(0, state, Attempting::RecordFieldLabel));
return Err(unexpected(arena, 0, Attempting::RecordFieldLabel, state));
}
(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);
buf.push(first_letter);
state = state.advance_without_indenting(bytes_parsed)?;
state = state.advance_without_indenting(arena, bytes_parsed)?;
while !state.bytes.is_empty() {
match peek_utf8_char(&state) {
@ -348,17 +364,17 @@ where
if ch.is_alphabetic() || ch.is_ascii_digit() {
buf.push(ch);
state = state.advance_without_indenting(bytes_parsed)?;
state = state.advance_without_indenting(arena, bytes_parsed)?;
} else {
// This is the end of the field. We're done!
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 ->`
pub fn lowercase_ident<'a>() -> impl Parser<'a, &'a str> {
move |arena, state| {
let (ident, state) =
let (progress, ident, 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)
|| (ident == keyword::THEN)
|| (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
let region = Region::zero();
Err((
Fail {
reason: FailReason::ReservedKeyword(region),
attempting: Attempting::Identifier,
},
MadeProgress,
Bag::from_state(arena, &state, FailReason::ReservedKeyword(region)),
state,
))
} else {
Ok((ident, state))
Ok((MadeProgress, ident, state))
}
}
}

View file

@ -7,9 +7,10 @@ use crate::header::{
TypedIdent,
};
use crate::ident::{lowercase_ident, unqualified_ident, uppercase_ident};
use crate::parser::Progress::{self, *};
use crate::parser::{
self, ascii_char, ascii_string, loc, optional, peek_utf8_char, peek_utf8_char_at, unexpected,
unexpected_eof, Either, ParseResult, Parser, State,
self, ascii_char, ascii_string, backtrackable, end_of_file, loc, optional, peek_utf8_char,
peek_utf8_char_at, unexpected, unexpected_eof, Either, ParseResult, Parser, State,
};
use crate::string_literal;
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() {
part_buf.push(ch);
state = state.advance_without_indenting(bytes_parsed)?;
state = state.advance_without_indenting(arena, bytes_parsed)?;
} 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)]
@ -113,14 +118,14 @@ pub fn module_name<'a>() -> impl Parser<'a, ModuleName<'a>> {
match peek_utf8_char(&state) {
Ok((first_letter, bytes_parsed)) => {
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);
buf.push(first_letter);
state = state.advance_without_indenting(bytes_parsed)?;
state = state.advance_without_indenting(arena, bytes_parsed)?;
while !state.bytes.is_empty() {
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()
// * A '.' separating module parts
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);
} else if ch == '.' {
@ -143,6 +148,7 @@ pub fn module_name<'a>() -> impl Parser<'a, ModuleName<'a>> {
buf.push(next);
state = state.advance_without_indenting(
arena,
bytes_parsed + next_bytes_parsed,
)?;
} else {
@ -151,25 +157,26 @@ pub fn module_name<'a>() -> impl Parser<'a, ModuleName<'a>> {
// There may be an identifier after this '.',
// e.g. "baz" in `Foo.Bar.baz`
return Ok((
MadeProgress,
ModuleName::new(buf.into_bump_str()),
state,
));
}
}
Err(reason) => return state.fail(reason),
Err(reason) => return state.fail(arena, MadeProgress, reason),
}
} else {
// This is the end of the module name. We're done!
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)]
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> {
@ -307,7 +315,10 @@ struct ProvidesTo<'a> {
fn provides_to<'a>() -> impl Parser<'a, ProvidesTo<'a>> {
map!(
and!(
and!(skip_second!(space1(1), ascii_string("provides")), space1(1)),
and!(
skip_second!(backtrackable(space1(1)), ascii_string("provides")),
space1(1)
),
and!(
collection!(
ascii_char(b'['),
@ -434,6 +445,7 @@ fn exposes_modules<'a>() -> impl Parser<
)
}
#[derive(Debug)]
struct Packages<'a> {
entries: Vec<'a, Located<PackageEntry<'a>>>,
@ -445,7 +457,10 @@ struct Packages<'a> {
fn packages<'a>() -> impl Parser<'a, Packages<'a>> {
map!(
and!(
and!(skip_second!(space1(1), ascii_string("packages")), space1(1)),
and!(
skip_second!(backtrackable(space1(1)), ascii_string("packages")),
space1(1)
),
collection!(
ascii_char(b'{'),
loc!(package_entry()),
@ -473,7 +488,10 @@ fn imports<'a>() -> impl Parser<
),
> {
and!(
and!(skip_second!(space1(1), ascii_string("imports")), space1(1)),
and!(
skip_second!(backtrackable(space1(1)), ascii_string("imports")),
space1(1)
),
collection!(
ascii_char(b'['),
loc!(imports_entry()),
@ -487,17 +505,17 @@ fn imports<'a>() -> impl Parser<
#[inline(always)]
fn effects<'a>() -> impl Parser<'a, Effects<'a>> {
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)?;
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.`
let (type_shortname, state) =
let (_, type_shortname, 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)?;
let (entries, state) = collection!(
let (_, entries, state) = collection!(
ascii_char(b'{'),
loc!(typed_ident()),
ascii_char(b','),
@ -507,6 +525,7 @@ fn effects<'a>() -> impl Parser<'a, Effects<'a>> {
.parse(arena, state)?;
Ok((
MadeProgress,
Effects {
spaces_before_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>> {
move |arena, state| {
// 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':'),
space0_before(type_annotation::located(0), 0)
)
@ -539,6 +558,7 @@ fn typed_ident<'a>() -> impl Parser<'a, TypedIdent<'a>> {
// printLine : Str -> Effect {}
Ok((
MadeProgress,
TypedIdent::Entry {
ident,
spaces_before_colon,

View file

@ -1,22 +1,23 @@
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::str::from_utf8_unchecked;
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();
match bytes.next() {
Some(&first_byte) => {
// Number literals must start with either an '-' or a 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 {
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>(
first_ch: char,
bytes: &mut I,
arena: &'a Bump,
state: State<'a>,
) -> ParseResult<'a, Expr<'a>>
where
@ -42,9 +44,10 @@ where
for &next_byte in bytes {
let err_unexpected = || {
Err(unexpected(
arena,
bytes_parsed,
state.clone(),
Attempting::NumberLiteral,
state.clone(),
))
};
@ -126,21 +129,23 @@ where
// we'll succeed with an appropriate Expr which records that.
match typ {
Num => Ok((
Progress::from_consumed(bytes_parsed),
// SAFETY: it's safe to use from_utf8_unchecked here, because we've
// already validated that this range contains only ASCII digits
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((
Progress::from_consumed(bytes_parsed),
// SAFETY: it's safe to use from_utf8_unchecked here, because we've
// already validated that this range contains only ASCII digits
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
Hex => from_base(Base::Hex, first_ch, bytes_parsed, state),
Octal => from_base(Base::Octal, first_ch, bytes_parsed, state),
Binary => from_base(Base::Binary, 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, arena, state),
Binary => from_base(Base::Binary, first_ch, bytes_parsed, arena, state),
}
}
@ -153,12 +158,13 @@ enum LiteralType {
Binary,
}
fn from_base(
fn from_base<'a>(
base: Base,
first_ch: char,
bytes_parsed: usize,
state: State<'_>,
) -> ParseResult<'_, Expr<'_>> {
arena: &'a Bump,
state: State<'a>,
) -> ParseResult<'a, Expr<'a>> {
let is_negative = first_ch == '-';
let bytes = if is_negative {
&state.bytes[3..bytes_parsed]
@ -168,13 +174,14 @@ fn from_base(
match parse_utf8(bytes) {
Ok(string) => Ok((
Progress::from_consumed(bytes_parsed),
Expr::NonBase10Int {
is_negative,
string,
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::expr;
use crate::parser::Progress::*;
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,
};
use bumpalo::collections::vec::Vec;
@ -17,16 +18,16 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>> {
match bytes.next() {
Some(&byte) => {
if byte != b'"' {
return Err(unexpected(0, state, Attempting::StrLiteral));
return Err(unexpected(arena, 0, Attempting::StrLiteral, state));
}
}
None => {
return Err(unexpected_eof(0, Attempting::StrLiteral, state));
return Err(unexpected_eof(arena, state, 0));
}
}
// 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
// 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));
// 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
segment_parsed_bytes = 0;
@ -62,12 +63,12 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>> {
match parse_utf8(string_bytes) {
Ok(string) => {
state = state.advance_without_indenting(string.len())?;
state = state.advance_without_indenting(arena, string.len())?;
segments.push($transform(string));
}
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
return Ok((PlainLine(""), state.advance_without_indenting(1)?));
return Ok((
MadeProgress,
PlainLine(""),
state.advance_without_indenting(arena, 1)?,
));
}
}
} else {
@ -123,7 +128,11 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>> {
};
// 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' => {
@ -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
// error starting from where the open quote appeared.
return Err(unexpected(
arena,
state.bytes.len() - 1,
state,
Attempting::StrLiteral,
state,
));
}
b'\\' => {
@ -153,7 +163,7 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>> {
match bytes.next() {
Some(b'(') => {
// 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();
@ -161,7 +171,7 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>> {
// Parse an arbitrary expression, then give a
// canonicalization error if that expression variant
// 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')'))
.parse(arena, state)?;
@ -178,14 +188,14 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>> {
}
Some(b'u') => {
// 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();
// Parse the hex digits, surrounded by parens, then
// give a canonicalization error if the digits form
// an invalid unicode code point.
let (loc_digits, new_state) = between!(
let (_progress, loc_digits, new_state) = between!(
ascii_char(b'('),
loc(ascii_hex_digits()),
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
// escapable characters (\n, \t, \", \\, etc)
return Err(unexpected(
arena,
state.bytes.len() - 1,
state,
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
Err(unexpected_eof(
state.bytes.len(),
Attempting::StrLiteral,
state.clone(),
))
Err(unexpected_eof(arena, state.clone(), state.bytes.len()))
}
}
@ -283,17 +290,19 @@ where
// Ok((StrLiteral::Block(lines.into_bump_slice()), state))
Err((
Fail {
attempting: state.attempting,
reason: FailReason::NotYetImplemented(format!(
MadeProgress,
Bag::from_state(
arena,
&state,
FailReason::NotYetImplemented(format!(
"TODO parse this line in a block string: {:?}",
line
)),
},
),
state,
))
}
Err(reason) => state.fail(reason),
Err(reason) => state.fail(arena, MadeProgress, reason),
};
}
quotes_seen += 1;
@ -310,7 +319,7 @@ where
line_start = parsed_chars;
}
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
Err(unexpected_eof(
parsed_chars,
// TODO custom BlockStrLiteral?
Attempting::StrLiteral,
state,
))
Err(unexpected_eof(arena, state, parsed_chars))
}

View file

@ -2,44 +2,47 @@ use crate::ast::{self, Attempting};
use crate::blankspace::space0_before;
use crate::expr::expr;
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::Bump;
use roc_region::all::Located;
#[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)
}
pub fn parse_header_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Module<'a>, Fail> {
let state = State::new(input.trim().as_bytes(), Attempting::Module);
pub fn parse_header_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Module<'a>, Bag<'a>> {
let state = State::new_in(arena, input.trim().as_bytes(), Attempting::Module);
let answer = header().parse(arena, state);
answer
.map(|(loc_expr, _)| loc_expr)
.map_err(|(fail, _)| fail)
.map(|(_, loc_expr, _)| loc_expr)
.map_err(|(_, fail, _)| fail)
}
#[allow(dead_code)]
pub fn parse_defs_with<'a>(
arena: &'a Bump,
input: &'a str,
) -> Result<Vec<'a, Located<ast::Def<'a>>>, Fail> {
let state = State::new(input.trim().as_bytes(), Attempting::Module);
) -> Result<Vec<'a, Located<ast::Def<'a>>>, Bag<'a>> {
let state = State::new_in(arena, input.trim().as_bytes(), Attempting::Module);
let answer = module_defs().parse(arena, state);
answer
.map(|(loc_expr, _)| loc_expr)
.map_err(|(fail, _)| fail)
.map(|(_, loc_expr, _)| loc_expr)
.map_err(|(_, fail, _)| fail)
}
#[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.trim().as_bytes(), Attempting::Module);
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(expr(0)), 0);
let answer = parser.parse(&arena, state);
answer
.map(|(loc_expr, _)| loc_expr)
.map_err(|(fail, _)| fail)
.map(|(_, loc_expr, _)| loc_expr)
.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::keyword;
use crate::parser::{
allocated, ascii_char, ascii_string, not, optional, peek_utf8_char, unexpected, Either, Fail,
FailReason, ParseResult, Parser, State,
allocated, ascii_char, ascii_string, not, optional, peek_utf8_char, unexpected, Bag, Either,
FailReason, ParseResult, Parser,
Progress::{self, *},
State,
};
use bumpalo::collections::string::String;
use bumpalo::collections::vec::Vec;
@ -30,7 +32,8 @@ macro_rules! tag_union {
),
optional(
// 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): (
@ -57,16 +60,19 @@ pub fn term<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>>
loc!(applied_type(min_indent)),
loc!(parse_type_variable)
),
optional(
// Inline type annotation, e.g. [ Nil, Cons a (List a) ] as List a
and!(
space1(min_indent),
skip_first!(
ascii_string(keyword::AS),
space1_before(term(min_indent), min_indent)
)
|a, s| {
optional(
// Inline type annotation, e.g. [ Nil, Cons a (List a) ] as List a
and!(
space1(min_indent),
skip_first!(
crate::parser::keyword(keyword::AS, min_indent),
space1_before(term(min_indent), min_indent)
)
),
)
)
.parse(a, s)
}
),
|arena: &'a Bump,
(loc_ann, opt_as): (
@ -95,21 +101,32 @@ 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!(
// Once we hit an "as", stop parsing args
not(ascii_string(keyword::AS)),
one_of!(
loc_wildcard(),
loc_parenthetical_type(min_indent),
loc!(record_type(min_indent)),
loc!(tag_union!(min_indent)),
loc!(parse_concrete_type),
loc!(parse_type_variable)
// and roll back parsing of preceding spaces
not(and!(
space1(min_indent),
crate::parser::keyword(keyword::AS, min_indent)
)),
space1_before(
one_of!(
loc_wildcard(),
loc_parenthetical_type(min_indent),
loc!(record_type(min_indent)),
loc!(tag_union!(min_indent)),
loc!(parse_concrete_type),
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)]
fn loc_parenthetical_type<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>> {
between!(
@ -130,10 +147,7 @@ fn tag_type<'a>(min_indent: u16) -> impl Parser<'a, Tag<'a>> {
either!(loc!(private_tag()), loc!(global_tag())),
// Optionally parse space-separated arguments for the constructor,
// e.g. `ok err` in `Result ok err`
zero_or_more!(space1_before(
move |arena, state| loc_applied_arg(min_indent).parse(arena, state),
min_indent,
))
loc_applied_args(min_indent)
),
|(either_name, args): (
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,
// Optionally parse space-separated arguments for the constructor,
// e.g. `Str Float` in `Map Str Float`
zero_or_more!(space1_before(
move |arena, state| loc_applied_arg(min_indent).parse(arena, state),
min_indent,
))
loc_applied_args(min_indent)
),
|(ctor, args): (TypeAnnotation<'a>, Vec<'a, Located<TypeAnnotation<'a>>>)| {
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>>> {
use crate::blankspace::space0;
move |arena, state: State<'a>| {
let (first, state) = space0_before(term(min_indent), min_indent).parse(arena, state)?;
let (rest, state) = zero_or_more!(skip_first!(
let (p1, first, state) = space0_before(term(min_indent), min_indent).parse(arena, state)?;
let (p2, rest, state) = zero_or_more!(skip_first!(
ascii_char(b','),
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
// 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)?;
if is_function.is_some() {
let (return_type, state) =
let (p4, return_type, state) =
space0_before(term(min_indent), min_indent).parse(arena, state)?;
// prepare arguments
@ -236,18 +247,21 @@ fn expression<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>
region: return_type.region,
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 {
let progress = p1.or(p2).or(p3);
// if there is no function arrow, there cannot be more than 1 "argument"
if rest.is_empty() {
Ok((first, state))
Ok((progress, first, state))
} else {
// 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((
Fail {
attempting: state.attempting,
reason: FailReason::NotYetImplemented("TODO: Decide the correct error to return for 'Invalid function signature'".to_string()),
},
progress,
Bag::from_state(arena, &state, FailReason::NotYetImplemented(msg)),
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 parts: Vec<&'a str> = Vec::new_in(arena);
let start_bytes_len = state.bytes.len();
// Qualified types must start with a capitalized letter.
match peek_utf8_char(&state) {
Ok((first_letter, bytes_parsed)) => {
if first_letter.is_alphabetic() && first_letter.is_uppercase() {
part_buf.push(first_letter);
} 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;
@ -333,9 +349,13 @@ fn parse_concrete_type<'a>(
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,
// yet we made it this far. The only explanation is that this was
// 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(
@ -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>(
@ -371,18 +392,23 @@ fn parse_type_variable<'a>(
) -> ParseResult<'a, TypeAnnotation<'a>> {
let mut buf = String::new_in(arena);
let start_bytes_len = state.bytes.len();
match peek_utf8_char(&state) {
Ok((first_letter, bytes_parsed)) => {
// Type variables must start with a lowercase letter.
if first_letter.is_alphabetic() && first_letter.is_lowercase() {
buf.push(first_letter);
} 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() {
@ -399,15 +425,19 @@ fn parse_type_variable<'a>(
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());
Ok((answer, state))
let progress = Progress::from_lengths(start_bytes_len, state.bytes.len());
Ok((progress, answer, state))
}
fn malformed<'a>(
@ -416,6 +446,8 @@ fn malformed<'a>(
mut state: State<'a>,
parts: Vec<&'a str>,
) -> ParseResult<'a, TypeAnnotation<'a>> {
// assumption: progress was made to conclude that the annotation is malformed
// Reconstruct the original string that we've been parsing.
let mut full_string = String::new_in(arena);
@ -437,13 +469,14 @@ fn malformed<'a>(
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((
MadeProgress,
TypeAnnotation::Malformed(full_string.into_bump_str()),
state,
))

View file

@ -31,7 +31,7 @@ mod test_parse {
PackageName, PackageOrPath, PlatformHeader, To,
};
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_region::all::{Located, Region};
use std::{f64, i64};
@ -43,12 +43,12 @@ mod test_parse {
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 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) {
@ -2410,8 +2410,11 @@ mod test_parse {
"#
);
let actual = app_header()
.parse(&arena, State::new(src.as_bytes(), Attempting::Module))
.map(|tuple| tuple.0);
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
assert_eq!(Ok(expected), actual);
}
@ -2448,8 +2451,11 @@ mod test_parse {
"#
);
let actual = app_header()
.parse(&arena, State::new(src.as_bytes(), Attempting::Module))
.map(|tuple| tuple.0);
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
assert_eq!(Ok(expected), actual);
}
@ -2499,9 +2505,13 @@ mod test_parse {
provides [ quicksort ] to base
"#
);
let actual = app_header()
.parse(&arena, State::new(src.as_bytes(), Attempting::Module))
.map(|tuple| tuple.0);
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
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 actual = platform_header()
.parse(&arena, State::new(src.as_bytes(), Attempting::Module))
.map(|tuple| tuple.0);
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
assert_eq!(Ok(expected), actual);
}
@ -2612,8 +2625,11 @@ mod test_parse {
"#
);
let actual = platform_header()
.parse(&arena, State::new(src.as_bytes(), Attempting::Module))
.map(|tuple| tuple.0);
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
assert_eq!(Ok(expected), actual);
}
@ -2641,8 +2657,11 @@ mod test_parse {
"#
);
let actual = interface_header()
.parse(&arena, State::new(src.as_bytes(), Attempting::Module))
.map(|tuple| tuple.0);
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
assert_eq!(Ok(expected), actual);
}
@ -2670,8 +2689,11 @@ mod test_parse {
"#
);
let actual = interface_header()
.parse(&arena, State::new(src.as_bytes(), Attempting::Module))
.map(|tuple| tuple.0);
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
assert_eq!(Ok(expected), actual);
}
@ -2697,8 +2719,11 @@ mod test_parse {
"#
);
let actual = module_defs()
.parse(&arena, State::new(src.as_bytes(), Attempting::Module))
.map(|tuple| tuple.0);
.parse(
&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,
// and then again for the lookup.
@ -2745,6 +2770,7 @@ mod test_parse {
Located::new(2, 2, 0, 10, def2),
Located::new(3, 3, 0, 13, def3),
];
let src = indoc!(
r#"
foo = 1
@ -2753,13 +2779,97 @@ mod test_parse {
baz = "stuff"
"#
);
let actual = module_defs()
.parse(&arena, State::new(src.as_bytes(), Attempting::Module))
.map(|tuple| tuple.0);
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
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]
fn newline_after_equals() {
// 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_problem = { path = "../problem" }
roc_types = { path = "../types" }
roc_load = { path = "../load" }
roc_can = { path = "../can" }
roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" }

View file

@ -502,7 +502,7 @@ fn pretty_runtime_error<'b>(
]),
alloc.region(region),
alloc.concat(vec![
alloc.reflow("Roc uses signed 64-bit floating points, allowing values between"),
alloc.reflow("Roc uses signed 64-bit floating points, allowing values between "),
alloc.text(format!("{:e}", f64::MIN)),
alloc.reflow(" and "),
alloc.text(format!("{:e}", f64::MAX)),

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 crate::report::{Report, RocDocAllocator};
use crate::report::{Report, RocDocAllocator, RocDocBuilder};
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>(
alloc: &'b RocDocAllocator<'b>,
filename: PathBuf,
problem: Fail,
starting_line: u32,
parse_problem: ParseProblem,
) -> 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 {
ArgumentsBeforeEquals(region) => {
let report = |doc| Report {
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![
alloc.reflow("Unexpected tokens in front of the `=` symbol:"),
alloc.region(region),
@ -24,19 +75,22 @@ pub fn parse_problem<'b>(
title: "PARSE PROBLEM".to_string(),
}
}
other => {
//
// Unexpected(char, Region),
// OutdentedTooFar,
// ConditionFailed,
// LineTooLong(u32 /* which line was too long */),
// TooManyLines,
// Eof(Region),
// InvalidPattern,
// ReservedKeyword(Region),
// ArgumentsBeforeEquals,
//}
todo!("unhandled parse error: {:?}", other)
Unexpected(mut region) => {
if region.start_col == region.end_col {
region.end_col += 1;
}
let doc = alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("Unexpected token "),
context(alloc, &parse_problem.context_stack, "here"),
alloc.text(":"),
]),
alloc.region(region),
]);
report(doc)
}
_ => todo!("unhandled parse error: {:?}", parse_problem.problem),
}
}

View file

@ -526,6 +526,7 @@ impl<'a> RocDocAllocator<'a> {
if error_highlight_line {
let highlight_text =
ERROR_UNDERLINE.repeat((sub_region.end_col - sub_region.start_col) as usize);
let highlight_line = self
.line()
// 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_parse::ast::{self, Attempting};
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_region::all::Located;
use roc_solve::solve;
@ -85,24 +85,8 @@ where
}
#[allow(dead_code)]
pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Expr<'a>, Fail> {
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>>, 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 fn can_expr<'a>(arena: &'a Bump, expr_str: &'a str) -> Result<CanExprOut, ParseErrOut<'a>> {
can_expr_with(arena, test_home(), expr_str)
}
pub struct CanExprOut {
@ -116,19 +100,38 @@ pub struct CanExprOut {
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)]
pub struct ParseErrOut {
pub fail: Fail,
pub struct ParseErrOut<'a> {
pub fail: Bag<'a>,
pub home: ModuleId,
pub interns: Interns,
}
#[allow(dead_code)]
pub fn can_expr_with(
arena: &Bump,
pub fn can_expr_with<'a>(
arena: &'a Bump,
home: ModuleId,
expr_str: &str,
) -> Result<CanExprOut, ParseErrOut> {
expr_str: &'a str,
) -> Result<CanExprOut, ParseErrOut<'a>> {
let loc_expr = match parse_loc_with(&arena, expr_str) {
Ok(e) => e,
Err(fail) => {

View file

@ -41,8 +41,9 @@ mod test_reporting {
}
}
fn infer_expr_help(
expr_src: &str,
fn infer_expr_help<'a>(
arena: &'a Bump,
expr_src: &'a str,
) -> Result<
(
Vec<solve::TypeError>,
@ -51,7 +52,7 @@ mod test_reporting {
ModuleId,
Interns,
),
ParseErrOut,
ParseErrOut<'a>,
> {
let CanExprOut {
loc_expr,
@ -63,7 +64,7 @@ mod test_reporting {
mut interns,
problems: can_problems,
..
} = can_expr(expr_src)?;
} = can_expr(arena, expr_src)?;
let mut subs = Subs::new(var_store.into());
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))
}
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
F: FnOnce(RocDocBuilder<'_>, &mut String),
{
@ -118,7 +119,7 @@ mod test_reporting {
let filename = filename_from_string(r"\code\proj\Main.roc");
match infer_expr_help(src) {
match infer_expr_help(arena, src) {
Err(parse_err) => {
let ParseErrOut {
fail,
@ -128,7 +129,8 @@ mod test_reporting {
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)
}
@ -169,6 +171,7 @@ mod test_reporting {
fn report_problem_as(src: &str, expected_rendering: &str) {
let mut buf: String = String::new();
let arena = Bump::new();
let callback = |doc: RocDocBuilder<'_>, buf: &mut String| {
doc.1
@ -176,13 +179,23 @@ mod test_reporting {
.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);
}
fn color_report_problem_as(src: &str, expected_rendering: &str) {
let mut buf: String = String::new();
let arena = Bump::new();
let callback = |doc: RocDocBuilder<'_>, buf: &mut String| {
doc.1
@ -196,7 +209,7 @@ mod test_reporting {
.expect("list_reports")
};
list_reports(src, &mut buf, callback);
list_reports(&arena, src, &mut buf, callback);
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) =
infer_expr_help(src).expect("parse error");
infer_expr_help(&arena, src).expect("parse error");
let mut buf = String::new();
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) =
infer_expr_help(src).expect("parse error");
infer_expr_help(&arena, src).expect("parse error");
let mut buf = String::new();
let src_lines: Vec<&str> = src.split('\n').collect();
@ -3304,16 +3319,15 @@ mod test_reporting {
#[test]
fn float_out_of_range() {
// have to deal with some whitespace issues because of the format! macro
report_problem_as(
&format!(
indoc!(
r#"
overflow = 1{:e}
underflow = -1{:e}
overflow = 11.7976931348623157e308
underflow = -11.7976931348623157e308
overflow + underflow
"#,
f64::MAX,
f64::MAX,
"#
),
indoc!(
r#"
@ -3321,11 +3335,11 @@ mod test_reporting {
This float literal is too big:
2 overflow = 11.7976931348623157e308
^^^^^^^^^^^^^^^^^^^^^^^
1 overflow = 11.7976931348623157e308
^^^^^^^^^^^^^^^^^^^^^^^
Roc uses signed 64-bit floating points, allowing values
between-1.7976931348623157e308 and 1.7976931348623157e308
Roc uses signed 64-bit floating points, allowing values between
-1.7976931348623157e308 and 1.7976931348623157e308
Tip: Learn more about number literals at TODO
@ -3333,11 +3347,11 @@ mod test_reporting {
This float literal is too small:
3 underflow = -11.7976931348623157e308
^^^^^^^^^^^^^^^^^^^^^^^^
2 underflow = -11.7976931348623157e308
^^^^^^^^^^^^^^^^^^^^^^^^
Roc uses signed 64-bit floating points, allowing values
between-1.7976931348623157e308 and 1.7976931348623157e308
Roc uses signed 64-bit floating points, allowing values between
-1.7976931348623157e308 and 1.7976931348623157e308
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_parse::ast::{self, Attempting};
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_region::all::{Located, Region};
use roc_solve::solve;
@ -87,19 +87,22 @@ where
}
#[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)
}
#[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.trim().as_bytes(), Attempting::Module);
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)
.map(|(_, loc_expr, _)| loc_expr)
.map_err(|(_, fail, _)| fail)
}
#[allow(dead_code)]

View file

@ -854,7 +854,7 @@ mod solve_expr {
infer_eq(
indoc!(
r#"
\f -> (\a, b -> f b a),
\f -> (\a, b -> f b a)
"#
),
"(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_parse::ast::{self, Attempting};
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_region::all::{Located, Region};
use roc_solve::solve;
@ -87,19 +87,22 @@ where
}
#[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)
}
#[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.trim().as_bytes(), Attempting::Module);
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)
.map(|(_, loc_expr, _)| loc_expr)
.map_err(|(_, fail, _)| fail)
}
#[allow(dead_code)]

View file

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

View file

@ -19,15 +19,15 @@ pub struct File<'a> {
}
#[derive(Debug)]
pub enum ReadError {
pub enum ReadError<'a> {
Read(std::io::Error),
ParseDefs(parser::Fail),
ParseHeader(parser::Fail),
ParseDefs(parser::Bag<'a>),
ParseHeader(parser::Bag<'a>),
DoesntHaveRocExtension,
}
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")) {
return Err(ReadError::DoesntHaveRocExtension);
}
@ -36,23 +36,23 @@ impl<'a> File<'a> {
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);
match parsed_module {
Ok((module, state)) => {
Ok((_, module, state)) => {
let parsed_defs = module_defs().parse(&arena, state);
match parsed_defs {
Ok((defs, _)) => Ok(File {
Ok((_, defs, _)) => Ok(File {
path,
module_header: module,
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)),
}
}