Merge branch 'trunk' of github.com:rtfeldman/roc into markup

This commit is contained in:
Anton-4 2021-03-15 13:34:53 +01:00
commit c0dbea6ab1
72 changed files with 4438 additions and 3447 deletions

2
Cargo.lock generated
View File

@ -2895,6 +2895,7 @@ dependencies = [
"roc_solve",
"roc_types",
"roc_unify",
"serde_json",
"target-lexicon",
"tempfile",
]
@ -2927,6 +2928,7 @@ dependencies = [
"pretty_assertions 0.5.1",
"quickcheck 0.8.5",
"quickcheck_macros 0.8.0",
"roc_builtins",
"roc_collections",
"roc_module",
"roc_parse",

View File

@ -8,7 +8,7 @@ install-other-libs:
FROM +prep-debian
RUN apt -y install wget git
RUN apt -y install libxcb-shape0-dev libxcb-xfixes0-dev # for editor clipboard
RUN apt -y install libc++-dev libc++abi-dev libunwind-dev pkg-config libx11-dev zlib1g-dev
RUN apt -y install libc++-dev libc++abi-dev g++ libunwind-dev pkg-config libx11-dev zlib1g-dev
install-zig-llvm-valgrind-clippy-rustfmt:
FROM +install-other-libs
@ -30,10 +30,10 @@ install-zig-llvm-valgrind-clippy-rustfmt:
RUN wget https://sourceware.org/pub/valgrind/valgrind-3.16.1.tar.bz2
RUN tar -xf valgrind-3.16.1.tar.bz2
# need to cd every time, every command starts at WORKDIR
RUN cd valgrind-3.16.1; ./autogen.sh
RUN cd valgrind-3.16.1; ./configure --disable-dependency-tracking
RUN cd valgrind-3.16.1; make -j`nproc`
RUN cd valgrind-3.16.1; make install
RUN cd valgrind-3.16.1 && ./autogen.sh
RUN cd valgrind-3.16.1 && ./configure --disable-dependency-tracking
RUN cd valgrind-3.16.1 && make -j`nproc`
RUN cd valgrind-3.16.1 && make install
# clippy
RUN rustup component add clippy
# rustfmt
@ -75,16 +75,16 @@ save-cache:
FROM +install-zig-llvm-valgrind-clippy-rustfmt
COPY +prepare-cache/recipe.json ./
RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo chef cook; sccache --show-stats # for clippy
cargo chef cook && sccache --show-stats # for clippy
RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo chef cook --release --tests; sccache --show-stats
cargo chef cook --release --tests && sccache --show-stats
SAVE ARTIFACT target
SAVE ARTIFACT $CARGO_HOME cargo_home
test-zig:
FROM +install-zig-llvm-valgrind-clippy-rustfmt
COPY --dir compiler/builtins/bitcode ./
RUN cd bitcode; ./run-tests.sh;
RUN cd bitcode && ./run-tests.sh
check-clippy:
FROM +copy-dirs-and-cache
@ -101,7 +101,7 @@ test-rust:
FROM +copy-dirs-and-cache
ENV RUST_BACKTRACE=1
RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo test --release; sccache --show-stats
cargo test --release && sccache --show-stats
test-all:
BUILD +test-zig

View File

@ -80,6 +80,7 @@ libloading = "0.6"
# This way, GitHub Actions works and nobody's builds get broken.
inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release3" }
target-lexicon = "0.10"
tempfile = "3.1.0"
[dev-dependencies]
pretty_assertions = "0.5.1"

View File

@ -7,10 +7,11 @@ use roc_can::builtins::builtin_defs_map;
use roc_collections::all::MutMap;
use roc_gen::llvm::build::OptLevel;
use roc_load::file::LoadingProblem;
use std::fs;
use std::env;
use std::path::PathBuf;
use std::time::{Duration, SystemTime};
use target_lexicon::Triple;
use tempfile::Builder;
fn report_timing(buf: &mut String, label: &str, duration: Duration) {
buf.push_str(&format!(
@ -52,8 +53,14 @@ pub fn build_file<'a>(
)?;
let path_to_platform = loaded.platform_path.clone();
let app_o_file = roc_file_path.with_file_name("roc_app.o");
let app_o_file = Builder::new()
.prefix("roc_app")
.suffix(".o")
.tempfile()
.map_err(|err| {
todo!("TODO Gracefully handle tempfile creation error {:?}", err);
})?;
let app_o_file = app_o_file.path();
let buf = &mut String::with_capacity(1024);
let mut it = loaded.timings.iter().peekable();
@ -96,13 +103,12 @@ pub fn build_file<'a>(
}
}
let cwd = app_o_file.parent().unwrap();
let cwd = roc_file_path.parent().unwrap();
let binary_path = cwd.join(&*loaded.output_path); // TODO should join ".exe" on Windows
let code_gen_timing = program::gen_from_mono_module(
&arena,
loaded,
roc_file_path,
&roc_file_path,
Triple::host(),
&app_o_file,
opt_level,
@ -119,16 +125,21 @@ pub fn build_file<'a>(
let compilation_end = compilation_start.elapsed().unwrap();
let size = std::fs::metadata(&app_o_file).unwrap().len();
let size = std::fs::metadata(&app_o_file)
.unwrap_or_else(|err| {
panic!(
"Could not open {:?} - which was supposed to have been generated. Error: {:?}",
app_o_file, err
);
})
.len();
if emit_debug_info {
println!(
"\n\nCompilation finished! Here's how long each module took to compile:\n\n{}",
"\n\nCompilation finished!\n\nHere's how long each module took to compile:\n\n{}",
buf
);
println!("\nSuccess! 🎉\n\n\t{}\n", app_o_file.display());
println!(
"Finished compilation and code gen in {} ms\n\nProduced a app.o file of size {:?}\n",
compilation_end.as_millis(),
@ -138,6 +149,7 @@ pub fn build_file<'a>(
// Step 2: link the precompiled host and compiled app
let mut host_input_path = PathBuf::from(cwd);
host_input_path.push(&*path_to_platform);
host_input_path.push("host.o");
@ -161,7 +173,7 @@ pub fn build_file<'a>(
link(
target,
binary_path,
&[host_input_path.as_path().to_str().unwrap(), app_o_file.as_path().to_str().unwrap()],
&[host_input_path.as_path().to_str().unwrap(), app_o_file.to_str().unwrap()],
link_type
)
.map_err(|_| {
@ -177,16 +189,21 @@ pub fn build_file<'a>(
println!("Finished linking in {} ms\n", link_end.as_millis());
}
// Clean up the leftover .o file from the Roc, if possible.
// (If cleaning it up fails, that's fine. No need to take action.)
// TODO compile the app_o_file to a tmpdir, as an extra precaution.
let _ = fs::remove_file(app_o_file);
// If the cmd errored out, return the Err.
cmd_result?;
// If possible, report the generated executable name relative to the current dir.
let generated_filename = binary_path
.strip_prefix(env::current_dir().unwrap())
.unwrap_or(&binary_path);
let total_end = compilation_start.elapsed().unwrap();
println!("Finished entire process in {} ms\n", total_end.as_millis());
println!(
"🎉 Built {} in {} ms",
generated_filename.to_str().unwrap(),
total_end.as_millis()
);
Ok(binary_path)
}

View File

@ -82,6 +82,14 @@ fn jit_to_ast_help<'a>(
content
)))
}
Layout::Builtin(Builtin::Int128) => {
Ok(run_jit_function!(
lib,
main_fn_name,
i128,
|num| num_to_ast(env, i128_to_ast(env.arena, num), content)
))
}
Layout::Builtin(Builtin::Float64) => {
Ok(run_jit_function!(lib, main_fn_name, f64, |num| num_to_ast(
env,
@ -852,6 +860,12 @@ fn i64_to_ast(arena: &Bump, num: i64) -> Expr<'_> {
Expr::Num(arena.alloc(format!("{}", num)))
}
/// This is centralized in case we want to format it differently later,
/// e.g. adding underscores for large numbers
fn i128_to_ast(arena: &Bump, num: i128) -> Expr<'_> {
Expr::Num(arena.alloc(format!("{}", num)))
}
/// This is centralized in case we want to format it differently later,
/// e.g. adding underscores for large numbers
fn f64_to_ast(arena: &Bump, num: f64) -> Expr<'_> {

View File

@ -18,6 +18,15 @@ mod cli_run {
use serial_test::serial;
use std::path::Path;
#[cfg(not(target_os = "macos"))]
const ALLOW_VALGRIND: bool = true;
// Disallow valgrind on macOS by default, because it reports a ton
// of false positives. For local development on macOS, feel free to
// change this to true!
#[cfg(target_os = "macos")]
const ALLOW_VALGRIND: bool = false;
fn check_output(
file: &Path,
executable_filename: &str,
@ -49,7 +58,7 @@ mod cli_run {
}
assert!(compile_out.status.success());
let out = if use_valgrind {
let out = if use_valgrind && ALLOW_VALGRIND {
let (valgrind_out, raw_xml) = run_with_valgrind(
stdin_str,
&[file.with_file_name(executable_filename).to_str().unwrap()],

View File

@ -5,6 +5,21 @@ const testing = std.testing;
const expectEqual = testing.expectEqual;
const expect = testing.expect;
comptime {
// This is a workaround for https://github.com/ziglang/zig/issues/8218
// which is only necessary on macOS.
//
// Once that issue is fixed, we can undo the changes in
// 177cf12e0555147faa4d436e52fc15175c2c4ff0 and go back to passing
// -fcompiler-rt in link.rs instead of doing this. Note that this
// workaround is present in many host.zig files, so make sure to undo
// it everywhere!
if (std.builtin.os.tag == .macos) {
_ = @import("compiler_rt");
}
}
const mem = std.mem;
const Allocator = mem.Allocator;

View File

@ -5,6 +5,20 @@ const testing = std.testing;
const expectEqual = testing.expectEqual;
const expect = testing.expect;
comptime {
// This is a workaround for https://github.com/ziglang/zig/issues/8218
// which is only necessary on macOS.
//
// Once that issue is fixed, we can undo the changes in
// 177cf12e0555147faa4d436e52fc15175c2c4ff0 and go back to passing
// -fcompiler-rt in link.rs instead of doing this. Note that this
// workaround is present in many host.zig files, so make sure to undo
// it everywhere!
if (std.builtin.os.tag == .macos) {
_ = @import("compiler_rt");
}
}
const mem = std.mem;
const Allocator = mem.Allocator;

View File

@ -27,6 +27,7 @@ bumpalo = { version = "3.2", features = ["collections"] }
inlinable_string = "0.1.0"
libloading = "0.6"
tempfile = "3.1.0"
serde_json = "1.0"
# NOTE: rtfeldman/inkwell is a fork of TheDan64/inkwell which does not change anything.
#
# The reason for this fork is that the way Inkwell is designed, you have to use

View File

@ -55,6 +55,109 @@ fn find_zig_str_path() -> PathBuf {
panic!("cannot find `str.zig`")
}
#[cfg(not(target_os = "macos"))]
fn build_zig_host(
env_path: &str,
env_home: &str,
emit_bin: &str,
zig_host_src: &str,
zig_str_path: &str,
) -> Output {
Command::new("zig")
.env_clear()
.env("PATH", env_path)
.env("HOME", env_home)
.args(&[
"build-obj",
zig_host_src,
emit_bin,
"--pkg-begin",
"str",
zig_str_path,
"--pkg-end",
// include the zig runtime
"-fcompiler-rt",
// include libc
"--library",
"c",
])
.output()
.unwrap()
}
#[cfg(target_os = "macos")]
fn build_zig_host(
env_path: &str,
env_home: &str,
emit_bin: &str,
zig_host_src: &str,
zig_str_path: &str,
) -> Output {
use serde_json::Value;
// Run `zig env` to find the location of zig's std/ directory
let zig_env_output = Command::new("zig").args(&["env"]).output().unwrap();
let zig_env_json = if zig_env_output.status.success() {
std::str::from_utf8(&zig_env_output.stdout).unwrap_or_else(|utf8_err| {
panic!(
"`zig env` failed; its stderr output was invalid utf8 ({:?})",
utf8_err
);
})
} else {
match std::str::from_utf8(&zig_env_output.stderr) {
Ok(stderr) => panic!("`zig env` failed - stderr output was: {:?}", stderr),
Err(utf8_err) => panic!(
"`zig env` failed; its stderr output was invalid utf8 ({:?})",
utf8_err
),
}
};
let mut zig_compiler_rt_path = match serde_json::from_str(zig_env_json) {
Ok(Value::Object(map)) => match map.get("std_dir") {
Some(Value::String(std_dir)) => PathBuf::from(Path::new(std_dir)),
_ => {
panic!("Expected JSON containing a `std_dir` String field from `zig env`, but got: {:?}", zig_env_json);
}
},
_ => {
panic!(
"Expected JSON containing a `std_dir` field from `zig env`, but got: {:?}",
zig_env_json
);
}
};
zig_compiler_rt_path.push("special");
zig_compiler_rt_path.push("compiler_rt.zig");
Command::new("zig")
.env_clear()
.env("PATH", &env_path)
.env("HOME", &env_home)
.args(&[
"build-obj",
zig_host_src,
&emit_bin,
"--pkg-begin",
"str",
zig_str_path,
"--pkg-end",
// include the zig runtime
"--pkg-begin",
"compiler_rt",
zig_compiler_rt_path.to_str().unwrap(),
"--pkg-end",
// include libc
"--library",
"c",
])
.output()
.unwrap()
}
pub fn rebuild_host(host_input_path: &Path) {
let c_host_src = host_input_path.with_file_name("host.c");
let c_host_dest = host_input_path.with_file_name("c_host.o");
@ -79,28 +182,17 @@ pub fn rebuild_host(host_input_path: &Path) {
&zig_str_path
);
let output = Command::new("zig")
.env_clear()
.env("PATH", &env_path)
.env("HOME", &env_home)
.args(&[
"build-obj",
zig_host_src.to_str().unwrap(),
validate_output(
"host.zig",
"zig",
build_zig_host(
&env_path,
&env_home,
&emit_bin,
"--pkg-begin",
"str",
zig_host_src.to_str().unwrap(),
zig_str_path.to_str().unwrap(),
"--pkg-end",
// include the zig runtime
"-fcompiler-rt",
// include libc
"--library",
"c",
])
.output()
.unwrap();
validate_output("host.zig", "zig", output);
),
);
} else {
// Compile host.c
let output = Command::new("clang")
@ -351,7 +443,11 @@ fn link_macos(
// Don't allow LD_ env vars to affect this
.env_clear()
.args(&[
"--gc-sections",
// NOTE: we don't do --gc-sections on macOS because the default
// macOS linker doesn't support it, but it's a performance
// optimization, so if we ever switch to a different linker,
// we'd like to re-enable it on macOS!
// "--gc-sections",
link_type_arg,
"-arch",
target.architecture.to_string().as_str(),

View File

@ -23,7 +23,7 @@ pub struct CodeGenTiming {
pub fn gen_from_mono_module(
arena: &Bump,
mut loaded: MonomorphizedModule,
_file_path: PathBuf,
roc_file_path: &Path,
target: Triple,
app_o_file: &Path,
opt_level: OptLevel,
@ -91,6 +91,13 @@ pub fn gen_from_mono_module(
use inkwell::attributes::{Attribute, AttributeLoc};
use inkwell::module::Linkage;
let app_ll_file = {
let mut temp = PathBuf::from(roc_file_path);
temp.set_extension("ll");
temp
};
let kind_id = Attribute::get_named_enum_kind_id("alwaysinline");
debug_assert!(kind_id > 0);
let attr = context.create_enum_attribute(kind_id, 1);
@ -165,11 +172,16 @@ pub fn gen_from_mono_module(
fpm.run_on(&fn_val);
} else {
fn_val.print_to_stderr();
// write the ll code to a file, so we can modify it
env.module.print_to_file(&app_ll_file).unwrap();
// env.module.print_to_stderr();
// NOTE: If this fails, uncomment the above println to debug.
panic!(
r"Non-main function {:?} failed LLVM verification. Uncomment the above println to debug!",
r"Non-main function {:?} failed LLVM verification. I wrote the full LLVM IR to {:?}",
fn_val.get_name(),
app_ll_file,
);
}
}
@ -183,7 +195,13 @@ pub fn gen_from_mono_module(
// Verify the module
if let Err(errors) = env.module.verify() {
panic!("😱 LLVM errors when defining module: {:?}", errors);
// write the ll code to a file, so we can modify it
env.module.print_to_file(&app_ll_file).unwrap();
panic!(
"😱 LLVM errors when defining module; I wrote the full LLVM IR to {:?}\n\n {:?}",
app_ll_file, errors,
);
}
// Uncomment this to see the module's optimized LLVM instruction output:
@ -197,13 +215,10 @@ pub fn gen_from_mono_module(
if emit_debug_info {
module.strip_debug_info();
let mut app_ll_file = std::path::PathBuf::from(app_o_file);
app_ll_file.set_extension("ll");
let mut app_ll_dbg_file = std::path::PathBuf::from(app_o_file);
let mut app_ll_dbg_file = PathBuf::from(roc_file_path);
app_ll_dbg_file.set_extension("dbg.ll");
let mut app_bc_file = std::path::PathBuf::from(app_o_file);
let mut app_bc_file = PathBuf::from(roc_file_path);
app_bc_file.set_extension("bc");
use std::process::Command;

View File

@ -114,6 +114,7 @@ pub const RocList = extern struct {
const Caller1 = fn (?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void;
const Caller2 = fn (?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void;
const Caller3 = fn (?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void;
pub fn listMap(list: RocList, transform: Opaque, caller: Caller1, alignment: usize, old_element_width: usize, new_element_width: usize) callconv(.C) RocList {
if (list.bytes) |source_ptr| {
@ -213,6 +214,129 @@ pub fn listMap2(list1: RocList, list2: RocList, transform: Opaque, caller: Calle
}
}
pub fn listMap3(list1: RocList, list2: RocList, list3: RocList, transform: Opaque, caller: Caller3, alignment: usize, a_width: usize, b_width: usize, c_width: usize, d_width: usize, dec_a: Dec, dec_b: Dec, dec_c: Dec) callconv(.C) RocList {
const smaller_length = std.math.min(list1.len(), list2.len());
const output_length = std.math.min(smaller_length, list3.len());
if (list1.bytes) |source_a| {
if (list2.bytes) |source_b| {
if (list3.bytes) |source_c| {
const output = RocList.allocate(std.heap.c_allocator, alignment, output_length, d_width);
const target_ptr = output.bytes orelse unreachable;
var i: usize = 0;
while (i < output_length) : (i += 1) {
const element_a = source_a + i * a_width;
const element_b = source_b + i * b_width;
const element_c = source_c + i * c_width;
const target = target_ptr + i * d_width;
caller(transform, element_a, element_b, element_c, target);
}
// if the lists don't have equal length, we must consume the remaining elements
// In this case we consume by (recursively) decrementing the elements
if (list1.len() > output_length) {
i = output_length;
while (i < list1.len()) : (i += 1) {
const element_a = source_a + i * a_width;
dec_a(element_a);
}
}
if (list2.len() > output_length) {
i = output_length;
while (i < list2.len()) : (i += 1) {
const element_b = source_b + i * b_width;
dec_b(element_b);
}
}
if (list3.len() > output_length) {
i = output_length;
while (i < list3.len()) : (i += 1) {
const element_c = source_c + i * c_width;
dec_c(element_c);
}
}
utils.decref(std.heap.c_allocator, alignment, list1.bytes, list1.len() * a_width);
utils.decref(std.heap.c_allocator, alignment, list2.bytes, list2.len() * b_width);
utils.decref(std.heap.c_allocator, alignment, list3.bytes, list3.len() * c_width);
return output;
} else {
// consume list1 elements (we know there is at least one because the list1.bytes pointer is non-null
var i: usize = 0;
while (i < list1.len()) : (i += 1) {
const element_a = source_a + i * a_width;
dec_a(element_a);
}
utils.decref(std.heap.c_allocator, alignment, list1.bytes, list1.len() * a_width);
// consume list2 elements (we know there is at least one because the list1.bytes pointer is non-null
i = 0;
while (i < list2.len()) : (i += 1) {
const element_b = source_b + i * b_width;
dec_b(element_b);
}
utils.decref(std.heap.c_allocator, alignment, list2.bytes, list2.len() * b_width);
return RocList.empty();
}
} else {
// consume list1 elements (we know there is at least one because the list1.bytes pointer is non-null
var i: usize = 0;
while (i < list1.len()) : (i += 1) {
const element_a = source_a + i * a_width;
dec_a(element_a);
}
utils.decref(std.heap.c_allocator, alignment, list1.bytes, list1.len() * a_width);
// consume list3 elements (if any)
if (list3.bytes) |source_c| {
i = 0;
while (i < list2.len()) : (i += 1) {
const element_c = source_c + i * c_width;
dec_c(element_c);
}
utils.decref(std.heap.c_allocator, alignment, list3.bytes, list3.len() * c_width);
}
return RocList.empty();
}
} else {
// consume list2 elements (if any)
if (list2.bytes) |source_b| {
var i: usize = 0;
while (i < list2.len()) : (i += 1) {
const element_b = source_b + i * b_width;
dec_b(element_b);
}
utils.decref(std.heap.c_allocator, alignment, list2.bytes, list2.len() * b_width);
}
// consume list3 elements (if any)
if (list3.bytes) |source_c| {
var i: usize = 0;
while (i < list2.len()) : (i += 1) {
const element_c = source_c + i * c_width;
dec_c(element_c);
}
utils.decref(std.heap.c_allocator, alignment, list3.bytes, list3.len() * c_width);
}
return RocList.empty();
}
}
pub fn listKeepIf(list: RocList, transform: Opaque, caller: Caller1, alignment: usize, element_width: usize, inc: Inc, dec: Dec) callconv(.C) RocList {
if (list.bytes) |source_ptr| {
const size = list.len();

View File

@ -8,6 +8,7 @@ const list = @import("list.zig");
comptime {
exportListFn(list.listMap, "map");
exportListFn(list.listMap2, "map2");
exportListFn(list.listMap3, "map3");
exportListFn(list.listMapWithIndex, "map_with_index");
exportListFn(list.listKeepIf, "keep_if");
exportListFn(list.listWalk, "walk");

View File

@ -64,6 +64,7 @@ pub const SET_FROM_LIST: &str = "roc_builtins.dict.set_from_list";
pub const LIST_MAP: &str = "roc_builtins.list.map";
pub const LIST_MAP2: &str = "roc_builtins.list.map2";
pub const LIST_MAP3: &str = "roc_builtins.list.map3";
pub const LIST_MAP_WITH_INDEX: &str = "roc_builtins.list.map_with_index";
pub const LIST_KEEP_IF: &str = "roc_builtins.list.keep_if";
pub const LIST_KEEP_OKS: &str = "roc_builtins.list.keep_oks";

View File

@ -3,8 +3,8 @@ use roc_module::ident::TagName;
use roc_module::symbol::Symbol;
use roc_region::all::Region;
use roc_types::builtin_aliases::{
bool_type, dict_type, float_type, int_type, list_type, nat_type, num_type, ordering_type,
result_type, set_type, str_type, str_utf8_byte_problem_type, u64_type, u8_type,
bool_type, dict_type, float_type, i128_type, int_type, list_type, nat_type, num_type,
ordering_type, result_type, set_type, str_type, str_utf8_byte_problem_type, u64_type, u8_type,
};
use roc_types::solved_types::SolvedType;
use roc_types::subs::VarId;
@ -384,6 +384,18 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
),
);
// isMultipleOf : Int a, Int a -> Bool
add_type(
Symbol::NUM_IS_MULTIPLE_OF,
top_level_function(
vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))],
Box::new(bool_type()),
),
);
// maxI128 : I128
add_type(Symbol::NUM_MAX_I128, i128_type());
// Float module
// div : Float a, Float a -> Float a
@ -817,6 +829,21 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
)
});
// map3 : List a, List b, List c, (a, b, c -> d) -> List d
add_type(Symbol::LIST_MAP3, {
let_tvars! {a, b, c, d, cvar};
top_level_function(
vec![
list_type(flex(a)),
list_type(flex(b)),
list_type(flex(c)),
closure(vec![flex(a), flex(b), flex(c)], cvar, Box::new(flex(d))),
],
Box::new(list_type(flex(d))),
)
});
// append : List elem, elem -> List elem
add_type(
Symbol::LIST_APPEND,
@ -900,9 +927,9 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
// empty : Dict * *
add_type(Symbol::DICT_EMPTY, dict_type(flex(TVAR1), flex(TVAR2)));
// singleton : k, v -> Dict k v
// single : k, v -> Dict k v
add_type(
Symbol::DICT_SINGLETON,
Symbol::DICT_SINGLE,
top_level_function(
vec![flex(TVAR1), flex(TVAR2)],
Box::new(dict_type(flex(TVAR1), flex(TVAR2))),
@ -1030,9 +1057,9 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
// empty : Set a
add_type(Symbol::SET_EMPTY, set_type(flex(TVAR1)));
// singleton : a -> Set a
// single : a -> Set a
add_type(
Symbol::SET_SINGLETON,
Symbol::SET_SINGLE,
top_level_function(vec![flex(TVAR1)], Box::new(set_type(flex(TVAR1)))),
);

View File

@ -270,6 +270,12 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
int_type(star, int)
});
// maxI128 : Int
add_type(Symbol::NUM_MAX_I128, {
let_tvars! { star, int };
int_type(star, int)
});
// minInt : Int
add_type(Symbol::NUM_MIN_INT, {
let_tvars! { star, int };
@ -919,8 +925,8 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
dict_type(star, k, v)
});
// singleton : k, v -> Attr * (Dict k v)
add_type(Symbol::DICT_SINGLETON, {
// single : k, v -> Attr * (Dict k v)
add_type(Symbol::DICT_SINGLE, {
let_tvars! { star, k , v };
unique_function(vec![flex(k), flex(v)], dict_type(star, k, v))
});
@ -1006,8 +1012,8 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
set_type(star, a)
});
// singleton : a -> Set a
add_type(Symbol::SET_SINGLETON, {
// single : a -> Set a
add_type(Symbol::SET_SINGLE, {
let_tvars! { star, a };
unique_function(vec![flex(a)], set_type(star, a))
});

View File

@ -12,6 +12,7 @@ roc_module = { path = "../module" }
roc_parse = { path = "../parse" }
roc_problem = { path = "../problem" }
roc_types = { path = "../types" }
roc_builtins = { path = "../builtins" }
ven_graph = { path = "../../vendor/pathfinding" }
im = "14" # im and im-rc should always have the same version!
im-rc = "14" # im and im-rc should always have the same version!

View File

@ -81,6 +81,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
LIST_JOIN => list_join,
LIST_MAP => list_map,
LIST_MAP2 => list_map2,
LIST_MAP3 => list_map3,
LIST_MAP_WITH_INDEX => list_map_with_index,
LIST_KEEP_IF => list_keep_if,
LIST_KEEP_OKS => list_keep_oks,
@ -90,7 +91,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
DICT_TEST_HASH => dict_hash_test_only,
DICT_LEN => dict_len,
DICT_EMPTY => dict_empty,
DICT_SINGLETON => dict_singleton,
DICT_SINGLE => dict_single,
DICT_INSERT => dict_insert,
DICT_REMOVE => dict_remove,
DICT_GET => dict_get,
@ -103,7 +104,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
DICT_WALK=> dict_walk,
SET_EMPTY => set_empty,
SET_LEN => set_len,
SET_SINGLETON => set_singleton,
SET_SINGLE => set_single,
SET_UNION=> set_union,
SET_INTERSECTION => set_intersection,
SET_DIFFERENCE => set_difference,
@ -135,6 +136,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
NUM_ABS => num_abs,
NUM_NEG => num_neg,
NUM_REM => num_rem,
NUM_IS_MULTIPLE_OF => num_is_multiple_of,
NUM_SQRT => num_sqrt,
NUM_ROUND => num_round,
NUM_IS_ODD => num_is_odd,
@ -159,6 +161,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
NUM_SHIFT_RIGHT => num_shift_right_by,
NUM_SHIFT_RIGHT_ZERO_FILL => num_shift_right_zf_by,
NUM_INT_CAST=> num_int_cast,
NUM_MAX_I128=> num_max_i128,
RESULT_MAP => result_map,
RESULT_MAP_ERR => result_map_err,
RESULT_AFTER => result_after,
@ -218,6 +221,7 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap<Symbol, Def> {
Symbol::LIST_JOIN => list_join,
Symbol::LIST_MAP => list_map,
Symbol::LIST_MAP2 => list_map2,
Symbol::LIST_MAP3 => list_map3,
Symbol::LIST_MAP_WITH_INDEX => list_map_with_index,
Symbol::LIST_KEEP_IF => list_keep_if,
Symbol::LIST_KEEP_OKS => list_keep_oks,
@ -227,7 +231,7 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap<Symbol, Def> {
Symbol::DICT_TEST_HASH => dict_hash_test_only,
Symbol::DICT_LEN => dict_len,
Symbol::DICT_EMPTY => dict_empty,
Symbol::DICT_SINGLETON => dict_singleton,
Symbol::DICT_SINGLE => dict_single,
Symbol::DICT_INSERT => dict_insert,
Symbol::DICT_REMOVE => dict_remove,
Symbol::DICT_GET => dict_get,
@ -240,7 +244,7 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap<Symbol, Def> {
Symbol::DICT_WALK=> dict_walk,
Symbol::SET_EMPTY => set_empty,
Symbol::SET_LEN => set_len,
Symbol::SET_SINGLETON => set_singleton,
Symbol::SET_SINGLE => set_single,
Symbol::SET_UNION=> set_union,
Symbol::SET_INTERSECTION=> set_intersection,
Symbol::SET_DIFFERENCE=> set_difference,
@ -268,6 +272,7 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap<Symbol, Def> {
Symbol::NUM_ABS => num_abs,
Symbol::NUM_NEG => num_neg,
Symbol::NUM_REM => num_rem,
Symbol::NUM_IS_MULTIPLE_OF => num_is_multiple_of,
Symbol::NUM_SQRT => num_sqrt,
Symbol::NUM_ROUND => num_round,
Symbol::NUM_IS_ODD => num_is_odd,
@ -292,6 +297,7 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap<Symbol, Def> {
Symbol::NUM_SHIFT_RIGHT => num_shift_right_by,
Symbol::NUM_SHIFT_RIGHT_ZERO_FILL => num_shift_right_zf_by,
Symbol::NUM_INT_CAST=> num_int_cast,
Symbol::NUM_MAX_I128=> num_max_i128,
Symbol::RESULT_MAP => result_map,
Symbol::RESULT_MAP_ERR => result_map_err,
Symbol::RESULT_AFTER => result_after,
@ -370,11 +376,43 @@ fn lowlevel_3(symbol: Symbol, op: LowLevel, var_store: &mut VarStore) -> Def {
)
}
fn lowlevel_4(symbol: Symbol, op: LowLevel, var_store: &mut VarStore) -> Def {
let arg1_var = var_store.fresh();
let arg2_var = var_store.fresh();
let arg3_var = var_store.fresh();
let arg4_var = var_store.fresh();
let ret_var = var_store.fresh();
let body = RunLowLevel {
op,
args: vec![
(arg1_var, Var(Symbol::ARG_1)),
(arg2_var, Var(Symbol::ARG_2)),
(arg3_var, Var(Symbol::ARG_3)),
(arg4_var, Var(Symbol::ARG_4)),
],
ret_var,
};
defn(
symbol,
vec![
(arg1_var, Symbol::ARG_1),
(arg2_var, Symbol::ARG_2),
(arg3_var, Symbol::ARG_3),
(arg4_var, Symbol::ARG_4),
],
var_store,
body,
ret_var,
)
}
/// Num.maxInt : Int
fn num_max_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
let int_var = var_store.fresh();
let int_percision_var = var_store.fresh();
let body = Int(int_var, int_percision_var, i64::MAX);
let body = Int(int_var, int_percision_var, i64::MAX.into());
Def {
annotation: None,
@ -389,7 +427,7 @@ fn num_max_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
fn num_min_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
let int_var = var_store.fresh();
let int_percision_var = var_store.fresh();
let body = Int(int_var, int_percision_var, i64::MIN);
let body = Int(int_var, int_percision_var, i64::MIN.into());
Def {
annotation: None,
@ -1344,6 +1382,33 @@ fn num_int_cast(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
}
/// Num.maxI128: I128
fn num_max_i128(symbol: Symbol, var_store: &mut VarStore) -> Def {
let int_var = var_store.fresh();
let int_percision_var = var_store.fresh();
let body = Int(int_var, int_percision_var, i128::MAX);
let std = roc_builtins::std::types();
let solved = std.get(&symbol).unwrap();
let mut free_vars = roc_types::solved_types::FreeVars::default();
let signature = roc_types::solved_types::to_type(&solved.0, &mut free_vars, var_store);
let annotation = crate::def::Annotation {
signature,
introduced_variables: Default::default(),
region: Region::zero(),
aliases: Default::default(),
};
Def {
annotation: Some(annotation),
expr_var: int_var,
loc_expr: Located::at_zero(body),
loc_pattern: Located::at_zero(Pattern::Identifier(symbol)),
pattern_vars: SendMap::default(),
}
}
/// List.isEmpty : List * -> Bool
fn list_is_empty(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh();
@ -2122,6 +2187,11 @@ fn list_map2(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_3(symbol, LowLevel::ListMap2, var_store)
}
/// List.map3 : List a, List b, (a, b -> c) -> List c
fn list_map3(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_4(symbol, LowLevel::ListMap3, var_store)
}
/// Dict.hashTestOnly : k, v -> Nat
pub fn dict_hash_test_only(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_2(symbol, LowLevel::Hash, var_store)
@ -2150,8 +2220,8 @@ fn dict_empty(symbol: Symbol, var_store: &mut VarStore) -> Def {
}
}
/// Dict.singleton : k, v -> Dict k v
fn dict_singleton(symbol: Symbol, var_store: &mut VarStore) -> Def {
/// Dict.single : k, v -> Dict k v
fn dict_single(symbol: Symbol, var_store: &mut VarStore) -> Def {
let key_var = var_store.fresh();
let value_var = var_store.fresh();
let dict_var = var_store.fresh();
@ -2325,8 +2395,8 @@ fn set_empty(symbol: Symbol, var_store: &mut VarStore) -> Def {
}
}
/// Set.singleton : k -> Set k
fn set_singleton(symbol: Symbol, var_store: &mut VarStore) -> Def {
/// Set.single : k -> Set k
fn set_single(symbol: Symbol, var_store: &mut VarStore) -> Def {
let key_var = var_store.fresh();
let set_var = var_store.fresh();
let value_var = Variable::EMPTY_RECORD;
@ -2543,6 +2613,11 @@ fn num_rem(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
/// Num.isMultipleOf : Int, Int -> Bool
fn num_is_multiple_of(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_2(symbol, LowLevel::NumIsMultipleOf, var_store)
}
/// Num.neg : Num a -> Num a
fn num_neg(symbol: Symbol, var_store: &mut VarStore) -> Def {
let num_var = var_store.fresh();

View File

@ -56,7 +56,7 @@ pub enum Expr {
Num(Variable, i64),
// Int and Float store a variable to generate better error messages
Int(Variable, Variable, i64),
Int(Variable, Variable, i128),
Float(Variable, Variable, f64),
Str(InlinableString),
List {

View File

@ -45,7 +45,7 @@ pub fn int_expr_from_result(
) -> Expr {
// Int stores a variable to generate better error messages
match result {
Ok(int) => Expr::Int(var_store.fresh(), var_store.fresh(), int),
Ok(int) => Expr::Int(var_store.fresh(), var_store.fresh(), int.into()),
Err((raw, error)) => {
let runtime_error = InvalidInt(error, base, region, raw.into());

View File

@ -420,6 +420,9 @@ fn binop_to_function(binop: BinOp) -> (&'static str, &'static str) {
And => (ModuleName::BOOL, "and"),
Or => (ModuleName::BOOL, "or"),
Pizza => unreachable!("Cannot desugar the |> operator"),
Assignment => unreachable!("Cannot desugar the = operator"),
HasType => unreachable!("Cannot desugar the : operator"),
Backpassing => unreachable!("Cannot desugar the <- operator"),
}
}

View File

@ -8,9 +8,6 @@ use roc_can::operator;
use roc_can::scope::Scope;
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, Parser, State, SyntaxError};
use roc_problem::can::Problem;
use roc_region::all::{Located, Region};
use roc_types::subs::{VarStore, Variable};
@ -20,25 +17,6 @@ pub fn test_home() -> ModuleId {
ModuleIds::default().get_or_insert(&"Test".into())
}
#[allow(dead_code)]
pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Expr<'a>, SyntaxError<'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>>, SyntaxError<'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)
}
#[allow(dead_code)]
pub fn can_expr(expr_str: &str) -> CanExprOut {
can_expr_with(&Bump::new(), test_home(), expr_str)
@ -56,7 +34,7 @@ pub struct CanExprOut {
#[allow(dead_code)]
pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut {
let loc_expr = parse_loc_with(&arena, expr_str).unwrap_or_else(|e| {
let loc_expr = roc_parse::test_helpers::parse_loc_with(&arena, expr_str).unwrap_or_else(|e| {
panic!(
"can_expr_with() got a parse error when attempting to canonicalize:\n\n{:?} {:?}",
expr_str, e

View File

@ -41,7 +41,7 @@ mod test_can {
}
}
fn assert_can_int(input: &str, expected: i64) {
fn assert_can_int(input: &str, expected: i128) {
let arena = Bump::new();
let actual_out = can_expr_with(&arena, test_home(), input);
@ -196,32 +196,32 @@ mod test_can {
#[test]
fn hex_max() {
assert_can_int(&format!("0x{:x}", i64::MAX), i64::MAX);
assert_can_int(&format!("0x{:x}", i64::MAX), i64::MAX.into());
}
#[test]
fn hex_min() {
assert_can_int(&format!("-0x{:x}", i64::MAX as i128 + 1), i64::MIN);
assert_can_int(&format!("-0x{:x}", i64::MAX as i128 + 1), i64::MIN.into());
}
#[test]
fn oct_max() {
assert_can_int(&format!("0o{:o}", i64::MAX), i64::MAX);
assert_can_int(&format!("0o{:o}", i64::MAX), i64::MAX.into());
}
#[test]
fn oct_min() {
assert_can_int(&format!("-0o{:o}", i64::MAX as i128 + 1), i64::MIN);
assert_can_int(&format!("-0o{:o}", i64::MAX as i128 + 1), i64::MIN.into());
}
#[test]
fn bin_max() {
assert_can_int(&format!("0b{:b}", i64::MAX), i64::MAX);
assert_can_int(&format!("0b{:b}", i64::MAX), i64::MAX.into());
}
#[test]
fn bin_min() {
assert_can_int(&format!("-0b{:b}", i64::MAX as i128 + 1), i64::MIN);
assert_can_int(&format!("-0b{:b}", i64::MAX as i128 + 1), i64::MIN.into());
}
#[test]

View File

@ -390,6 +390,9 @@ fn fmt_bin_op<'a>(
operator::BinOp::And => buf.push_str("&&"),
operator::BinOp::Or => buf.push_str("||"),
operator::BinOp::Pizza => buf.push_str("|>"),
operator::BinOp::Assignment => unreachable!(),
operator::BinOp::HasType => unreachable!(),
operator::BinOp::Backpassing => unreachable!(),
}
buf.push(' ');

View File

@ -4,8 +4,6 @@ extern crate pretty_assertions;
extern crate indoc;
extern crate bumpalo;
extern crate roc_fmt;
#[macro_use]
extern crate roc_parse;
#[cfg(test)]
mod test_fmt {
@ -14,27 +12,15 @@ mod test_fmt {
use roc_fmt::annotation::{Formattable, Newlines, Parens};
use roc_fmt::def::fmt_def;
use roc_fmt::module::fmt_module;
use roc_parse::ast::{Attempting, Expr};
use roc_parse::blankspace::space0_before;
use roc_parse::module::{self, module_defs};
use roc_parse::parser::{Parser, State, SyntaxError};
fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Expr<'a>, SyntaxError<'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)
}
use roc_parse::parser::{Parser, State};
fn expr_formats_to(input: &str, expected: &str) {
let arena = Bump::new();
let input = input.trim_end();
let expected = expected.trim_end();
match parse_with(&arena, input) {
match roc_parse::test_helpers::parse_expr_with(&arena, input.trim()) {
Ok(actual) => {
let mut buf = String::new_in(&arena);
@ -55,8 +41,8 @@ mod test_fmt {
let src = src.trim_end();
let expected = expected.trim_end();
match module::header().parse(&arena, State::new_in(&arena, src.as_bytes(), Attempting::Module)) {
Ok((_, actual, state)) => {
match module::parse_header(&arena, State::new(src.as_bytes())) {
Ok((actual, state)) => {
let mut buf = String::new_in(&arena);
fmt_module(&mut buf, &actual);

View File

@ -7,8 +7,8 @@ use crate::llvm::build_hash::generic_hash;
use crate::llvm::build_list::{
allocate_list, empty_list, empty_polymorphic_list, list_append, list_concat, list_contains,
list_get_unsafe, list_join, list_keep_errs, list_keep_if, list_keep_oks, list_len, list_map,
list_map2, list_map_with_index, list_prepend, list_repeat, list_reverse, list_set, list_single,
list_sum, list_walk, list_walk_backwards,
list_map2, list_map3, list_map_with_index, list_prepend, list_repeat, list_reverse, list_set,
list_single, list_sum, list_walk, list_walk_backwards,
};
use crate::llvm::build_str::{
str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int, str_from_utf8,
@ -2492,9 +2492,6 @@ struct SwitchArgsIr<'a, 'ctx> {
}
fn const_i128<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, value: i128) -> IntValue<'ctx> {
// TODO verify the order [a, b] is correct for larger numbers when we can parse them
debug_assert!(value <= i64::MAX as i128);
// truncate the lower 64 bits
let value = value as u128;
let a = value as u64;
@ -3746,6 +3743,38 @@ fn run_low_level<'a, 'ctx, 'env>(
_ => unreachable!("invalid list layout"),
}
}
ListMap3 => {
debug_assert_eq!(args.len(), 4);
let (list1, list1_layout) = load_symbol_and_layout(scope, &args[0]);
let (list2, list2_layout) = load_symbol_and_layout(scope, &args[1]);
let (list3, list3_layout) = load_symbol_and_layout(scope, &args[2]);
let (func, func_layout) = load_symbol_and_layout(scope, &args[3]);
match (list1_layout, list2_layout, list3_layout) {
(
Layout::Builtin(Builtin::List(_, element1_layout)),
Layout::Builtin(Builtin::List(_, element2_layout)),
Layout::Builtin(Builtin::List(_, element3_layout)),
) => list_map3(
env,
layout_ids,
func,
func_layout,
list1,
list2,
list3,
element1_layout,
element2_layout,
element3_layout,
),
(Layout::Builtin(Builtin::EmptyList), _, _)
| (_, Layout::Builtin(Builtin::EmptyList), _)
| (_, _, Layout::Builtin(Builtin::EmptyList)) => empty_list(env),
_ => unreachable!("invalid list layout"),
}
}
ListMapWithIndex => {
// List.map : List before, (before -> after) -> List after
debug_assert_eq!(args.len(), 2);
@ -4046,8 +4075,8 @@ fn run_low_level<'a, 'ctx, 'env>(
}
NumAdd | NumSub | NumMul | NumLt | NumLte | NumGt | NumGte | NumRemUnchecked
| NumAddWrap | NumAddChecked | NumDivUnchecked | NumPow | NumPowInt | NumSubWrap
| NumSubChecked | NumMulWrap | NumMulChecked => {
| NumIsMultipleOf | NumAddWrap | NumAddChecked | NumDivUnchecked | NumPow | NumPowInt
| NumSubWrap | NumSubChecked | NumMulWrap | NumMulChecked => {
debug_assert_eq!(args.len(), 2);
let (lhs_arg, lhs_layout) = load_symbol_and_layout(scope, &args[0]);
@ -4749,6 +4778,44 @@ fn build_int_binop<'a, 'ctx, 'env>(
NumLt => bd.build_int_compare(SLT, lhs, rhs, "int_lt").into(),
NumLte => bd.build_int_compare(SLE, lhs, rhs, "int_lte").into(),
NumRemUnchecked => bd.build_int_signed_rem(lhs, rhs, "rem_int").into(),
NumIsMultipleOf => {
/* this builds the following construct
if rhs == 0 {
lhs == 0
} else {
let rem = lhs % rhs;
rem == 0
}
*/
let zero = rhs.get_type().const_zero();
let condition_rhs = bd.build_int_compare(IntPredicate::EQ, rhs, zero, "is_zero_rhs");
let condition_lhs = bd.build_int_compare(IntPredicate::EQ, lhs, zero, "is_zero_lhs");
let current_block = bd.get_insert_block().unwrap(); //block that we are in right now;
let else_block = env.context.append_basic_block(parent, "else"); //
let cont_block = env.context.append_basic_block(parent, "branchcont");
bd.build_conditional_branch(condition_rhs, cont_block, else_block);
bd.position_at_end(else_block);
let rem = bd.build_int_signed_rem(lhs, rhs, "int_rem");
let condition_rem = bd.build_int_compare(IntPredicate::EQ, rem, zero, "is_zero_rem");
bd.build_unconditional_branch(cont_block);
bd.position_at_end(cont_block);
let phi = bd.build_phi(env.context.bool_type(), "branch");
phi.add_incoming(&[
(&condition_lhs, current_block),
(&condition_rem, else_block),
]);
phi.as_basic_value()
}
NumDivUnchecked => bd.build_int_signed_div(lhs, rhs, "div_int").into(),
NumPowInt => call_bitcode_fn(env, &[lhs.into(), rhs.into()], &bitcode::NUM_POW_INT),
NumBitwiseAnd => bd.build_and(lhs, rhs, "int_bitwise_and").into(),

View File

@ -1305,6 +1305,114 @@ pub fn list_map2<'a, 'ctx, 'env>(
)
}
pub fn list_map3<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
transform: BasicValueEnum<'ctx>,
transform_layout: &Layout<'a>,
list1: BasicValueEnum<'ctx>,
list2: BasicValueEnum<'ctx>,
list3: BasicValueEnum<'ctx>,
element1_layout: &Layout<'a>,
element2_layout: &Layout<'a>,
element3_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
let return_layout = match transform_layout {
Layout::FunctionPointer(_, ret) => ret,
Layout::Closure(_, _, ret) => ret,
_ => unreachable!("not a callable layout"),
};
let u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic);
let list1_i128 = complex_bitcast(
env.builder,
list1,
env.context.i128_type().into(),
"to_i128",
);
let list2_i128 = complex_bitcast(
env.builder,
list2,
env.context.i128_type().into(),
"to_i128",
);
let list3_i128 = complex_bitcast(
env.builder,
list3,
env.context.i128_type().into(),
"to_i128",
);
let transform_ptr = builder.build_alloca(transform.get_type(), "transform_ptr");
env.builder.build_store(transform_ptr, transform);
let argument_layouts = [
element1_layout.clone(),
element2_layout.clone(),
element3_layout.clone(),
];
let stepper_caller =
build_transform_caller(env, layout_ids, transform_layout, &argument_layouts)
.as_global_value()
.as_pointer_value();
let a_width = env
.ptr_int()
.const_int(element1_layout.stack_size(env.ptr_bytes) as u64, false);
let b_width = env
.ptr_int()
.const_int(element2_layout.stack_size(env.ptr_bytes) as u64, false);
let c_width = env
.ptr_int()
.const_int(element3_layout.stack_size(env.ptr_bytes) as u64, false);
let d_width = env
.ptr_int()
.const_int(return_layout.stack_size(env.ptr_bytes) as u64, false);
let alignment = return_layout.alignment_bytes(env.ptr_bytes);
let alignment_iv = env.ptr_int().const_int(alignment as u64, false);
let dec_a = build_dec_wrapper(env, layout_ids, element1_layout);
let dec_b = build_dec_wrapper(env, layout_ids, element2_layout);
let dec_c = build_dec_wrapper(env, layout_ids, element3_layout);
let output = call_bitcode_fn(
env,
&[
list1_i128,
list2_i128,
list3_i128,
env.builder
.build_bitcast(transform_ptr, u8_ptr, "to_opaque"),
stepper_caller.into(),
alignment_iv.into(),
a_width.into(),
b_width.into(),
c_width.into(),
d_width.into(),
dec_a.as_global_value().as_pointer_value().into(),
dec_b.as_global_value().as_pointer_value().into(),
dec_c.as_global_value().as_pointer_value().into(),
],
bitcode::LIST_MAP3,
);
complex_bitcast(
env.builder,
output,
collection(env.context, env.ptr_bytes).into(),
"from_i128",
)
}
/// List.concat : List elem, List elem -> List elem
pub fn list_concat<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,

View File

@ -520,7 +520,7 @@ impl<
Literal::Int(x) => {
let reg = self.claim_general_reg(sym)?;
let val = *x;
ASM::mov_reg64_imm64(&mut self.buf, reg, val);
ASM::mov_reg64_imm64(&mut self.buf, reg, val as i64);
Ok(())
}
Literal::Float(x) => {

View File

@ -855,6 +855,19 @@ mod gen_num {
// );
// }
#[test]
fn max_i128() {
assert_evals_to!(
indoc!(
r#"
Num.maxI128
"#
),
i128::MAX,
i128
);
}
#[test]
fn num_max_int() {
assert_evals_to!(

View File

@ -22,7 +22,7 @@ use roc_mono::ir::{
CapturedSymbols, ExternalSpecializations, PartialProc, PendingSpecialization, Proc, Procs,
};
use roc_mono::layout::{Layout, LayoutCache, LayoutProblem};
use roc_parse::ast::{self, Attempting, StrLiteral, TypeAnnotation};
use roc_parse::ast::{self, StrLiteral, TypeAnnotation};
use roc_parse::header::{
ExposesEntry, ImportsEntry, PackageEntry, PackageOrPath, PlatformHeader, To, TypedIdent,
};
@ -2304,8 +2304,8 @@ fn load_pkg_config<'a>(
Ok(bytes_vec) => {
let parse_start = SystemTime::now();
let bytes = arena.alloc(bytes_vec);
let parse_state = parser::State::new_in(arena, bytes, Attempting::Module);
let parsed = roc_parse::module::header().parse(&arena, parse_state);
let parse_state = parser::State::new(bytes);
let parsed = roc_parse::module::parse_header(&arena, parse_state);
let parse_header_duration = parse_start.elapsed().unwrap();
// Insert the first entries for this module's timings
@ -2319,19 +2319,19 @@ fn load_pkg_config<'a>(
effect_module_timing.parse_header = parse_header_duration;
match parsed {
Ok((_, ast::Module::Interface { header }, _parse_state)) => {
Ok((ast::Module::Interface { header }, _parse_state)) => {
Err(LoadingProblem::UnexpectedHeader(format!(
"expected platform/package module, got Interface with header\n{:?}",
header
)))
}
Ok((_, ast::Module::App { header }, _parse_state)) => {
Ok((ast::Module::App { header }, _parse_state)) => {
Err(LoadingProblem::UnexpectedHeader(format!(
"expected platform/package module, got App with header\n{:?}",
header
)))
}
Ok((_, ast::Module::Platform { header }, parser_state)) => {
Ok((ast::Module::Platform { header }, parser_state)) => {
// make a Pkg-Config module that ultimately exposes `main` to the host
let pkg_config_module_msg = fabricate_pkg_config_module(
arena,
@ -2359,8 +2359,8 @@ fn load_pkg_config<'a>(
Ok(Msg::Many(vec![effects_module_msg, pkg_config_module_msg]))
}
Err((_, fail, _)) => Err(LoadingProblem::ParsingFailed(
fail.into_parse_problem(filename, bytes),
Err(fail) => Err(LoadingProblem::ParsingFailed(
SyntaxError::Header(fail).into_parse_problem(filename, bytes),
)),
}
}
@ -2474,8 +2474,8 @@ fn parse_header<'a>(
start_time: SystemTime,
) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> {
let parse_start = SystemTime::now();
let parse_state = parser::State::new_in(arena, src_bytes, Attempting::Module);
let parsed = roc_parse::module::header().parse(&arena, parse_state);
let parse_state = parser::State::new(src_bytes);
let parsed = roc_parse::module::parse_header(&arena, parse_state);
let parse_header_duration = parse_start.elapsed().unwrap();
// Insert the first entries for this module's timings
@ -2485,7 +2485,7 @@ fn parse_header<'a>(
module_timing.parse_header = parse_header_duration;
match parsed {
Ok((_, ast::Module::Interface { header }, parse_state)) => {
Ok((ast::Module::Interface { header }, parse_state)) => {
let header_src = unsafe {
let chomped = src_bytes.len() - parse_state.bytes.len();
std::str::from_utf8_unchecked(&src_bytes[..chomped])
@ -2514,7 +2514,7 @@ fn parse_header<'a>(
module_timing,
))
}
Ok((_, ast::Module::App { header }, parse_state)) => {
Ok((ast::Module::App { header }, parse_state)) => {
let mut pkg_config_dir = filename.clone();
pkg_config_dir.pop();
@ -2623,7 +2623,7 @@ fn parse_header<'a>(
},
}
}
Ok((_, ast::Module::Platform { header }, _parse_state)) => Ok(fabricate_effects_module(
Ok((ast::Module::Platform { header }, _parse_state)) => Ok(fabricate_effects_module(
arena,
&"",
module_ids,
@ -2632,8 +2632,8 @@ fn parse_header<'a>(
header,
module_timing,
)),
Err((_, fail, _)) => Err(LoadingProblem::ParsingFailed(
fail.into_parse_problem(filename, src_bytes),
Err(fail) => Err(LoadingProblem::ParsingFailed(
SyntaxError::Header(fail).into_parse_problem(filename, src_bytes),
)),
}
}

View File

@ -16,8 +16,8 @@ Model position :
initialModel : position -> Model position
initialModel = \start ->
{ evaluated : Set.empty
, openSet : Set.singleton start
, costs : Map.singleton start 0.0
, openSet : Set.single start
, costs : Dict.single start 0.0
, cameFrom : Map.empty
}

View File

@ -16,8 +16,8 @@ Model position :
initialModel : position -> Model position
initialModel = \start ->
{ evaluated : Set.empty
, openSet : Set.singleton start
, costs : Dict.singleton start 0.0
, openSet : Set.single start
, costs : Dict.single start 0.0
, cameFrom : Dict.empty
}

View File

@ -28,6 +28,7 @@ pub enum LowLevel {
ListJoin,
ListMap,
ListMap2,
ListMap3,
ListMapWithIndex,
ListKeepIf,
ListWalk,
@ -64,6 +65,7 @@ pub enum LowLevel {
NumCompare,
NumDivUnchecked,
NumRemUnchecked,
NumIsMultipleOf,
NumAbs,
NumNeg,
NumSin,

View File

@ -42,6 +42,9 @@ pub enum BinOp {
And,
Or,
Pizza, // lowest precedence
Assignment,
HasType,
Backpassing,
}
#[derive(Clone, Debug, PartialEq, Eq)]
@ -83,6 +86,7 @@ impl BinOp {
Equals | NotEquals | LessThan | GreaterThan | LessThanOrEq | GreaterThanOrEq => {
NonAssociative
}
Assignment | HasType | Backpassing => unreachable!(),
}
}
@ -95,6 +99,7 @@ impl BinOp {
And => 3,
Or => 2,
Pizza => 1,
Assignment | HasType | Backpassing => unreachable!(),
}
}
}
@ -131,6 +136,9 @@ impl std::fmt::Display for BinOp {
And => "&&",
Or => "||",
Pizza => "|>",
Assignment => "=",
HasType => ":",
Backpassing => "<-",
};
write!(f, "{}", as_str)

View File

@ -855,6 +855,8 @@ define_builtins! {
94 NUM_NATURAL: "Natural" imported
95 NUM_NAT: "Nat" imported
96 NUM_INT_CAST: "intCast"
97 NUM_MAX_I128: "maxI128"
98 NUM_IS_MULTIPLE_OF: "isMultipleOf"
}
2 BOOL: "Bool" => {
@ -910,6 +912,7 @@ define_builtins! {
22 LIST_KEEP_ERRS: "keepErrs"
23 LIST_MAP_WITH_INDEX: "mapWithIndex"
24 LIST_MAP2: "map2"
25 LIST_MAP3: "map3"
}
5 RESULT: "Result" => {
0 RESULT_RESULT: "Result" imported // the Result.Result type alias
@ -922,7 +925,7 @@ define_builtins! {
0 DICT_DICT: "Dict" imported // the Dict.Dict type alias
1 DICT_AT_DICT: "@Dict" // the Dict.@Dict private tag
2 DICT_EMPTY: "empty"
3 DICT_SINGLETON: "singleton"
3 DICT_SINGLE: "single"
4 DICT_GET: "get"
5 DICT_GET_RESULT: "#get_result" // symbol used in the definition of Dict.get
6 DICT_WALK: "walk"
@ -948,7 +951,7 @@ define_builtins! {
0 SET_SET: "Set" imported // the Set.Set type alias
1 SET_AT_SET: "@Set" // the Set.@Set private tag
2 SET_EMPTY: "empty"
3 SET_SINGLETON: "singleton"
3 SET_SINGLE: "single"
4 SET_LEN: "len"
5 SET_INSERT: "insert"
6 SET_REMOVE: "remove"

View File

@ -652,6 +652,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
ListJoin => arena.alloc_slice_copy(&[irrelevant]),
ListMap | ListMapWithIndex => arena.alloc_slice_copy(&[owned, irrelevant]),
ListMap2 => arena.alloc_slice_copy(&[owned, owned, irrelevant]),
ListMap3 => arena.alloc_slice_copy(&[owned, owned, owned, irrelevant]),
ListKeepIf | ListKeepOks | ListKeepErrs => arena.alloc_slice_copy(&[owned, borrowed]),
ListContains => arena.alloc_slice_copy(&[borrowed, irrelevant]),
ListWalk => arena.alloc_slice_copy(&[owned, irrelevant, owned]),
@ -666,10 +667,9 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
And | Or | NumAdd | NumAddWrap | NumAddChecked | NumSub | NumSubWrap | NumSubChecked
| NumMul | NumMulWrap | NumMulChecked | NumGt | NumGte | NumLt | NumLte | NumCompare
| NumDivUnchecked | NumRemUnchecked | NumPow | NumPowInt | NumBitwiseAnd
| NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy | NumShiftRightBy | NumShiftRightZfBy => {
arena.alloc_slice_copy(&[irrelevant, irrelevant])
}
| NumDivUnchecked | NumRemUnchecked | NumIsMultipleOf | NumPow | NumPowInt
| NumBitwiseAnd | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy | NumShiftRightBy
| NumShiftRightZfBy => arena.alloc_slice_copy(&[irrelevant, irrelevant]),
NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumRound | NumCeiling | NumFloor
| NumToFloat | Not | NumIsFinite | NumAtan | NumAcos | NumAsin | NumIntCast => {

View File

@ -1153,7 +1153,7 @@ fn test_to_equality<'a>(
// (e.g. record pattern guard matches)
debug_assert!(union.alternatives.len() > 1);
let lhs = Expr::Literal(Literal::Int(tag_id as i64));
let lhs = Expr::Literal(Literal::Int(tag_id as i128));
let mut field_layouts =
bumpalo::collections::Vec::with_capacity_in(arguments.len(), env.arena);
@ -1189,7 +1189,7 @@ fn test_to_equality<'a>(
Test::IsInt(test_int) => {
// TODO don't downcast i128 here
debug_assert!(test_int <= i64::MAX as i128);
let lhs = Expr::Literal(Literal::Int(test_int as i64));
let lhs = Expr::Literal(Literal::Int(test_int as i128));
let lhs_symbol = env.unique_symbol();
stores.push((lhs_symbol, Layout::Builtin(Builtin::Int64), lhs));

View File

@ -918,7 +918,7 @@ impl ModifyRc {
#[derive(Clone, Debug, PartialEq)]
pub enum Literal<'a> {
// Literals
Int(i64),
Int(i128),
Float(f64),
Str(&'a str),
/// Closed tag unions containing exactly two (0-arity) tags compile to Expr::Bool,
@ -2549,13 +2549,13 @@ pub fn with_hole<'a>(
Num(var, num) => match num_argument_to_int_or_float(env.subs, env.ptr_bytes, var, false) {
IntOrFloat::SignedIntType(precision) => Stmt::Let(
assigned,
Expr::Literal(Literal::Int(num)),
Expr::Literal(Literal::Int(num.into())),
Layout::Builtin(int_precision_to_builtin(precision)),
hole,
),
IntOrFloat::UnsignedIntType(precision) => Stmt::Let(
assigned,
Expr::Literal(Literal::Int(num)),
Expr::Literal(Literal::Int(num.into())),
Layout::Builtin(int_precision_to_builtin(precision)),
hole,
),
@ -3075,7 +3075,7 @@ pub fn with_hole<'a>(
// define the tag id
stmt = Stmt::Let(
tag_id_symbol,
Expr::Literal(Literal::Int(tag_id as i64)),
Expr::Literal(Literal::Int(tag_id as i128)),
Layout::Builtin(TAG_SIZE),
arena.alloc(stmt),
);
@ -3760,7 +3760,7 @@ pub fn with_hole<'a>(
tag_symbols.push(tag_id_symbol);
tag_symbols.extend(symbols);
let expr1 = Expr::Literal(Literal::Int(tag_id as i64));
let expr1 = Expr::Literal(Literal::Int(tag_id as i128));
let expr2 = Expr::Tag {
tag_id,
tag_layout,
@ -5588,7 +5588,7 @@ fn reuse_function_symbol<'a>(
tag_symbols.push(tag_id_symbol);
tag_symbols.extend(symbols);
let expr1 = Expr::Literal(Literal::Int(tag_id as i64));
let expr1 = Expr::Literal(Literal::Int(tag_id as i128));
let expr2 = Expr::Tag {
tag_id,
tag_layout,

View File

@ -589,33 +589,6 @@ impl<'a> Spaceable<'a> for Def<'a> {
}
}
/// What we're currently attempting to parse, e.g.
/// "currently attempting to parse a list." This helps error messages!
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Attempting {
LineComment,
List,
Keyword,
StrLiteral,
RecordLiteral,
RecordFieldLabel,
InterpolatedString,
NumberLiteral,
UnicodeEscape,
ClosureParams,
ClosureBody,
Def,
Module,
Record,
Identifier,
HexDigit,
ConcreteType,
TypeVariable,
WhenCondition,
WhenBranch,
TODO,
}
impl<'a> Expr<'a> {
pub fn loc_ref(&'a self, region: Region) -> Loc<&'a Self> {
Loc {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +1,11 @@
use crate::blankspace::space0;
use crate::ast::{CommentOrNewline, Spaceable, StrLiteral, TypeAnnotation};
use crate::blankspace::space0_e;
use crate::ident::lowercase_ident;
use crate::module::package_name;
use crate::parser::{ascii_char, optional, Either, Parser, Progress::*, State, SyntaxError};
use crate::string_literal;
use crate::{
ast::{CommentOrNewline, Spaceable, StrLiteral, TypeAnnotation},
parser::specialize,
use crate::parser::Progress::{self, *};
use crate::parser::{
specialize, word1, EPackageEntry, EPackageName, EPackageOrPath, Parser, State,
};
use crate::string_literal;
use bumpalo::collections::Vec;
use inlinable_string::InlinableString;
use roc_region::all::Loc;
@ -242,18 +241,32 @@ impl<'a> Spaceable<'a> for PackageEntry<'a> {
}
}
pub fn package_entry<'a>() -> impl Parser<'a, PackageEntry<'a>, SyntaxError<'a>> {
pub fn package_entry<'a>() -> impl Parser<'a, PackageEntry<'a>, EPackageEntry<'a>> {
move |arena, state| {
// You may optionally have a package shorthand,
// e.g. "uc" in `uc: roc/unicode 1.0.0`
//
// (Indirect dependencies don't have a shorthand.)
let (_, opt_shorthand, state) = optional(and!(
skip_second!(lowercase_ident(), ascii_char(b':')),
space0(1)
let min_indent = 1;
let (_, opt_shorthand, state) = maybe!(and!(
skip_second!(
specialize(|_, r, c| EPackageEntry::Shorthand(r, c), lowercase_ident()),
word1(b':', EPackageEntry::Colon)
),
space0_e(
min_indent,
EPackageEntry::Space,
EPackageEntry::IndentPackageOrPath
)
))
.parse(arena, state)?;
let (_, package_or_path, state) = loc!(specialize(
EPackageEntry::BadPackageOrPath,
package_or_path()
))
.parse(arena, state)?;
let (_, package_or_path, state) = loc!(package_or_path()).parse(arena, state)?;
let entry = match opt_shorthand {
Some((shorthand, spaces_after_shorthand)) => PackageEntry::Entry {
@ -272,27 +285,117 @@ pub fn package_entry<'a>() -> impl Parser<'a, PackageEntry<'a>, SyntaxError<'a>>
}
}
pub fn package_or_path<'a>() -> impl Parser<'a, PackageOrPath<'a>, SyntaxError<'a>> {
map!(
either!(
specialize(
|e, r, c| SyntaxError::Expr(crate::parser::EExpr::Str(e, r, c)),
string_literal::parse()
),
and!(
package_name(),
skip_first!(one_or_more!(ascii_char(b' ')), package_version())
)
pub fn package_or_path<'a>() -> impl Parser<'a, PackageOrPath<'a>, EPackageOrPath<'a>> {
one_of![
map!(
specialize(EPackageOrPath::BadPath, string_literal::parse()),
PackageOrPath::Path
),
|answer| {
match answer {
Either::First(str_literal) => PackageOrPath::Path(str_literal),
Either::Second((name, version)) => PackageOrPath::Package(name, version),
}
}
)
map!(
and!(
specialize(EPackageOrPath::BadPackage, package_name()),
skip_first!(skip_spaces(), package_version())
),
|(name, version)| { PackageOrPath::Package(name, version) }
)
]
}
fn package_version<'a>() -> impl Parser<'a, Version<'a>, SyntaxError<'a>> {
fn skip_spaces<'a, T>() -> impl Parser<'a, (), T>
where
T: 'a,
{
|_, mut state: State<'a>| {
let mut chomped = 0;
let mut it = state.bytes.iter();
while let Some(b' ') = it.next() {
chomped += 1;
}
if chomped == 0 {
Ok((NoProgress, (), state))
} else {
state.column += chomped;
state.bytes = it.as_slice();
Ok((MadeProgress, (), state))
}
}
}
fn package_version<'a, T>() -> impl Parser<'a, Version<'a>, T>
where
T: 'a,
{
move |_, _| todo!("TODO parse package version")
}
#[inline(always)]
pub fn package_name<'a>() -> impl Parser<'a, PackageName<'a>, EPackageName> {
use encode_unicode::CharExt;
// e.g. rtfeldman/blah
//
// Package names and accounts can be capitalized and can contain dashes.
// They cannot contain underscores or other special characters.
// They must be ASCII.
|_, mut state: State<'a>| match chomp_package_part(state.bytes) {
Err(progress) => Err((
progress,
EPackageName::Account(state.line, state.column),
state,
)),
Ok(account) => {
let mut chomped = account.len();
if let Ok(('/', width)) = char::from_utf8_slice_start(&state.bytes[chomped..]) {
chomped += width;
match chomp_package_part(&state.bytes[chomped..]) {
Err(progress) => Err((
progress,
EPackageName::Pkg(state.line, state.column + chomped as u16),
state,
)),
Ok(pkg) => {
chomped += pkg.len();
state.column += chomped as u16;
state.bytes = &state.bytes[chomped..];
let value = PackageName { account, pkg };
Ok((MadeProgress, value, state))
}
}
} else {
Err((
MadeProgress,
EPackageName::MissingSlash(state.line, state.column + chomped as u16),
state,
))
}
}
}
}
fn chomp_package_part(buffer: &[u8]) -> Result<&str, Progress> {
use encode_unicode::CharExt;
let mut chomped = 0;
while let Ok((ch, width)) = char::from_utf8_slice_start(&buffer[chomped..]) {
if ch == '-' || ch.is_ascii_alphanumeric() {
chomped += width;
} else {
// we're done
break;
}
}
if chomped == 0 {
Err(Progress::NoProgress)
} else {
let name = unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) };
Ok(name)
}
}

View File

@ -1,14 +1,7 @@
use crate::ast::Attempting;
use crate::keyword;
use crate::parser::Progress::{self, *};
use crate::parser::{
peek_utf8_char, unexpected, BadInputError, Col, EExpr, ParseResult, Parser, Row, State,
SyntaxError,
};
use bumpalo::collections::string::String;
use crate::parser::{BadInputError, Col, EExpr, ParseResult, Parser, Row, State};
use bumpalo::collections::vec::Vec;
use bumpalo::Bump;
use roc_region::all::Region;
/// The parser accepts all of these in any position where any one of them could
/// appear. This way, canonicalization can give more helpful error messages like
@ -61,82 +54,43 @@ impl<'a> Ident<'a> {
}
}
pub fn ident<'a>() -> impl Parser<'a, Ident<'a>, SyntaxError<'a>> {
crate::parser::specialize(|e, _, _| SyntaxError::Expr(e), parse_ident_help)
}
pub fn global_tag_or_ident<'a, F>(pred: F) -> impl Parser<'a, &'a str, SyntaxError<'a>>
where
F: Fn(char) -> bool,
{
move |arena, mut state: State<'a>| {
// pred will determine if this is a tag or ident (based on capitalization)
let (first_letter, bytes_parsed) = match peek_utf8_char(&state) {
Ok((first_letter, bytes_parsed)) => {
if !pred(first_letter) {
return Err(unexpected(0, Attempting::RecordFieldLabel, state));
}
(first_letter, bytes_parsed)
}
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)?;
while !state.bytes.is_empty() {
match peek_utf8_char(&state) {
Ok((ch, bytes_parsed)) => {
// After the first character, only these are allowed:
//
// * Unicode alphabetic chars - you might include `鹏` if that's clear to your readers
// * ASCII digits - e.g. `1` but not `¾`, both of which pass .is_numeric()
// * A ':' indicating the end of the field
if ch.is_alphabetic() || ch.is_ascii_digit() {
buf.push(ch);
state = state.advance_without_indenting(bytes_parsed)?;
} else {
// This is the end of the field. We're done!
break;
}
}
Err(reason) => return state.fail(arena, MadeProgress, reason),
};
}
Ok((MadeProgress, buf.into_bump_str(), state))
}
}
/// This could be:
///
/// * A record field, e.g. "email" in `.email` or in `email:`
/// * A named pattern match, e.g. "foo" in `foo =` or `foo ->` or `\foo ->`
pub fn lowercase_ident<'a>() -> impl Parser<'a, &'a str, SyntaxError<'a>> {
pub fn lowercase_ident<'a>() -> impl Parser<'a, &'a str, ()> {
move |_, state: State<'a>| match chomp_lowercase_part(state.bytes) {
Err(progress) => Err((progress, (), state)),
Ok(ident) => {
if crate::keyword::KEYWORDS.iter().any(|kw| &ident == kw) {
Err((NoProgress, (), state))
} else {
let width = ident.len();
match state.advance_without_indenting_ee(width, |_, _| ()) {
Ok(state) => Ok((MadeProgress, ident, state)),
Err(bad) => Err(bad),
}
}
}
}
}
pub fn tag_name<'a>() -> impl Parser<'a, &'a str, ()> {
move |arena, state: State<'a>| {
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)
|| (ident == keyword::WHEN)
|| (ident == keyword::IS)
|| (ident == keyword::AS)
{
// TODO Calculate the correct region based on state
let region = Region::zero();
Err((MadeProgress, SyntaxError::ReservedKeyword(region), state))
if state.bytes.starts_with(b"@") {
match chomp_private_tag(state.bytes, state.line, state.column) {
Err(BadIdent::Start(_, _)) => Err((NoProgress, (), state)),
Err(_) => Err((MadeProgress, (), state)),
Ok(ident) => {
let width = ident.len();
match state.advance_without_indenting_ee(width, |_, _| ()) {
Ok(state) => Ok((MadeProgress, ident, state)),
Err(bad) => Err(bad),
}
}
}
} else {
Ok((MadeProgress, ident, state))
uppercase_ident().parse(arena, state)
}
}
}
@ -146,30 +100,34 @@ pub fn lowercase_ident<'a>() -> impl Parser<'a, &'a str, SyntaxError<'a>> {
/// * A module name
/// * A type name
/// * A global tag
pub fn uppercase_ident<'a>() -> impl Parser<'a, &'a str, SyntaxError<'a>> {
global_tag_or_ident(|first_char| first_char.is_uppercase())
}
pub fn unqualified_ident<'a>() -> impl Parser<'a, &'a str, SyntaxError<'a>> {
global_tag_or_ident(|first_char| first_char.is_alphabetic())
}
pub fn join_module_parts<'a>(arena: &'a Bump, module_parts: &[&str]) -> &'a str {
let capacity = module_parts.len() * 3; // Module parts tend to be 3+ characters.
let mut buf = String::with_capacity_in(capacity, arena);
let mut any_parts_added = false;
for part in module_parts {
if any_parts_added {
buf.push('.');
} else {
any_parts_added = true;
pub fn uppercase_ident<'a>() -> impl Parser<'a, &'a str, ()> {
move |_, state: State<'a>| match chomp_uppercase_part(state.bytes) {
Err(progress) => Err((progress, (), state)),
Ok(ident) => {
let width = ident.len();
match state.advance_without_indenting_ee(width, |_, _| ()) {
Ok(state) => Ok((MadeProgress, ident, state)),
Err(bad) => Err(bad),
}
}
buf.push_str(part);
}
}
buf.into_bump_str()
pub fn unqualified_ident<'a>() -> impl Parser<'a, &'a str, ()> {
move |_, state: State<'a>| match chomp_part(|c| c.is_alphabetic(), state.bytes) {
Err(progress) => Err((progress, (), state)),
Ok(ident) => {
if crate::keyword::KEYWORDS.iter().any(|kw| &ident == kw) {
Err((MadeProgress, (), state))
} else {
let width = ident.len();
match state.advance_without_indenting_ee(width, |_, _| ()) {
Ok(state) => Ok((MadeProgress, ident, state)),
Err(bad) => Err(bad),
}
}
}
}
}
macro_rules! advance_state {
@ -184,10 +142,10 @@ pub fn parse_ident_help<'a>(
arena: &'a Bump,
state: State<'a>,
) -> ParseResult<'a, Ident<'a>, EExpr<'a>> {
let initial = state.clone();
let initial = state;
match parse_ident_help_help(arena, state) {
Ok((progress, (ident, _), state)) => {
Ok((progress, ident, state)) => {
if let Ident::Access { module_name, parts } = ident {
if module_name.is_empty() {
if let Some(first) = parts.first() {
@ -212,7 +170,7 @@ pub fn parse_ident_help<'a>(
Err((MadeProgress, fail, state)) => match fail {
BadIdent::Start(r, c) => Err((NoProgress, EExpr::Start(r, c), state)),
BadIdent::Space(e, r, c) => Err((NoProgress, EExpr::Space(e, r, c), state)),
_ => malformed_identifier(initial.bytes, fail, arena, state),
_ => malformed_identifier(initial.bytes, fail, state),
},
}
}
@ -220,294 +178,367 @@ pub fn parse_ident_help<'a>(
fn malformed_identifier<'a>(
initial_bytes: &'a [u8],
problem: BadIdent,
_arena: &'a Bump,
mut state: State<'a>,
) -> ParseResult<'a, Ident<'a>, EExpr<'a>> {
// skip forward to the next non-identifier character
while !state.bytes.is_empty() {
match peek_utf8_char(&state) {
Ok((ch, bytes_parsed)) => {
// We can't use ch.is_alphanumeric() here because that passes for
// things that are "numeric" but not ASCII digits, like `¾`
if ch == '.' || ch == '_' || ch.is_alphabetic() || ch.is_ascii_digit() {
state = state.advance_without_indenting_ee(bytes_parsed, |r, c| {
EExpr::Space(crate::parser::BadInputError::LineTooLong, r, c)
})?;
continue;
} else {
break;
}
}
Err(_reason) => {
break;
}
let chomped = chomp_malformed(state.bytes);
let delta = initial_bytes.len() - state.bytes.len();
let parsed_str = unsafe { std::str::from_utf8_unchecked(&initial_bytes[..chomped + delta]) };
state = state.advance_without_indenting_ee(chomped, |r, c| {
EExpr::Space(crate::parser::BadInputError::LineTooLong, r, c)
})?;
Ok((MadeProgress, Ident::Malformed(parsed_str, problem), state))
}
/// skip forward to the next non-identifier character
pub fn chomp_malformed(bytes: &[u8]) -> usize {
use encode_unicode::CharExt;
let mut chomped = 0;
while let Ok((ch, width)) = char::from_utf8_slice_start(&bytes[chomped..]) {
// We can't use ch.is_alphanumeric() here because that passes for
// things that are "numeric" but not ASCII digits, like `¾`
if ch == '.' || ch == '_' || ch.is_alphabetic() || ch.is_ascii_digit() {
chomped += width;
continue;
} else {
break;
}
}
let parsed = &initial_bytes[..(initial_bytes.len() - state.bytes.len())];
let parsed_str = unsafe { std::str::from_utf8_unchecked(parsed) };
Ok((MadeProgress, Ident::Malformed(parsed_str, problem), state))
chomped
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BadIdent {
Start(Row, Col),
Space(BadInputError, Row, Col),
Underscore(Row, Col),
QualifiedTag(Row, Col),
PrivateTagNotUppercase(Row, Col),
PartStartsWithNumber(Row, Col),
WeirdAccessor(Row, Col),
PrivateTagFieldAccess(Row, Col),
WeirdDotAccess(Row, Col),
WeirdDotQualified(Row, Col),
DoubleDot(Row, Col),
StrayDot(Row, Col),
BadPrivateTag(Row, Col),
}
/// Parse an identifier into a string.
///
/// This is separate from the `ident` Parser because string interpolation
/// wants to use it this way.
pub fn parse_ident_help_help<'a>(
fn chomp_lowercase_part(buffer: &[u8]) -> Result<&str, Progress> {
chomp_part(|c: char| c.is_lowercase(), buffer)
}
fn chomp_uppercase_part(buffer: &[u8]) -> Result<&str, Progress> {
chomp_part(|c: char| c.is_uppercase(), buffer)
}
#[inline(always)]
fn chomp_part<F>(leading_is_good: F, buffer: &[u8]) -> Result<&str, Progress>
where
F: Fn(char) -> bool,
{
use encode_unicode::CharExt;
let mut chomped = 0;
if let Ok((ch, width)) = char::from_utf8_slice_start(&buffer[chomped..]) {
if leading_is_good(ch) {
chomped += width;
} else {
return Err(NoProgress);
}
}
while let Ok((ch, width)) = char::from_utf8_slice_start(&buffer[chomped..]) {
if ch.is_alphabetic() || ch.is_ascii_digit() {
chomped += width;
} else {
// we're done
break;
}
}
if chomped == 0 {
Err(NoProgress)
} else {
let name = unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) };
Ok(name)
}
}
/// a `.foo` accessor function
fn chomp_accessor(buffer: &[u8], row: Row, col: Col) -> Result<&str, BadIdent> {
// assumes the leading `.` has been chomped already
use encode_unicode::CharExt;
match chomp_lowercase_part(buffer) {
Ok(name) => {
let chomped = name.len();
if let Ok(('.', _)) = char::from_utf8_slice_start(&buffer[chomped..]) {
Err(BadIdent::WeirdAccessor(row, col))
} else {
Ok(name)
}
}
Err(_) => {
// we've already made progress with the initial `.`
Err(BadIdent::StrayDot(row, col + 1))
}
}
}
/// a `@Token` private tag
fn chomp_private_tag(buffer: &[u8], row: Row, col: Col) -> Result<&str, BadIdent> {
// assumes the leading `@` has NOT been chomped already
debug_assert_eq!(buffer.get(0), Some(&b'@'));
use encode_unicode::CharExt;
match chomp_uppercase_part(&buffer[1..]) {
Ok(name) => {
let width = 1 + name.len();
if let Ok(('.', _)) = char::from_utf8_slice_start(&buffer[width..]) {
Err(BadIdent::BadPrivateTag(row, col + width as u16))
} else {
let value = unsafe { std::str::from_utf8_unchecked(&buffer[..width]) };
Ok(value)
}
}
Err(_) => Err(BadIdent::BadPrivateTag(row, col + 1)),
}
}
fn chomp_identifier_chain<'a>(
arena: &'a Bump,
mut state: State<'a>,
) -> ParseResult<'a, (Ident<'a>, Option<char>), BadIdent> {
let mut part_buf = String::new_in(arena); // The current "part" (parts are dot-separated.)
let mut capitalized_parts: Vec<&'a str> = Vec::new_in(arena);
let mut noncapitalized_parts: Vec<&'a str> = Vec::new_in(arena);
let mut is_capitalized;
let is_accessor_fn;
let mut is_private_tag = false;
buffer: &'a [u8],
row: Row,
col: Col,
) -> Result<(u16, Ident<'a>), (u16, BadIdent)> {
use encode_unicode::CharExt;
// 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) {
Ok((first_ch, bytes_parsed)) => {
if first_ch.is_alphabetic() {
part_buf.push(first_ch);
let first_is_uppercase;
let mut chomped = 0;
is_capitalized = first_ch.is_uppercase();
is_accessor_fn = false;
match char::from_utf8_slice_start(&buffer[chomped..]) {
Ok((ch, width)) => match ch {
'.' => match chomp_accessor(&buffer[1..], row, col) {
Ok(accessor) => {
let bytes_parsed = 1 + accessor.len();
state = advance_state!(state, bytes_parsed)?;
} else if first_ch == '.' {
is_capitalized = false;
is_accessor_fn = true;
state = advance_state!(state, bytes_parsed)?;
} else if first_ch == '@' {
state = advance_state!(state, 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 = advance_state!(state, next_bytes_parsed)?;
part_buf.push('@');
part_buf.push(next_ch);
is_private_tag = true;
is_capitalized = true;
is_accessor_fn = false;
} else {
return Err((
MadeProgress,
BadIdent::PrivateTagNotUppercase(state.line, state.column),
state,
));
}
}
Err(_reason) => {
return Err((
MadeProgress,
BadIdent::PrivateTagNotUppercase(state.line, state.column),
state,
));
}
return Ok((bytes_parsed as u16, Ident::AccessorFunction(accessor)));
}
} else {
return Err((NoProgress, BadIdent::Start(state.line, state.column), state));
Err(fail) => return Err((1, fail)),
},
'@' => match chomp_private_tag(buffer, row, col) {
Ok(tagname) => {
let bytes_parsed = tagname.len();
return Ok((bytes_parsed as u16, Ident::PrivateTag(tagname)));
}
Err(fail) => return Err((1, fail)),
},
c if c.is_alphabetic() => {
// fall through
chomped += width;
first_is_uppercase = c.is_uppercase();
}
}
Err(_reason) => {
return Err((NoProgress, BadIdent::Start(state.line, state.column), state));
_ => {
return Err((0, BadIdent::Start(row, col)));
}
},
Err(_) => return Err((0, BadIdent::Start(row, col))),
}
while let Ok((ch, width)) = char::from_utf8_slice_start(&buffer[chomped..]) {
if ch.is_alphabetic() || ch.is_ascii_digit() {
chomped += width;
} else {
// we're done
break;
}
}
while !state.bytes.is_empty() {
match peek_utf8_char(&state) {
Ok((ch, bytes_parsed)) => {
// After the first character, only these are allowed:
//
// * Unicode alphabetic chars - you might name a variable `鹏` if that's clear to your readers
// * ASCII digits - e.g. `1` but not `¾`, both of which pass .is_numeric()
// * A dot ('.')
if ch.is_alphabetic() {
if part_buf.is_empty() {
// Capitalization is determined by the first character in the part.
is_capitalized = ch.is_uppercase();
}
part_buf.push(ch);
} else if ch.is_ascii_digit() {
// Parts may not start with numbers!
if part_buf.is_empty() {
return Err((
MadeProgress,
BadIdent::PartStartsWithNumber(state.line, state.column),
state,
));
}
part_buf.push(ch);
} else if ch == '.' {
// There are two posssible errors here:
//
// 1. Having two consecutive dots is an error.
// 2. Having capitalized parts after noncapitalized (e.g. `foo.Bar`) is an error.
if part_buf.is_empty() {
return Err((
MadeProgress,
BadIdent::DoubleDot(state.line, state.column),
state,
));
}
if is_capitalized && !noncapitalized_parts.is_empty() {
return Err((
MadeProgress,
BadIdent::WeirdDotQualified(state.line, state.column),
state,
));
}
if is_capitalized {
capitalized_parts.push(part_buf.into_bump_str());
} else {
noncapitalized_parts.push(part_buf.into_bump_str());
}
// Now that we've recorded the contents of the current buffer, reset it.
part_buf = String::new_in(arena);
} else if ch == '_' {
// we don't allow underscores in the middle of an identifier
// but still parse them (and generate a malformed identifier)
// to give good error messages for this case
state = advance_state!(state, bytes_parsed)?;
return Err((
MadeProgress,
BadIdent::Underscore(state.line, state.column),
state,
));
} else {
// This must be the end of the identifier. We're done!
break;
if let Ok(('.', _)) = char::from_utf8_slice_start(&buffer[chomped..]) {
let module_name = if first_is_uppercase {
match chomp_module_chain(&buffer[chomped..]) {
Ok(width) => {
chomped += width as usize;
unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) }
}
state = advance_state!(state, bytes_parsed)?;
}
Err(_reason) => {
//
return Err((
MadeProgress,
BadIdent::Start(state.line, state.column),
state,
));
}
}
}
if part_buf.is_empty() {
// We probably had a trailing dot, e.g. `Foo.bar.` - this is malformed!
//
// This condition might also occur if we encounter a malformed accessor like `.|`
//
// If we made it this far and don't have a next_char, then necessarily
// we have consumed a '.' char previously.
let fail = if noncapitalized_parts.is_empty() {
if capitalized_parts.is_empty() {
BadIdent::StrayDot(state.line, state.column)
} else {
BadIdent::WeirdDotQualified(state.line, state.column)
Err(MadeProgress) => todo!(),
Err(NoProgress) => unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) },
}
} else {
BadIdent::WeirdDotAccess(state.line, state.column)
""
};
return Err((MadeProgress, fail, state));
}
let mut parts = Vec::with_capacity_in(4, arena);
// Record the final parts.
if is_capitalized {
capitalized_parts.push(part_buf.into_bump_str());
} else {
noncapitalized_parts.push(part_buf.into_bump_str());
}
let answer = if is_accessor_fn {
// Handle accessor functions first because they have the strictest requirements.
// Accessor functions may have exactly 1 noncapitalized part, and no capitalzed parts.
if capitalized_parts.is_empty() && noncapitalized_parts.len() == 1 && !is_private_tag {
let value = noncapitalized_parts.iter().next().unwrap();
Ident::AccessorFunction(value)
} else {
return Err((
MadeProgress,
BadIdent::WeirdAccessor(state.line, state.column),
state,
));
if !first_is_uppercase {
let first_part = unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) };
parts.push(first_part);
}
} else if noncapitalized_parts.is_empty() {
// We have capitalized parts only, so this must be a tag.
match capitalized_parts.first() {
Some(value) => {
if capitalized_parts.len() == 1 {
if is_private_tag {
Ident::PrivateTag(value)
} else {
Ident::GlobalTag(value)
match chomp_access_chain(&buffer[chomped..], &mut parts) {
Ok(width) => {
chomped += width as usize;
let ident = Ident::Access {
module_name,
parts: parts.into_bump_slice(),
};
Ok((chomped as u16, ident))
}
Err(0) if !module_name.is_empty() => Err((
chomped as u16,
BadIdent::QualifiedTag(row, chomped as u16 + col),
)),
Err(1) if parts.is_empty() => Err((
chomped as u16 + 1,
BadIdent::WeirdDotQualified(row, chomped as u16 + col + 1),
)),
Err(width) => Err((
chomped as u16 + width,
BadIdent::WeirdDotAccess(row, chomped as u16 + col + width),
)),
}
} else if let Ok(('_', _)) = char::from_utf8_slice_start(&buffer[chomped..]) {
// we don't allow underscores in the middle of an identifier
// but still parse them (and generate a malformed identifier)
// to give good error messages for this case
Err((
chomped as u16 + 1,
BadIdent::Underscore(row, col + chomped as u16 + 1),
))
} else if first_is_uppercase {
// just one segment, starting with an uppercase letter; that's a global tag
let value = unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) };
Ok((chomped as u16, Ident::GlobalTag(value)))
} else {
// just one segment, starting with a lowercase letter; that's a normal identifier
let value = unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) };
let ident = Ident::Access {
module_name: "",
parts: arena.alloc([value]),
};
Ok((chomped as u16, ident))
}
}
fn chomp_module_chain(buffer: &[u8]) -> Result<u16, Progress> {
let mut chomped = 0;
while let Some(b'.') = buffer.get(chomped) {
match &buffer.get(chomped + 1..) {
Some(slice) => match chomp_uppercase_part(slice) {
Ok(name) => {
chomped += name.len() + 1;
}
Err(MadeProgress) => return Err(MadeProgress),
Err(NoProgress) => break,
},
None => return Err(MadeProgress),
}
}
if chomped == 0 {
Err(NoProgress)
} else {
Ok(chomped as u16)
}
}
pub fn concrete_type<'a>() -> impl Parser<'a, (&'a str, &'a str), ()> {
move |_, state: State<'a>| match chomp_concrete_type(state.bytes) {
Err(progress) => Err((progress, (), state)),
Ok((module_name, type_name, width)) => {
match state.advance_without_indenting_ee(width, |_, _| ()) {
Ok(state) => Ok((MadeProgress, (module_name, type_name), state)),
Err(bad) => Err(bad),
}
}
}
}
// parse a type name like `Result` or `Result.Result`
fn chomp_concrete_type(buffer: &[u8]) -> Result<(&str, &str, usize), Progress> {
let first = crate::ident::chomp_uppercase_part(buffer)?;
if let Some(b'.') = buffer.get(first.len()) {
match crate::ident::chomp_module_chain(&buffer[first.len()..]) {
Err(_) => Err(MadeProgress),
Ok(rest) => {
let width = first.len() + rest as usize;
// we must explicitly check here for a trailing `.`
if let Some(b'.') = buffer.get(width) {
return Err(MadeProgress);
}
let slice = &buffer[..width];
match slice.iter().rev().position(|c| *c == b'.') {
None => Ok(("", first, first.len())),
Some(rev_index) => {
let index = slice.len() - rev_index;
let module_name =
unsafe { std::str::from_utf8_unchecked(&slice[..index - 1]) };
let type_name = unsafe { std::str::from_utf8_unchecked(&slice[index..]) };
Ok((module_name, type_name, width))
}
} else {
// This is a qualified tag, which is not allowed!
return Err((
MadeProgress,
BadIdent::QualifiedTag(state.line, state.column),
state,
));
}
}
None => {
// 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((
MadeProgress,
BadIdent::StrayDot(state.line, state.column),
state,
));
}
}
} else if is_private_tag {
// This is qualified field access with an '@' in front, which does not make sense!
return Err((
MadeProgress,
BadIdent::PrivateTagFieldAccess(state.line, state.column),
state,
));
} else {
// We have multiple noncapitalized parts, so this must be field access.
Ident::Access {
module_name: join_module_parts(arena, capitalized_parts.into_bump_slice()),
parts: noncapitalized_parts.into_bump_slice(),
}
};
Ok((Progress::MadeProgress, (answer, None), state))
Ok(("", first, first.len()))
}
}
fn chomp_access_chain<'a>(buffer: &'a [u8], parts: &mut Vec<'a, &'a str>) -> Result<u16, u16> {
let mut chomped = 0;
while let Some(b'.') = buffer.get(chomped) {
match &buffer.get(chomped + 1..) {
Some(slice) => match chomp_lowercase_part(slice) {
Ok(name) => {
let value = unsafe {
std::str::from_utf8_unchecked(
&buffer[chomped + 1..chomped + 1 + name.len()],
)
};
parts.push(value);
chomped += name.len() + 1;
}
Err(_) => return Err(chomped as u16 + 1),
},
None => return Err(chomped as u16 + 1),
}
}
if chomped == 0 {
Err(0)
} else {
Ok(chomped as u16)
}
}
fn parse_ident_help_help<'a>(
arena: &'a Bump,
mut state: State<'a>,
) -> ParseResult<'a, Ident<'a>, BadIdent> {
match chomp_identifier_chain(arena, state.bytes, state.line, state.column) {
Ok((width, ident)) => {
state = advance_state!(state, width as usize)?;
Ok((MadeProgress, ident, state))
}
Err((0, fail)) => Err((NoProgress, fail, state)),
Err((width, fail)) => {
state = advance_state!(state, width as usize)?;
Err((MadeProgress, fail, state))
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,5 @@
use crate::ast::Base;
use crate::parser::{parse_utf8, Number, ParseResult, Parser, Progress, State, SyntaxError};
use std::char;
use std::str::from_utf8_unchecked;
use crate::parser::{Number, ParseResult, Parser, Progress, State};
pub enum NumLiteral<'a> {
Float(&'a str),
@ -13,6 +11,20 @@ pub enum NumLiteral<'a> {
},
}
pub fn positive_number_literal<'a>() -> impl Parser<'a, NumLiteral<'a>, Number> {
move |_arena, state: State<'a>| {
match state.bytes.get(0) {
Some(first_byte) if (*first_byte as char).is_ascii_digit() => {
parse_number_base(false, &state.bytes, state)
}
_ => {
// this is not a number at all
Err((Progress::NoProgress, Number::End, state))
}
}
}
}
pub fn number_literal<'a>() -> impl Parser<'a, NumLiteral<'a>, Number> {
move |_arena, state: State<'a>| {
match state.bytes.get(0) {
@ -52,29 +64,21 @@ fn chomp_number_base<'a>(
) -> ParseResult<'a, NumLiteral<'a>, Number> {
let (_is_float, chomped) = chomp_number(bytes);
match parse_utf8(&bytes[0..chomped]) {
Ok(string) => match state.advance_without_indenting(chomped + 2 + is_negative as usize) {
Ok(new) => {
// all is well
Ok((
Progress::MadeProgress,
NumLiteral::NonBase10Int {
is_negative,
string,
base,
},
new,
))
}
Err((_, SyntaxError::LineTooLong(_), new)) => {
// the only error we care about in this context
Err((Progress::MadeProgress, Number::LineTooLong, new))
}
Err(_) => unreachable!("we know advancing will succeed if there is space on the line"),
},
let string = unsafe { std::str::from_utf8_unchecked(&bytes[..chomped]) };
Err(_) => unreachable!("no invalid utf8 could have been chomped"),
}
let new = state.advance_without_indenting_ee(chomped + 2 + is_negative as usize, |_, _| {
Number::LineTooLong
})?;
Ok((
Progress::MadeProgress,
NumLiteral::NonBase10Int {
is_negative,
string,
base,
},
new,
))
}
fn chomp_number_dec<'a>(
@ -94,27 +98,21 @@ fn chomp_number_dec<'a>(
return Err((Progress::NoProgress, Number::End, state));
}
let string = unsafe { from_utf8_unchecked(&state.bytes[0..chomped + is_negative as usize]) };
let string =
unsafe { std::str::from_utf8_unchecked(&state.bytes[0..chomped + is_negative as usize]) };
match state.advance_without_indenting(chomped + is_negative as usize) {
Ok(new) => {
// all is well
Ok((
Progress::MadeProgress,
if is_float {
NumLiteral::Float(string)
} else {
NumLiteral::Num(string)
},
new,
))
}
Err((_, SyntaxError::LineTooLong(_), new)) => {
// the only error we care about in this context
Err((Progress::MadeProgress, Number::LineTooLong, new))
}
Err(_) => unreachable!("we know advancing will succeed if there is space on the line"),
}
let new = state
.advance_without_indenting_ee(chomped + is_negative as usize, |_, _| Number::LineTooLong)?;
Ok((
Progress::MadeProgress,
if is_float {
NumLiteral::Float(string)
} else {
NumLiteral::Num(string)
},
new,
))
}
fn chomp_number(mut bytes: &[u8]) -> (bool, usize) {

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,10 @@
use crate::ast::Pattern;
use crate::blankspace::{space0_around_ee, space0_before_e, space0_e};
use crate::ident::{ident, lowercase_ident, Ident};
use crate::ident::{lowercase_ident, parse_ident_help, Ident};
use crate::parser::Progress::{self, *};
use crate::parser::{
backtrackable, optional, specialize, specialize_ref, word1, EPattern, PInParens, PRecord,
ParseResult, Parser, State, SyntaxError,
ParseResult, Parser, State,
};
use bumpalo::collections::string::String;
use bumpalo::collections::Vec;
@ -51,13 +51,6 @@ fn parse_closure_param<'a>(
.parse(arena, state)
}
pub fn loc_pattern<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<'a>>, SyntaxError<'a>> {
specialize(
|e, _, _| SyntaxError::Pattern(e),
loc_pattern_help(min_indent),
)
}
pub fn loc_pattern_help<'a>(
min_indent: u16,
) -> impl Parser<'a, Located<Pattern<'a>>, EPattern<'a>> {
@ -130,7 +123,7 @@ fn loc_pattern_in_parens_help<'a>(
between!(
word1(b'(', PInParens::Open),
space0_around_ee(
move |arena, state| specialize_ref(PInParens::Syntax, loc_pattern(min_indent))
move |arena, state| specialize_ref(PInParens::Pattern, loc_pattern_help(min_indent))
.parse(arena, state),
min_indent,
PInParens::Space,
@ -176,10 +169,11 @@ fn loc_ident_pattern_help<'a>(
can_have_arguments: bool,
) -> impl Parser<'a, Located<Pattern<'a>>, EPattern<'a>> {
move |arena: &'a Bump, state: State<'a>| {
let original_state = state.clone();
let original_state = state;
let (_, loc_ident, state) =
specialize(|_, r, c| EPattern::Start(r, c), loc!(ident())).parse(arena, state)?;
specialize(|_, r, c| EPattern::Start(r, c), loc!(parse_ident_help))
.parse(arena, state)?;
match loc_ident.value {
Ident::GlobalTag(tag) => {
@ -296,10 +290,6 @@ fn loc_ident_pattern_help<'a>(
}
}
pub fn underscore_pattern<'a>() -> impl Parser<'a, Pattern<'a>, SyntaxError<'a>> {
specialize(|e, _, _| SyntaxError::Pattern(e), underscore_pattern_help())
}
fn underscore_pattern_help<'a>() -> impl Parser<'a, Pattern<'a>, EPattern<'a>> {
move |arena: &'a Bump, state: State<'a>| {
let (_, _, next_state) = word1(b'_', EPattern::Underscore).parse(arena, state)?;
@ -324,13 +314,6 @@ fn lowercase_ident_pattern<'a>(
specialize(move |_, _, _| EPattern::End(row, col), lowercase_ident()).parse(arena, state)
}
pub fn record_pattern<'a>(min_indent: u16) -> impl Parser<'a, Pattern<'a>, SyntaxError<'a>> {
specialize(
|e, r, c| SyntaxError::Pattern(EPattern::Record(e, r, c)),
record_pattern_help(min_indent),
)
}
#[inline(always)]
fn record_pattern_help<'a>(min_indent: u16) -> impl Parser<'a, Pattern<'a>, PRecord<'a>> {
move |arena, state| {
@ -385,7 +368,7 @@ fn record_pattern_field<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<
match opt_loc_val {
Some(First(_)) => {
let val_parser = specialize_ref(PRecord::Syntax, loc_pattern(min_indent));
let val_parser = specialize_ref(PRecord::Pattern, loc_pattern_help(min_indent));
let (_, loc_val, state) =
space0_before_e(val_parser, min_indent, PRecord::Space, PRecord::IndentColon)
.parse(arena, state)?;
@ -413,7 +396,7 @@ fn record_pattern_field<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<
}
Some(Second(_)) => {
let val_parser =
specialize_ref(PRecord::Syntax, loc!(crate::expr::expr(min_indent)));
specialize_ref(PRecord::Expr, loc!(crate::expr::expr_help(min_indent)));
let (_, loc_val, state) =
space0_before_e(val_parser, min_indent, PRecord::Space, PRecord::IndentColon)

View File

@ -1,10 +1,7 @@
use crate::ast::{EscapedChar, StrLiteral, StrSegment};
use crate::expr;
use crate::parser::Progress::*;
use crate::parser::{
allocated, ascii_char, loc, parse_utf8, specialize_ref, word1, BadInputError, EString, Parser,
State,
};
use crate::parser::{allocated, loc, specialize_ref, word1, BadInputError, EString, Parser, State};
use bumpalo::collections::vec::Vec;
use bumpalo::Bump;
@ -102,7 +99,7 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>, EString<'a>> {
// to exclude that char we just parsed.
let string_bytes = &state.bytes[0..(segment_parsed_bytes - 1)];
match parse_utf8(string_bytes) {
match std::str::from_utf8(string_bytes) {
Ok(string) => {
state = advance_state!(state, string.len())?;
@ -233,9 +230,9 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>, EString<'a>> {
// Parse an arbitrary expression, then give a
// canonicalization error if that expression variant
// is not allowed inside a string interpolation.
let (_progress, loc_expr, new_state) = specialize_ref(
EString::Format,
skip_second!(loc(allocated(expr::expr(0))), ascii_char(b')')),
let (_progress, loc_expr, new_state) = skip_second!(
specialize_ref(EString::Format, loc(allocated(expr::expr_help(0)))),
word1(b')', EString::FormatEnd)
)
.parse(arena, state)?;

View File

@ -1,13 +1,10 @@
use crate::ast::{self, Attempting};
use crate::blankspace::space0_before;
use crate::expr::expr;
use crate::module::{header, module_defs};
use crate::parser::{loc, Parser, State, SyntaxError};
use crate::ast;
use crate::module::module_defs;
use crate::parser::{Parser, State, SyntaxError};
use bumpalo::collections::Vec;
use bumpalo::Bump;
use roc_region::all::Located;
#[allow(dead_code)]
pub fn parse_expr_with<'a>(
arena: &'a Bump,
input: &'a str,
@ -15,24 +12,12 @@ pub fn parse_expr_with<'a>(
parse_loc_with(arena, input).map(|loc_expr| loc_expr.value)
}
pub fn parse_header_with<'a>(
arena: &'a Bump,
input: &'a str,
) -> Result<ast::Module<'a>, SyntaxError<'a>> {
let state = State::new_in(arena, input.trim().as_bytes(), Attempting::Module);
let answer = header().parse(arena, state);
answer
.map(|(_, loc_expr, _)| loc_expr)
.map_err(|(_, fail, _)| fail)
}
#[allow(dead_code)]
pub fn parse_defs_with<'a>(
arena: &'a Bump,
input: &'a str,
) -> Result<Vec<'a, Located<ast::Def<'a>>>, SyntaxError<'a>> {
let state = State::new_in(arena, input.trim().as_bytes(), Attempting::Module);
let state = State::new(input.trim().as_bytes());
let answer = module_defs().parse(arena, state);
answer
.map(|(_, loc_expr, _)| loc_expr)
@ -44,11 +29,10 @@ pub fn parse_loc_with<'a>(
arena: &'a Bump,
input: &'a str,
) -> Result<Located<ast::Expr<'a>>, SyntaxError<'a>> {
let state = State::new_in(arena, input.trim().as_bytes(), Attempting::Module);
let parser = space0_before(loc(expr(0)), 0);
let answer = parser.parse(&arena, state);
let state = State::new(input.trim().as_bytes());
answer
.map(|(_, loc_expr, _)| loc_expr)
.map_err(|(_, fail, _)| fail)
match crate::expr::test_parse_expr(0, arena, state) {
Ok(loc_expr) => Ok(loc_expr),
Err(fail) => Err(SyntaxError::Expr(fail)),
}
}

View File

@ -1,24 +1,16 @@
use crate::ast::{AssignedField, Tag, TypeAnnotation};
use crate::blankspace::{space0_around_ee, space0_before_e, space0_e};
use crate::ident::join_module_parts;
use crate::keyword;
use crate::parser::{
allocated, backtrackable, not_e, optional, peek_utf8_char_e, specialize, specialize_ref, word1,
word2, ParseResult, Parser,
allocated, backtrackable, optional, specialize, specialize_ref, word1, word2, ParseResult,
Parser,
Progress::{self, *},
State, SyntaxError, TApply, TInParens, TRecord, TTagUnion, TVariable, Type,
State, TApply, TInParens, TRecord, TTagUnion, Type,
};
use bumpalo::collections::string::String;
use bumpalo::collections::vec::Vec;
use bumpalo::Bump;
use roc_region::all::{Located, Region};
pub fn located<'a>(
min_indent: u16,
) -> impl Parser<'a, Located<TypeAnnotation<'a>>, SyntaxError<'a>> {
specialize(|x, _, _| SyntaxError::Type(x), expression(min_indent))
}
pub fn located_help<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, Type<'a>> {
expression(min_indent)
}
@ -62,7 +54,7 @@ fn term<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, Typ
loc!(specialize(Type::TRecord, record_type(min_indent))),
loc!(specialize(Type::TTagUnion, tag_union_type(min_indent))),
loc!(applied_type(min_indent)),
loc!(specialize(Type::TVariable, parse_type_variable))
loc!(parse_type_variable)
),
// Inline alias notation, e.g. [ Nil, Cons a (List a) ] as List a
one_of![
@ -117,21 +109,13 @@ fn loc_applied_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotatio
map_with_arena!(
and!(
backtrackable(space0_e(min_indent, Type::TSpace, Type::TIndentStart)),
skip_first!(
// Once we hit an "as", stop parsing args
// and roll back parsing of preceding spaces
not_e(
crate::parser::keyword(keyword::AS, min_indent),
Type::TStart
),
one_of!(
loc_wildcard(),
specialize(Type::TInParens, loc_type_in_parens(min_indent)),
loc!(specialize(Type::TRecord, record_type(min_indent))),
loc!(specialize(Type::TTagUnion, tag_union_type(min_indent))),
loc!(specialize(Type::TApply, parse_concrete_type)),
loc!(specialize(Type::TVariable, parse_type_variable))
)
one_of!(
loc_wildcard(),
specialize(Type::TInParens, loc_type_in_parens(min_indent)),
loc!(specialize(Type::TRecord, record_type(min_indent))),
loc!(specialize(Type::TTagUnion, tag_union_type(min_indent))),
loc!(specialize(Type::TApply, parse_concrete_type)),
loc!(parse_type_variable)
)
),
|arena: &'a Bump, (spaces, argument): (&'a [_], Located<TypeAnnotation<'a>>)| {
@ -192,102 +176,9 @@ where
F: Fn(Row, Col) -> E,
E: 'a,
{
use encode_unicode::CharExt;
move |arena, mut state: State<'a>| {
let mut buf;
match char::from_utf8_slice_start(state.bytes) {
Ok((first_letter, bytes_parsed)) => match first_letter {
'@' => {
debug_assert_eq!(bytes_parsed, 1);
// parsing a private tag name
match char::from_utf8_slice_start(&state.bytes[1..]) {
Ok((second_letter, bytes_parsed_2)) if second_letter.is_uppercase() => {
let total_parsed = bytes_parsed + bytes_parsed_2;
buf = String::with_capacity_in(total_parsed, arena);
buf.push('@');
buf.push(second_letter);
state = state.advance_without_indenting(total_parsed).map_err(
|(progress, _, state)| {
(progress, to_problem(state.line, state.column), state)
},
)?;
}
_ => {
// important for error messages
state = state.advance_without_indenting(bytes_parsed).map_err(
|(progress, _, state)| {
(progress, to_problem(state.line, state.column), state)
},
)?;
let row = state.line;
let col = state.column;
return state.fail(arena, MadeProgress, to_problem(row, col));
}
}
}
_ if first_letter.is_uppercase() => {
buf = String::with_capacity_in(1, arena);
buf.push(first_letter);
state = state.advance_without_indenting(bytes_parsed).map_err(
|(progress, _, state)| {
(progress, to_problem(state.line, state.column), state)
},
)?;
}
_ => {
let row = state.line;
let col = state.column;
return state.fail(arena, NoProgress, to_problem(row, col));
}
},
Err(_) => {
let row = state.line;
let col = state.column;
return state.fail(arena, NoProgress, to_problem(row, col));
}
};
while !state.bytes.is_empty() {
match char::from_utf8_slice_start(state.bytes) {
Ok((ch, bytes_parsed)) => {
// After the first character, only these are allowed:
//
// * Unicode alphabetic chars - you might include `鹏` if that's clear to your readers
// * ASCII digits - e.g. `1` but not `¾`, both of which pass .is_numeric()
// * A ':' indicating the end of the field
if ch.is_alphabetic() || ch.is_ascii_digit() {
buf.push(ch);
state = state.advance_without_indenting(bytes_parsed).map_err(
|(progress, _, state)| {
(progress, to_problem(state.line, state.column), state)
},
)?;
} else {
// This is the end of the field. We're done!
break;
}
}
Err(_) => {
let row = state.line;
let col = state.column;
return state.fail(arena, MadeProgress, to_problem(row, col));
}
};
}
Ok((MadeProgress, buf.into_bump_str(), state))
move |arena, state: State<'a>| match crate::ident::tag_name().parse(arena, state) {
Ok(good) => Ok(good),
Err((progress, _, state)) => Err((progress, to_problem(state.line, state.column), state)),
}
}
@ -517,156 +408,52 @@ fn expression<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>
fn parse_concrete_type<'a>(
arena: &'a Bump,
mut state: State<'a>,
state: State<'a>,
) -> ParseResult<'a, TypeAnnotation<'a>, TApply> {
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 initial_bytes = state.bytes;
// Qualified types must start with a capitalized letter.
match peek_utf8_char_e(&state, TApply::StartNotUppercase, TApply::Space) {
Ok((first_letter, bytes_parsed)) => {
if first_letter.is_alphabetic() && first_letter.is_uppercase() {
part_buf.push(first_letter);
} else {
let problem = TApply::StartNotUppercase(state.line, state.column + 1);
return Err((NoProgress, problem, state));
}
match crate::ident::concrete_type().parse(arena, state) {
Ok((_, (module_name, type_name), state)) => {
let answer = TypeAnnotation::Apply(module_name, type_name, &[]);
state = state.advance_without_indenting_e(bytes_parsed, TApply::Space)?;
Ok((MadeProgress, answer, state))
}
Err(reason) => return Err((NoProgress, reason, state)),
}
Err((NoProgress, _, state)) => {
Err((NoProgress, TApply::End(state.line, state.column), state))
}
Err((MadeProgress, _, mut state)) => {
// we made some progress, but ultimately failed.
// that means a malformed type name
let chomped = crate::ident::chomp_malformed(state.bytes);
let delta = initial_bytes.len() - state.bytes.len();
let parsed_str =
unsafe { std::str::from_utf8_unchecked(&initial_bytes[..chomped + delta]) };
while !state.bytes.is_empty() {
match peek_utf8_char_e(&state, TApply::End, TApply::Space) {
Ok((ch, bytes_parsed)) => {
// After the first character, only these are allowed:
//
// * Unicode alphabetic chars - you might name a variable `鹏` if that's clear to your readers
// * ASCII digits - e.g. `1` but not `¾`, both of which pass .is_numeric()
// * A dot ('.')
if ch.is_alphabetic() {
if part_buf.is_empty() && !ch.is_uppercase() {
// Each part must begin with a capital letter.
return Err((
MadeProgress,
TApply::StartNotUppercase(state.line, state.column),
state,
));
}
state = state.advance_without_indenting_ee(chomped, |r, c| {
TApply::Space(crate::parser::BadInputError::LineTooLong, r, c)
})?;
part_buf.push(ch);
} else if ch.is_ascii_digit() {
// Parts may not start with numbers!
if part_buf.is_empty() {
return Err((
MadeProgress,
TApply::StartIsNumber(state.line, state.column),
state,
));
}
dbg!(&state);
part_buf.push(ch);
} else if ch == '.' {
// Having two consecutive dots is an error.
if part_buf.is_empty() {
return Err((
MadeProgress,
TApply::DoubleDot(state.line, state.column),
state,
));
}
parts.push(part_buf.into_bump_str());
// Now that we've recorded the contents of the current buffer, reset it.
part_buf = String::new_in(arena);
} else {
// This must be the end of the type. We're done!
break;
}
state = state.advance_without_indenting_e(bytes_parsed, TApply::Space)?;
}
Err(reason) => {
return Err((MadeProgress, reason, state));
}
Ok((MadeProgress, TypeAnnotation::Malformed(parsed_str), state))
}
}
if part_buf.is_empty() {
// We probably had a trailing dot, e.g. `Foo.bar.` - this is malformed!
//
// This condition might also occur if we encounter a malformed accessor like `.|`
//
// If we made it this far and don't have a next_char, then necessarily
// we have consumed a '.' char previously.
return Err((
MadeProgress,
TApply::TrailingDot(state.line, state.column),
state,
));
}
let answer = TypeAnnotation::Apply(
join_module_parts(arena, parts.into_bump_slice()),
part_buf.into_bump_str(),
&[],
);
Ok((MadeProgress, answer, state))
}
fn parse_type_variable<'a>(
arena: &'a Bump,
mut state: State<'a>,
) -> ParseResult<'a, TypeAnnotation<'a>, TVariable> {
let mut buf = String::new_in(arena);
state: State<'a>,
) -> ParseResult<'a, TypeAnnotation<'a>, Type<'a>> {
match crate::ident::lowercase_ident().parse(arena, state) {
Ok((_, name, state)) => {
let answer = TypeAnnotation::BoundVariable(name);
let start_bytes_len = state.bytes.len();
match peek_utf8_char_e(&state, TVariable::StartNotLowercase, TVariable::Space) {
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((
NoProgress,
TVariable::StartNotLowercase(state.line, state.column),
state,
));
}
state = state.advance_without_indenting_e(bytes_parsed, TVariable::Space)?;
Ok((MadeProgress, answer, state))
}
Err(reason) => return Err((NoProgress, reason, state)),
Err((progress, _, state)) => Err((
progress,
Type::TBadTypeVariable(state.line, state.column),
state,
)),
}
while !state.bytes.is_empty() {
match peek_utf8_char_e(&state, TVariable::End, TVariable::Space) {
Ok((ch, bytes_parsed)) => {
// After the first character, only these are allowed:
//
// * Unicode alphabetic chars - you might name a variable `鹏` if that's clear to your readers
// * ASCII digits - e.g. `1` but not `¾`, both of which pass .is_numeric()
if ch.is_alphabetic() || ch.is_ascii_digit() {
buf.push(ch);
} else {
// This must be the end of the type. We're done!
break;
}
state = state.advance_without_indenting_e(bytes_parsed, TVariable::Space)?;
}
Err(reason) => {
return state.fail(arena, MadeProgress, reason);
}
}
}
let answer = TypeAnnotation::BoundVariable(buf.into_bump_str());
let progress = Progress::from_lengths(start_bytes_len, state.bytes.len());
Ok((progress, answer, state))
}

View File

@ -23,14 +23,12 @@ mod test_parse {
use roc_parse::ast::Pattern::{self, *};
use roc_parse::ast::StrLiteral::{self, *};
use roc_parse::ast::StrSegment::*;
use roc_parse::ast::{
self, Attempting, Def, EscapedChar, Spaceable, TypeAnnotation, WhenBranch,
};
use roc_parse::ast::{self, Def, EscapedChar, Spaceable, TypeAnnotation, WhenBranch};
use roc_parse::header::{
AppHeader, Effects, ExposesEntry, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry,
PackageName, PackageOrPath, PlatformHeader, To,
};
use roc_parse::module::{app_header, interface_header, module_defs, platform_header};
use roc_parse::module::module_defs;
use roc_parse::parser::{Parser, State, SyntaxError};
use roc_parse::test_helpers::parse_expr_with;
use roc_region::all::{Located, Region};
@ -43,10 +41,9 @@ mod test_parse {
assert_eq!(Ok(expected_expr), actual);
}
fn assert_parsing_fails<'a>(input: &'a str, _reason: SyntaxError, _attempting: Attempting) {
fn assert_parsing_fails<'a>(input: &'a str, _reason: SyntaxError) {
let arena = Bump::new();
let actual = parse_expr_with(&arena, input);
// let expected_fail = Fail { reason, attempting };
assert!(actual.is_err());
}
@ -291,7 +288,7 @@ mod test_parse {
#[test]
fn empty_source_file() {
assert_parsing_fails("", SyntaxError::Eof(Region::zero()), Attempting::Module);
assert_parsing_fails("", SyntaxError::Eof(Region::zero()));
}
#[test]
@ -308,11 +305,7 @@ mod test_parse {
// Make sure it's longer than our maximum line length
assert_eq!(too_long_str.len(), max_line_length + 1);
assert_parsing_fails(
&too_long_str,
SyntaxError::LineTooLong(0),
Attempting::Module,
);
assert_parsing_fails(&too_long_str, SyntaxError::LineTooLong(0));
}
// INT LITERALS
@ -647,6 +640,33 @@ mod test_parse {
assert_eq!(Ok(expected), actual);
}
#[test]
fn newline_and_spaces_before_less_than() {
let arena = Bump::new();
let spaced_int = arena.alloc(Num("1")).after(&[Newline]);
let tuple = arena.alloc((
Located::new(0, 0, 4, 5, spaced_int),
Located::new(1, 1, 4, 5, LessThan),
Located::new(1, 1, 6, 7, Num("2")),
));
let newlines = bumpalo::vec![in &arena; Newline, Newline];
let def = Def::Body(
arena.alloc(Located::new(0, 0, 0, 1, Identifier("x"))),
arena.alloc(Located::new(0, 1, 4, 7, BinOp(tuple))),
);
let loc_def = &*arena.alloc(Located::new(0, 1, 0, 7, def));
let defs = &[loc_def];
let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines.into_bump_slice());
let loc_ret = Located::new(3, 3, 0, 2, ret);
let expected = Defs(defs, arena.alloc(loc_ret));
// let expected = BinOp(tuple);
let actual = parse_expr_with(&arena, "x = 1\n < 2\n\n42");
assert_eq!(Ok(expected), actual);
}
#[test]
fn comment_with_non_ascii() {
let arena = Bump::new();
@ -1012,7 +1032,7 @@ mod test_parse {
use roc_parse::ident::BadIdent;
let arena = Bump::new();
let expected = Expr::MalformedIdent("@One.Two.Whee", BadIdent::QualifiedTag(0, 13));
let expected = Expr::MalformedIdent("@One.Two.Whee", BadIdent::BadPrivateTag(0, 4));
let actual = parse_expr_with(&arena, "@One.Two.Whee");
assert_eq!(Ok(expected), actual);
@ -1321,21 +1341,18 @@ mod test_parse {
},
));
let args = &[&*arg1, &*arg2];
let apply_expr = Expr::Apply(
arena.alloc(Located::new(
0,
0,
1,
5,
Var {
module_name: "",
ident: "whee",
},
)),
args,
CalledVia::Space,
let function = Located::new(
0,
0,
1,
5,
Var {
module_name: "",
ident: "whee",
},
);
let expected = UnaryOp(arena.alloc(Located::new(0, 0, 1, 13, apply_expr)), loc_op);
let unary = Located::new(0, 0, 0, 5, UnaryOp(arena.alloc(function), loc_op));
let expected = Expr::Apply(arena.alloc(unary), args, CalledVia::Space);
let actual = parse_expr_with(&arena, "-whee 12 foo");
assert_eq!(Ok(expected), actual);
@ -1357,21 +1374,19 @@ mod test_parse {
},
));
let args = &[&*arg1, &*arg2];
let apply_expr = Expr::Apply(
arena.alloc(Located::new(
0,
0,
1,
5,
Var {
module_name: "",
ident: "whee",
},
)),
args,
CalledVia::Space,
let function = Located::new(
0,
0,
1,
5,
Var {
module_name: "",
ident: "whee",
},
);
let expected = UnaryOp(arena.alloc(Located::new(0, 0, 1, 13, apply_expr)), loc_op);
let unary = Located::new(0, 0, 0, 5, UnaryOp(arena.alloc(function), loc_op));
let expected = Expr::Apply(arena.alloc(unary), args, CalledVia::Space);
let actual = parse_expr_with(&arena, "!whee 12 foo");
assert_eq!(Ok(expected), actual);
@ -1407,7 +1422,7 @@ mod test_parse {
args,
CalledVia::Space,
)));
let expected = UnaryOp(arena.alloc(Located::new(0, 0, 1, 15, apply_expr)), loc_op);
let expected = UnaryOp(arena.alloc(Located::new(0, 0, 2, 14, apply_expr)), loc_op);
let actual = parse_expr_with(&arena, "-(whee 12 foo)");
assert_eq!(Ok(expected), actual);
@ -1443,7 +1458,7 @@ mod test_parse {
args,
CalledVia::Space,
)));
let expected = UnaryOp(arena.alloc(Located::new(0, 0, 1, 15, apply_expr)), loc_op);
let expected = UnaryOp(arena.alloc(Located::new(0, 0, 2, 14, apply_expr)), loc_op);
let actual = parse_expr_with(&arena, "!(whee 12 foo)");
assert_eq!(Ok(expected), actual);
@ -2416,7 +2431,7 @@ mod test_parse {
let imports = Vec::new_in(&arena);
let provides = Vec::new_in(&arena);
let module_name = StrLiteral::PlainLine("test-app");
let expected = AppHeader {
let header = AppHeader {
name: Located::new(0, 0, 4, 14, module_name),
packages,
imports,
@ -2433,17 +2448,15 @@ mod test_parse {
after_to: &[],
};
let expected = roc_parse::ast::Module::App { header };
let src = indoc!(
r#"
app "test-app" packages {} imports [] provides [] to blah
"#
);
let actual = app_header()
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
let actual = roc_parse::module::parse_header(&arena, State::new(src.as_bytes()))
.map(|tuple| tuple.0);
assert_eq!(Ok(expected), actual);
}
@ -2457,7 +2470,7 @@ mod test_parse {
let imports = Vec::new_in(&arena);
let provides = Vec::new_in(&arena);
let module_name = StrLiteral::PlainLine("test-app");
let expected = AppHeader {
let header = AppHeader {
name: Located::new(0, 0, 4, 14, module_name),
packages,
imports,
@ -2474,17 +2487,16 @@ mod test_parse {
after_to: &[],
};
let expected = roc_parse::ast::Module::App { header };
let src = indoc!(
r#"
app "test-app" provides [] to "./blah"
"#
);
let actual = app_header()
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
let actual = roc_parse::module::parse_header(&arena, State::new(src.as_bytes()))
.map(|tuple| tuple.0);
assert_eq!(Ok(expected), actual);
}
@ -2509,7 +2521,8 @@ mod test_parse {
let provide_entry = Located::new(3, 3, 15, 24, Exposed("quicksort"));
let provides = bumpalo::vec![in &arena; provide_entry];
let module_name = StrLiteral::PlainLine("quicksort");
let expected = AppHeader {
let header = AppHeader {
name: Located::new(0, 0, 4, 15, module_name),
packages,
imports,
@ -2526,6 +2539,8 @@ mod test_parse {
after_to: &[],
};
let expected = roc_parse::ast::Module::App { header };
let src = indoc!(
r#"
app "quicksort"
@ -2535,12 +2550,8 @@ mod test_parse {
"#
);
let actual = app_header()
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
let actual = roc_parse::module::parse_header(&arena, State::new(src.as_bytes()))
.map(|tuple| tuple.0);
assert_eq!(Ok(expected), actual);
}
@ -2560,7 +2571,7 @@ mod test_parse {
spaces_after_effects_keyword: &[],
spaces_after_type_name: &[],
};
let expected = PlatformHeader {
let header = PlatformHeader {
name: Located::new(0, 0, 9, 23, pkg_name),
requires: Vec::new_in(&arena),
exposes: Vec::new_in(&arena),
@ -2581,13 +2592,11 @@ mod test_parse {
after_provides: &[],
};
let expected = roc_parse::ast::Module::Platform { header };
let src = "platform rtfeldman/blah requires {} exposes [] packages {} imports [] provides [] effects fx.Blah {}";
let actual = platform_header()
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
let actual = roc_parse::module::parse_header(&arena, State::new(src.as_bytes()))
.map(|tuple| tuple.0);
assert_eq!(Ok(expected), actual);
}
@ -2621,7 +2630,7 @@ mod test_parse {
spaces_after_effects_keyword: &[],
spaces_after_type_name: &[],
};
let expected = PlatformHeader {
let header = PlatformHeader {
name: Located::new(0, 0, 9, 19, pkg_name),
requires: Vec::new_in(&arena),
exposes: Vec::new_in(&arena),
@ -2642,6 +2651,8 @@ mod test_parse {
after_provides: &[],
};
let expected = roc_parse::ast::Module::Platform { header };
let src = indoc!(
r#"
platform foo/barbaz
@ -2653,12 +2664,8 @@ mod test_parse {
effects fx.Effect {}
"#
);
let actual = platform_header()
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
let actual = roc_parse::module::parse_header(&arena, State::new(src.as_bytes()))
.map(|tuple| tuple.0);
assert_eq!(Ok(expected), actual);
}
@ -2669,7 +2676,7 @@ mod test_parse {
let exposes = Vec::new_in(&arena);
let imports = Vec::new_in(&arena);
let module_name = ModuleName::new("Foo");
let expected = InterfaceHeader {
let header = InterfaceHeader {
name: Located::new(0, 0, 10, 13, module_name),
exposes,
imports,
@ -2680,17 +2687,16 @@ mod test_parse {
before_imports: &[],
after_imports: &[],
};
let expected = roc_parse::ast::Module::Interface { header };
let src = indoc!(
r#"
interface Foo exposes [] imports []
"#
);
let actual = interface_header()
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
let actual = roc_parse::module::parse_header(&arena, State::new(src.as_bytes()))
.map(|tuple| tuple.0);
assert_eq!(Ok(expected), actual);
}
@ -2701,7 +2707,7 @@ mod test_parse {
let exposes = Vec::new_in(&arena);
let imports = Vec::new_in(&arena);
let module_name = ModuleName::new("Foo.Bar.Baz");
let expected = InterfaceHeader {
let header = InterfaceHeader {
name: Located::new(0, 0, 10, 21, module_name),
exposes,
imports,
@ -2712,17 +2718,16 @@ mod test_parse {
before_imports: &[],
after_imports: &[],
};
let expected = roc_parse::ast::Module::Interface { header };
let src = indoc!(
r#"
interface Foo.Bar.Baz exposes [] imports []
"#
);
let actual = interface_header()
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
let actual = roc_parse::module::parse_header(&arena, State::new(src.as_bytes()))
.map(|tuple| tuple.0);
assert_eq!(Ok(expected), actual);
}
@ -2748,10 +2753,7 @@ mod test_parse {
"#
);
let actual = module_defs()
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.parse(&arena, State::new(src.as_bytes()))
.map(|tuple| tuple.1);
// It should occur twice in the debug output - once for the pattern,
@ -2810,10 +2812,7 @@ mod test_parse {
);
let actual = module_defs()
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.parse(&arena, State::new(src.as_bytes()))
.map(|tuple| tuple.1);
assert_eq!(Ok(expected), actual);
@ -2833,11 +2832,8 @@ mod test_parse {
);
let actual = module_defs()
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
.parse(&arena, State::new(src.as_bytes()))
.map(|tuple| tuple.0);
assert!(actual.is_ok());
}
@ -2858,18 +2854,15 @@ mod test_parse {
);
let actual = module_defs()
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
.parse(&arena, State::new(src.as_bytes()))
.map(|tuple| tuple.0);
assert!(actual.is_ok());
}
#[test]
fn outdenting_newline_after_else() {
let arena = Bump::new();
let arena = &Bump::new();
// highlights a problem with the else branch demanding a newline after its expression
let src = indoc!(
@ -2881,16 +2874,19 @@ mod test_parse {
"#
);
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());
let state = State::new(src.as_bytes());
let parser = module_defs();
let parsed = parser.parse(arena, state);
match parsed {
Ok((_, _, state)) => {
dbg!(state);
return;
}
Err((_, _fail, _state)) => {
dbg!(_fail, _state);
assert!(false);
}
}
}
#[test]

View File

@ -3,7 +3,7 @@ use std::fmt;
/// TODO replace Located with this
pub type Loc<T> = Located<T>;
#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Default)]
pub struct Region {
pub start_line: u32,
pub end_line: u32,
@ -107,6 +107,20 @@ impl Region {
end_line,
}
}
pub const fn start(&self) -> Position {
Position {
row: self.start_line,
col: self.start_col,
}
}
pub const fn end(&self) -> Position {
Position {
row: self.end_line,
col: self.end_col,
}
}
}
#[test]
@ -132,6 +146,12 @@ impl fmt::Debug for Region {
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Default)]
pub struct Position {
pub row: u32,
pub col: u16,
}
#[derive(Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
pub struct Located<T> {
pub region: Region,

View File

@ -1,4 +1,5 @@
use roc_collections::all::MutSet;
use roc_parse::parser::{Col, Row};
use roc_problem::can::PrecedenceProblem::BothNonAssociative;
use roc_problem::can::{FloatErrorKind, IntErrorKind, Problem, RuntimeError};
use roc_region::all::Region;
@ -357,23 +358,7 @@ fn to_bad_ident_expr_report<'b>(
let region = Region::from_row_col(row, col);
alloc.stack(vec![
alloc.reflow(r"I trying to parse a record field accessor here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("Something like "),
alloc.parser_suggestion(".name"),
alloc.reflow(" or "),
alloc.parser_suggestion(".height"),
alloc.reflow(" that accesses a value from a record."),
]),
])
}
PartStartsWithNumber(row, col) => {
let region = Region::from_row_col(row, col);
alloc.stack(vec![
alloc.reflow("I trying to parse a record field access here:"),
alloc.reflow(r"I trying to parse a record field access here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("So I expect to see a lowercase letter next, like "),
@ -430,34 +415,73 @@ fn to_bad_ident_expr_report<'b>(
]),
])
}
PrivateTagNotUppercase(row, col) => {
let region = Region::from_row_col(row, col);
Underscore(row, col) => {
let region =
Region::from_rows_cols(surroundings.start_line, surroundings.start_col, row, col);
alloc.stack(vec![
alloc.reflow("I am trying to parse a private tag here:"),
alloc.reflow("Underscores are not allowed in identifier names:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow(r"But after the "),
alloc.keyword("@"),
alloc.reflow(r" symbol I found a lowercase letter. "),
alloc.reflow(r"All tag names (global and private)"),
alloc.reflow(r" must start with an uppercase letter, like "),
alloc.parser_suggestion("@UUID"),
alloc.reflow(" or "),
alloc.parser_suggestion("@Secrets"),
alloc.reflow("."),
]),
alloc.concat(vec![alloc.reflow(
r"I recommend using camelCase, it is the standard in the Roc ecosystem.",
)]),
])
}
PrivateTagFieldAccess(_row, _col) => alloc.stack(vec![
alloc.reflow("I am very confused by this field access:"),
alloc.region(surroundings),
alloc.concat(vec![
alloc.reflow(r"It looks like a record field access on a private tag.")
]),
]),
_ => todo!(),
BadPrivateTag(row, col) => {
use BadIdentNext::*;
match what_is_next(alloc.src_lines, row, col) {
LowercaseAccess(width) => {
let region = Region::from_rows_cols(row, col, row, col + width);
alloc.stack(vec![
alloc.reflow("I am very confused by this field access:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow(r"It looks like a record field access on a private tag.")
]),
])
}
UppercaseAccess(width) => {
let region = Region::from_rows_cols(row, col, row, col + width);
alloc.stack(vec![
alloc.reflow("I am very confused by this expression:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow(
r"Looks like a private tag is treated like a module name. ",
),
alloc.reflow(r"Maybe you wanted a qualified name, like "),
alloc.parser_suggestion("Json.Decode.string"),
alloc.text("?"),
]),
])
}
Other(Some(c)) if c.is_lowercase() => {
let region = Region::from_rows_cols(
surroundings.start_line,
surroundings.start_col + 1,
row,
col + 1,
);
alloc.stack(vec![
alloc.reflow("I am trying to parse a private tag here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow(r"But after the "),
alloc.keyword("@"),
alloc.reflow(r" symbol I found a lowercase letter. "),
alloc.reflow(r"All tag names (global and private)"),
alloc.reflow(r" must start with an uppercase letter, like "),
alloc.parser_suggestion("@UUID"),
alloc.reflow(" or "),
alloc.parser_suggestion("@Secrets"),
alloc.reflow("."),
]),
])
}
other => todo!("{:?}", other),
}
}
}
}
@ -486,22 +510,6 @@ fn to_bad_ident_pattern_report<'b>(
])
}
PartStartsWithNumber(row, col) => {
let region = Region::from_row_col(row, col);
alloc.stack(vec![
alloc.reflow("I trying to parse a record field access here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("So I expect to see a lowercase letter next, like "),
alloc.parser_suggestion(".name"),
alloc.reflow(" or "),
alloc.parser_suggestion(".height"),
alloc.reflow("."),
]),
])
}
WeirdAccessor(_row, _col) => alloc.stack(vec![
alloc.reflow("I am very confused by this field access"),
alloc.region(surroundings),
@ -547,33 +555,6 @@ fn to_bad_ident_pattern_report<'b>(
]),
])
}
PrivateTagNotUppercase(row, col) => {
let region = Region::from_row_col(row, col);
alloc.stack(vec![
alloc.reflow("I am trying to parse a private tag here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow(r"But after the "),
alloc.keyword("@"),
alloc.reflow(r" symbol I found a lowercase letter. "),
alloc.reflow(r"All tag names (global and private)"),
alloc.reflow(r" must start with an uppercase letter, like "),
alloc.parser_suggestion("@UUID"),
alloc.reflow(" or "),
alloc.parser_suggestion("@Secrets"),
alloc.reflow("."),
]),
])
}
PrivateTagFieldAccess(_row, _col) => alloc.stack(vec![
alloc.reflow("I am very confused by this field access:"),
alloc.region(surroundings),
alloc.concat(vec![
alloc.reflow(r"It looks like a record field access on a private tag.")
]),
]),
Underscore(row, col) => {
let region = Region::from_row_col(row, col - 1);
@ -591,6 +572,69 @@ fn to_bad_ident_pattern_report<'b>(
}
}
#[derive(Debug)]
enum BadIdentNext<'a> {
LowercaseAccess(u16),
UppercaseAccess(u16),
NumberAccess(u16),
Keyword(&'a str),
DanglingDot,
Other(Option<char>),
}
fn what_is_next<'a>(source_lines: &'a [&'a str], row: Row, col: Col) -> BadIdentNext<'a> {
let row_index = row as usize;
let col_index = col as usize;
match source_lines.get(row_index) {
None => BadIdentNext::Other(None),
Some(line) => {
let chars = &line[col_index..];
let mut it = chars.chars();
match roc_parse::keyword::KEYWORDS
.iter()
.find(|keyword| crate::error::parse::starts_with_keyword(chars, keyword))
{
Some(keyword) => BadIdentNext::Keyword(keyword),
None => match it.next() {
None => BadIdentNext::Other(None),
Some('.') => match it.next() {
Some(c) if c.is_lowercase() => {
BadIdentNext::LowercaseAccess(2 + till_whitespace(it) as u16)
}
Some(c) if c.is_uppercase() => {
BadIdentNext::UppercaseAccess(2 + till_whitespace(it) as u16)
}
Some(c) if c.is_ascii_digit() => {
BadIdentNext::NumberAccess(2 + till_whitespace(it) as u16)
}
_ => BadIdentNext::DanglingDot,
},
Some(c) => BadIdentNext::Other(Some(c)),
},
}
}
}
}
fn till_whitespace<I>(it: I) -> usize
where
I: Iterator<Item = char>,
{
let mut chomped = 0;
for c in it {
if c.is_ascii_whitespace() || c == '#' {
break;
} else {
chomped += 1;
continue;
}
}
chomped
}
fn pretty_runtime_error<'b>(
alloc: &'b RocDocAllocator<'b>,
runtime_error: RuntimeError,

View File

@ -125,6 +125,22 @@ fn to_syntax_report<'a>(
report(doc)
}
NotEndOfFile(row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, *row, *col);
let region = Region::from_row_col(*row, *col);
let doc = alloc.stack(vec![
alloc.reflow(r"I expected to reach the end of the file, but got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![alloc.reflow("no hints")]),
]);
Report {
filename,
doc,
title: "NOT END OF FILE".to_string(),
}
}
SyntaxError::Eof(region) => {
let doc = alloc.stack(vec![alloc.reflow("End of Field"), alloc.region(*region)]);
@ -153,6 +169,7 @@ fn to_syntax_report<'a>(
0,
0,
),
Header(header) => to_header_report(alloc, filename, &header, 0, 0),
_ => todo!("unhandled parse error: {:?}", parse_problem),
}
}
@ -171,6 +188,8 @@ enum Node {
IfElseBranch,
ListElement,
InsideParens,
RecordConditionalDefault,
StringFormat,
}
fn to_expr_report<'a>(
@ -197,14 +216,12 @@ fn to_expr_report<'a>(
to_expr_in_parens_report(alloc, filename, context, &expr, *row, *col)
}
EExpr::Type(tipe, row, col) => to_type_report(alloc, filename, &tipe, *row, *col),
EExpr::Def(syntax, row, col) => to_syntax_report(alloc, filename, syntax, *row, *col),
EExpr::ElmStyleFunction(region, row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, *row, *col);
let region = *region;
let doc = alloc.stack(vec![
alloc.reflow(r"I am in the middle of parsing a definition, but I got stuck here:"),
alloc.reflow(r"I am partway through parsing a definition, but I got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("Looks like you are trying to define a function. "),
@ -344,6 +361,8 @@ fn to_expr_report<'a>(
]),
),
Node::ListElement => (r, c, alloc.text("a list")),
Node::RecordConditionalDefault => (r, c, alloc.text("record field default")),
Node::StringFormat => (r, c, alloc.text("a string format")),
Node::InsideParens => (r, c, alloc.text("some parentheses")),
},
Context::InDef(r, c) => (r, c, alloc.text("a definition")),
@ -375,12 +394,39 @@ fn to_expr_report<'a>(
}
}
EExpr::DefMissingFinalExpr(row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, *row, *col);
let region = Region::from_row_col(*row, *col);
let doc = alloc.stack(vec![
alloc.reflow(r"I am partway through parsing a definition, but I got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("This definition is missing a final expression."),
alloc.reflow(" A nested definition must be followed by"),
alloc.reflow(" either another definition, or an expression"),
]),
alloc.vcat(vec![
alloc.text("x = 4").indent(4),
alloc.text("y = 2").indent(4),
alloc.text(""),
alloc.text("x + y").indent(4),
]),
]);
Report {
filename,
doc,
title: "MISSING FINAL EXPRESSION".to_string(),
}
}
EExpr::Colon(row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, *row, *col);
let region = Region::from_row_col(*row, *col);
let doc = alloc.stack(vec![
alloc.reflow(r"I am in the middle of parsing a definition, but I got stuck here:"),
alloc.reflow(r"I am partway through parsing a definition, but I got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("Looks like you are trying to define a function. "),
@ -419,7 +465,7 @@ fn to_lambda_report<'a>(
let doc = alloc.stack(vec![
alloc
.reflow(r"I am in the middle of parsing a function argument list, but I got stuck here:"),
.reflow(r"I am partway through parsing a function argument list, but I got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("I was expecting a "),
@ -440,7 +486,7 @@ fn to_lambda_report<'a>(
let doc = alloc.stack(vec![
alloc
.reflow(r"I am in the middle of parsing a function argument list, but I got stuck here:"),
.reflow(r"I am partway through parsing a function argument list, but I got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("I was expecting a "),
@ -464,7 +510,7 @@ fn to_lambda_report<'a>(
let doc = alloc.stack(vec![
alloc
.reflow(r"I am in the middle of parsing a function argument list, but I got stuck here:"),
.reflow(r"I am partway through parsing a function argument list, but I got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("I was expecting a "),
@ -485,7 +531,7 @@ fn to_lambda_report<'a>(
let doc = alloc.stack(vec![
alloc
.reflow(r"I am in the middle of parsing a function argument list, but I got stuck here:"),
.reflow(r"I am partway through parsing a function argument list, but I got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("I was expecting a "),
@ -509,7 +555,7 @@ fn to_lambda_report<'a>(
let doc = alloc.stack(vec![
alloc
.reflow(r"I am in the middle of parsing a function argument list, but I got stuck at this comma:"),
.reflow(r"I am partway through parsing a function argument list, but I got stuck at this comma:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("I was expecting an argument pattern before this, "),
@ -529,7 +575,7 @@ fn to_lambda_report<'a>(
let doc = alloc.stack(vec![
alloc
.reflow(r"I am in the middle of parsing a function argument list, but I got stuck here:"),
.reflow(r"I am partway through parsing a function argument list, but I got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("I was expecting an argument pattern before this, "),
@ -636,7 +682,7 @@ fn to_unfinished_lambda_report<'a>(
fn to_str_report<'a>(
alloc: &'a RocDocAllocator<'a>,
filename: PathBuf,
_context: Context,
context: Context,
parse_problem: &roc_parse::parser::EString<'a>,
start_row: Row,
start_col: Col,
@ -645,7 +691,14 @@ fn to_str_report<'a>(
match *parse_problem {
EString::Open(_row, _col) => unreachable!("another branch would be taken"),
EString::Format(syntax, row, col) => to_syntax_report(alloc, filename, syntax, row, col),
EString::Format(expr, row, col) => to_expr_report(
alloc,
filename,
Context::InNode(Node::StringFormat, start_row, start_col, Box::new(context)),
expr,
row,
col,
),
EString::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col),
EString::UnknownEscape(row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
@ -712,6 +765,26 @@ fn to_str_report<'a>(
title: "WEIRD CODE POINT".to_string(),
}
}
EString::FormatEnd(row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
let region = Region::from_row_col(row, col);
let doc = alloc.stack(vec![
alloc.reflow(r"I cannot find the end of this format expression:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow(r"You could change it to something like "),
alloc.parser_suggestion("\"The count is \\(count\\)\""),
alloc.reflow("."),
]),
]);
Report {
filename,
doc,
title: "ENDLESS FORMAT".to_string(),
}
}
EString::EndlessSingle(row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
let region = Region::from_row_col(row, col);
@ -839,7 +912,6 @@ fn to_list_report<'a>(
use roc_parse::parser::List;
match *parse_problem {
List::Syntax(syntax, row, col) => to_syntax_report(alloc, filename, syntax, row, col),
List::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col),
List::Expr(expr, row, col) => to_expr_report(
@ -948,7 +1020,6 @@ fn to_if_report<'a>(
use roc_parse::parser::If;
match *parse_problem {
If::Syntax(syntax, row, col) => to_syntax_report(alloc, filename, syntax, row, col),
If::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col),
If::Condition(expr, row, col) => to_expr_report(
@ -1119,7 +1190,6 @@ fn to_when_report<'a>(
}
}
When::Syntax(syntax, row, col) => to_syntax_report(alloc, filename, syntax, row, col),
When::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col),
When::Branch(expr, row, col) => to_expr_report(
@ -1512,7 +1582,20 @@ fn to_precord_report<'a>(
PRecord::Pattern(pattern, row, col) => {
to_pattern_report(alloc, filename, pattern, row, col)
}
PRecord::Syntax(syntax, row, col) => to_syntax_report(alloc, filename, syntax, row, col),
PRecord::Expr(expr, row, col) => to_expr_report(
alloc,
filename,
Context::InNode(
Node::RecordConditionalDefault,
start_row,
start_col,
Box::new(Context::InDef(row, col)),
),
expr,
row,
col,
),
PRecord::IndentOpen(row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
@ -2469,6 +2552,479 @@ fn to_tapply_report<'a>(
}
}
fn to_header_report<'a>(
alloc: &'a RocDocAllocator<'a>,
filename: PathBuf,
parse_problem: &roc_parse::parser::EHeader<'a>,
start_row: Row,
start_col: Col,
) -> Report<'a> {
use roc_parse::parser::EHeader;
match parse_problem {
EHeader::Provides(provides, row, col) => {
to_provides_report(alloc, filename, &provides, *row, *col)
}
EHeader::Exposes(exposes, row, col) => {
to_exposes_report(alloc, filename, &exposes, *row, *col)
}
EHeader::Imports(imports, row, col) => {
to_imports_report(alloc, filename, &imports, *row, *col)
}
EHeader::Requires(requires, row, col) => {
to_requires_report(alloc, filename, &requires, *row, *col)
}
EHeader::Packages(packages, row, col) => {
to_packages_report(alloc, filename, &packages, *row, *col)
}
EHeader::Effects(effects, row, col) => {
to_effects_report(alloc, filename, &effects, *row, *col)
}
EHeader::IndentStart(row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, *row, *col);
let region = Region::from_row_col(*row, *col);
let doc = alloc.stack(vec![
alloc.reflow(r"I am partway through parsing a header, but got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![alloc.reflow("I may be confused by indentation.")]),
]);
Report {
filename,
doc,
title: "INCOMPLETE HEADER".to_string(),
}
}
EHeader::Start(row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, *row, *col);
let region = Region::from_row_col(*row, *col);
let doc = alloc.stack(vec![
alloc.reflow(r"I am expecting a header, but got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("I am expecting a module keyword next, one of "),
alloc.keyword("interface"),
alloc.reflow(", "),
alloc.keyword("app"),
alloc.reflow(" or "),
alloc.keyword("platform"),
alloc.reflow("."),
]),
]);
Report {
filename,
doc,
title: "MISSING HEADER".to_string(),
}
}
EHeader::ModuleName(row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, *row, *col);
let region = Region::from_row_col(*row, *col);
let doc = alloc.stack(vec![
alloc.reflow(r"I am partway through parsing a header, but got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("I am expecting a module name next, like "),
alloc.parser_suggestion("BigNum"),
alloc.reflow(" or "),
alloc.parser_suggestion("Main"),
alloc.reflow(". Module names must start with an uppercase letter."),
]),
]);
Report {
filename,
doc,
title: "WEIRD MODULE NAME".to_string(),
}
}
EHeader::AppName(_, row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, *row, *col);
let region = Region::from_row_col(*row, *col);
let doc = alloc.stack(vec![
alloc.reflow(r"I am partway through parsing a header, but got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("I am expecting an application name next, like "),
alloc.parser_suggestion("app \"main\""),
alloc.reflow(" or "),
alloc.parser_suggestion("app \"editor\""),
alloc.reflow(". App names are surrounded by quotation marks."),
]),
]);
Report {
filename,
doc,
title: "WEIRD APP NAME".to_string(),
}
}
EHeader::PlatformName(_, row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, *row, *col);
let region = Region::from_row_col(*row, *col);
let doc = alloc.stack(vec![
alloc.reflow(r"I am partway through parsing a header, but got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("I am expecting a platform name next, like "),
alloc.parser_suggestion("roc/core"),
alloc.reflow("."),
]),
]);
Report {
filename,
doc,
title: "WEIRD MODULE NAME".to_string(),
}
}
EHeader::Space(error, row, col) => to_space_report(alloc, filename, &error, *row, *col),
}
}
fn to_provides_report<'a>(
alloc: &'a RocDocAllocator<'a>,
filename: PathBuf,
parse_problem: &roc_parse::parser::EProvides,
start_row: Row,
start_col: Col,
) -> Report<'a> {
use roc_parse::parser::EProvides;
match *parse_problem {
EProvides::Identifier(row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
let region = Region::from_row_col(row, col);
let doc = alloc.stack(vec![
alloc
.reflow(r"I am partway through parsing a provides list, but I got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![alloc.reflow(
"I was expecting a type name, value name or function name next, like",
)]),
alloc
.parser_suggestion("provides [ Animal, default, tame ]")
.indent(4),
]);
Report {
filename,
doc,
title: "WEIRD PROVIDES".to_string(),
}
}
EProvides::Provides(row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
let region = Region::from_row_col(row, col);
let doc = alloc.stack(vec![
alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("I am expecting the "),
alloc.keyword("provides"),
alloc.reflow(" keyword next, like "),
]),
alloc
.parser_suggestion("provides [ Animal, default, tame ]")
.indent(4),
]);
Report {
filename,
doc,
title: "WEIRD PROVIDES".to_string(),
}
}
EProvides::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col),
_ => todo!("unhandled parse error {:?}", parse_problem),
}
}
fn to_exposes_report<'a>(
alloc: &'a RocDocAllocator<'a>,
filename: PathBuf,
parse_problem: &roc_parse::parser::EExposes,
start_row: Row,
start_col: Col,
) -> Report<'a> {
use roc_parse::parser::EExposes;
match *parse_problem {
EExposes::Identifier(row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
let region = Region::from_row_col(row, col);
let doc = alloc.stack(vec![
alloc.reflow(r"I am partway through parsing a exposes list, but I got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![alloc.reflow(
"I was expecting a type name, value name or function name next, like",
)]),
alloc
.parser_suggestion("exposes [ Animal, default, tame ]")
.indent(4),
]);
Report {
filename,
doc,
title: "WEIRD EXPOSES".to_string(),
}
}
EExposes::Exposes(row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
let region = Region::from_row_col(row, col);
let doc = alloc.stack(vec![
alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("I am expecting the "),
alloc.keyword("exposes"),
alloc.reflow(" keyword next, like "),
]),
alloc
.parser_suggestion("exposes [ Animal, default, tame ]")
.indent(4),
]);
Report {
filename,
doc,
title: "WEIRD EXPOSES".to_string(),
}
}
EExposes::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col),
_ => todo!("unhandled parse error {:?}", parse_problem),
}
}
fn to_imports_report<'a>(
alloc: &'a RocDocAllocator<'a>,
filename: PathBuf,
parse_problem: &roc_parse::parser::EImports,
start_row: Row,
start_col: Col,
) -> Report<'a> {
use roc_parse::parser::EImports;
match *parse_problem {
EImports::Identifier(row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
let region = Region::from_row_col(row, col);
let doc = alloc.stack(vec![
alloc.reflow(r"I am partway through parsing a imports list, but I got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![alloc.reflow(
"I was expecting a type name, value name or function name next, like ",
)]),
alloc
.parser_suggestion("imports [ Animal, default, tame ]")
.indent(4),
]);
Report {
filename,
doc,
title: "WEIRD EXPOSES".to_string(),
}
}
EImports::Imports(row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
let region = Region::from_row_col(row, col);
let doc = alloc.stack(vec![
alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("I am expecting the "),
alloc.keyword("imports"),
alloc.reflow(" keyword next, like "),
]),
alloc
.parser_suggestion("imports [ Animal, default, tame ]")
.indent(4),
]);
Report {
filename,
doc,
title: "WEIRD IMPORTS".to_string(),
}
}
EImports::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col),
EImports::ModuleName(row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
let region = Region::from_row_col(row, col);
let doc = alloc.stack(vec![
alloc.reflow(r"I am partway through parsing a header, but got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("I am expecting a module name next, like "),
alloc.parser_suggestion("BigNum"),
alloc.reflow(" or "),
alloc.parser_suggestion("Main"),
alloc.reflow(". Module names must start with an uppercase letter."),
]),
]);
Report {
filename,
doc,
title: "WEIRD MODULE NAME".to_string(),
}
}
_ => todo!("unhandled parse error {:?}", parse_problem),
}
}
fn to_requires_report<'a>(
alloc: &'a RocDocAllocator<'a>,
filename: PathBuf,
parse_problem: &roc_parse::parser::ERequires<'a>,
start_row: Row,
start_col: Col,
) -> Report<'a> {
use roc_parse::parser::ERequires;
match *parse_problem {
ERequires::Requires(row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
let region = Region::from_row_col(row, col);
let doc = alloc.stack(vec![
alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("I am expecting the "),
alloc.keyword("requires"),
alloc.reflow(" keyword next, like "),
]),
alloc
.parser_suggestion("requires { main : Task I64 Str }")
.indent(4),
]);
Report {
filename,
doc,
title: "MISSING REQUIRES".to_string(),
}
}
ERequires::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col),
_ => todo!("unhandled parse error {:?}", parse_problem),
}
}
fn to_packages_report<'a>(
alloc: &'a RocDocAllocator<'a>,
filename: PathBuf,
parse_problem: &roc_parse::parser::EPackages,
start_row: Row,
start_col: Col,
) -> Report<'a> {
use roc_parse::parser::EPackages;
match *parse_problem {
EPackages::Packages(row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
let region = Region::from_row_col(row, col);
let doc = alloc.stack(vec![
alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("I am expecting the "),
alloc.keyword("packages"),
alloc.reflow(" keyword next, like "),
]),
alloc.parser_suggestion("packages {}").indent(4),
]);
Report {
filename,
doc,
title: "MISSING PACKAGES".to_string(),
}
}
EPackages::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col),
_ => todo!("unhandled parse error {:?}", parse_problem),
}
}
fn to_effects_report<'a>(
alloc: &'a RocDocAllocator<'a>,
filename: PathBuf,
parse_problem: &roc_parse::parser::EEffects,
start_row: Row,
start_col: Col,
) -> Report<'a> {
use roc_parse::parser::EEffects;
match *parse_problem {
EEffects::Effects(row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
let region = Region::from_row_col(row, col);
let doc = alloc.stack(vec![
alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("I am expecting the "),
alloc.keyword("effects"),
alloc.reflow(" keyword next, like "),
]),
alloc.parser_suggestion("effects {}").indent(4),
]);
Report {
filename,
doc,
title: "MISSING PACKAGES".to_string(),
}
}
EEffects::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col),
_ => todo!("unhandled parse error {:?}", parse_problem),
}
}
fn to_space_report<'a>(
alloc: &'a RocDocAllocator<'a>,
filename: PathBuf,
@ -2538,7 +3094,7 @@ fn what_is_next<'a>(source_lines: &'a [&'a str], row: Row, col: Col) -> Next<'a>
}
}
fn starts_with_keyword(rest_of_line: &str, keyword: &str) -> bool {
pub fn starts_with_keyword(rest_of_line: &str, keyword: &str) -> bool {
if let Some(stripped) = rest_of_line.strip_prefix(keyword) {
match stripped.chars().next() {
None => true,

View File

@ -11,9 +11,6 @@ use roc_collections::all::{ImMap, MutMap, SendSet};
use roc_constrain::expr::constrain_expr;
use roc_constrain::module::{constrain_imported_values, Import};
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds};
use roc_parse::ast::{self, Attempting};
use roc_parse::blankspace::space0_before;
use roc_parse::parser::{loc, Parser, State, SyntaxError};
use roc_problem::can::Problem;
use roc_region::all::Located;
use roc_solve::solve;
@ -100,28 +97,9 @@ pub struct CanExprOut {
pub constraint: Constraint,
}
#[allow(dead_code)]
pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Expr<'a>, SyntaxError<'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>>, SyntaxError<'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<'a> {
pub fail: SyntaxError<'a>,
pub fail: roc_parse::parser::SyntaxError<'a>,
pub home: ModuleId,
pub interns: Interns,
}
@ -132,7 +110,7 @@ pub fn can_expr_with<'a>(
home: ModuleId,
expr_str: &'a str,
) -> Result<CanExprOut, ParseErrOut<'a>> {
let loc_expr = match parse_loc_with(&arena, expr_str) {
let loc_expr = match roc_parse::test_helpers::parse_loc_with(&arena, expr_str) {
Ok(e) => e,
Err(fail) => {
let interns = Interns::default();

View File

@ -65,6 +65,7 @@ mod test_reporting {
problems: can_problems,
..
} = can_expr(arena, expr_src)?;
dbg!(&loc_expr);
let mut subs = Subs::new(var_store.into());
for (var, name) in output.introduced_variables.name_by_var {
@ -169,6 +170,37 @@ mod test_reporting {
}
}
fn list_header_reports<F>(arena: &Bump, src: &str, buf: &mut String, callback: F)
where
F: FnOnce(RocDocBuilder<'_>, &mut String),
{
use ven_pretty::DocAllocator;
use roc_parse::parser::State;
let state = State::new(src.as_bytes());
let filename = filename_from_string(r"\code\proj\Main.roc");
let src_lines: Vec<&str> = src.split('\n').collect();
match roc_parse::module::parse_header(arena, state) {
Err(fail) => {
let interns = Interns::default();
let home = crate::helpers::test_home();
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
use roc_parse::parser::SyntaxError;
let problem =
SyntaxError::Header(fail).into_parse_problem(filename.clone(), src.as_bytes());
let doc = parse_problem(&alloc, filename, 0, problem);
callback(doc.pretty(&alloc).append(alloc.line()), buf)
}
Ok(_) => todo!(),
}
}
fn report_problem_as(src: &str, expected_rendering: &str) {
let mut buf: String = String::new();
let arena = Bump::new();
@ -193,6 +225,30 @@ mod test_reporting {
assert_eq!(buf, expected_rendering);
}
fn report_header_problem_as(src: &str, expected_rendering: &str) {
let mut buf: String = String::new();
let arena = Bump::new();
let callback = |doc: RocDocBuilder<'_>, buf: &mut String| {
doc.1
.render_raw(70, &mut roc_reporting::report::CiWrite::new(buf))
.expect("list_reports")
};
list_header_reports(&arena, src, &mut buf, callback);
// convenient to copy-paste the generated message
if true {
if buf != expected_rendering {
for line in buf.split("\n") {
println!(" {}", line);
}
}
}
assert_eq!(buf, expected_rendering);
}
fn color_report_problem_as(src: &str, expected_rendering: &str) {
let mut buf: String = String::new();
let arena = Bump::new();
@ -3160,7 +3216,7 @@ mod test_reporting {
r#"
ARGUMENTS BEFORE EQUALS
I am in the middle of parsing a definition, but I got stuck here:
I am partway through parsing a definition, but I got stuck here:
1 f x y = x
^^^
@ -4070,12 +4126,12 @@ mod test_reporting {
r#"
SYNTAX PROBLEM
I trying to parse a record field accessor here:
I trying to parse a record field access here:
1 foo.bar.
^
Something like .name or .height that accesses a value from a record.
So I expect to see a lowercase letter next, like .name or .height.
"#
),
)
@ -4093,14 +4149,13 @@ mod test_reporting {
r#"
SYNTAX PROBLEM
I am trying to parse a qualified name here:
I am very confused by this expression:
1 @Foo.Bar
^
^^^^
This looks like a qualified tag name to me, but tags cannot be
qualified! Maybe you wanted a qualified name, something like
Json.Decode.string?
Looks like a private tag is treated like a module name. Maybe you
wanted a qualified name, like Json.Decode.string?
"#
),
)
@ -4145,18 +4200,18 @@ mod test_reporting {
x == 5
Num.add 1 2
x y
{ x, y }
"#
),
indoc!(
r#"
TOO MANY ARGS
The `add` function expects 2 arguments, but it got 4 instead:
4 Num.add 1 2
^^^^^^^
This value is not a function, but it was given 3 arguments:
3 x == 5
^
Are there any missing commas? Or missing parentheses?
"#
),
@ -4508,21 +4563,21 @@ mod test_reporting {
indoc!(
r#"
f : Foo..Bar
f
"#
),
indoc!(
r#"
DOUBLE DOT
I encountered two dots in a row:
1 f : Foo..Bar
^
Try removing one of them.
"#
),
indoc!(r#""#),
)
// ── DOUBLE DOT ──────────────────────────────────────────────────────────────────
//
// I encountered two dots in a row:
//
// 1│ f : Foo..Bar
// ^
//
// Try removing one of them.
}
#[test]
@ -4531,22 +4586,22 @@ mod test_reporting {
indoc!(
r#"
f : Foo.Bar.
f
"#
),
indoc!(
r#"
TRAILING DOT
I encountered a dot with nothing after it:
1 f : Foo.Bar.
^
Dots are used to refer to a type in a qualified way, like
Num.I64 or List.List a. Try adding a type name next.
"#
),
indoc!(r#""#),
)
// ── TRAILING DOT ────────────────────────────────────────────────────────────────
//
// I encountered a dot with nothing after it:
//
// 1│ f : Foo.Bar.
// ^
//
// Dots are used to refer to a type in a qualified way, like
// Num.I64 or List.List a. Try adding a type name next.
}
#[test]
@ -4582,26 +4637,40 @@ mod test_reporting {
indoc!(
r#"
f : Foo.1
f
"#
),
indoc!(
r#"
WEIRD QUALIFIED NAME
I encountered a number at the start of a qualified name segment:
1 f : Foo.1
^
All parts of a qualified type name must start with an uppercase
letter, like Num.I64 or List.List a.
"#
),
indoc!(r#""#),
)
// ── WEIRD QUALIFIED NAME ────────────────────────────────────────────────────────
//
// I encountered a number at the start of a qualified name segment:
//
// 1│ f : Foo.1
// ^
//
// All parts of a qualified type name must start with an uppercase
// letter, like Num.I64 or List.List a.
}
#[test]
fn type_apply_start_with_lowercase() {
report_problem_as(
indoc!(
r#"
f : Foo.foo
f
"#
),
indoc!(r#""#),
)
}
#[test]
fn def_missing_final_expression() {
report_problem_as(
indoc!(
r#"
@ -4610,16 +4679,20 @@ mod test_reporting {
),
indoc!(
r#"
WEIRD QUALIFIED NAME
MISSING FINAL EXPRESSION
I encountered a lowercase letter at the start of a qualified name
segment:
I am partway through parsing a definition, but I got stuck here:
1 f : Foo.foo
^
^
All parts of a qualified type name must start with an uppercase
letter, like Num.I64 or List.List a.
This definition is missing a final expression. A nested definition
must be followed by either another definition, or an expression
x = 4
y = 2
x + y
"#
),
)
@ -4984,8 +5057,8 @@ mod test_reporting {
r#"
UNFINISHED ARGUMENT LIST
I am in the middle of parsing a function argument list, but I got
stuck at this comma:
I am partway through parsing a function argument list, but I got stuck
at this comma:
1 \a,,b -> 1
^
@ -5009,8 +5082,8 @@ mod test_reporting {
r#"
UNFINISHED ARGUMENT LIST
I am in the middle of parsing a function argument list, but I got
stuck at this comma:
I am partway through parsing a function argument list, but I got stuck
at this comma:
1 \,b -> 1
^
@ -5417,12 +5490,12 @@ mod test_reporting {
r#"
SYNTAX PROBLEM
I trying to parse a record field accessor here:
I trying to parse a record field access here:
1 Num.add . 23
^
Something like .name or .height that accesses a value from a record.
So I expect to see a lowercase letter next, like .name or .height.
"#
),
)
@ -5468,7 +5541,7 @@ mod test_reporting {
I am very confused by this field access:
1 @UUID.bar
^^^^^^^^^
^^^^
It looks like a record field access on a private tag.
"#
@ -5655,22 +5728,24 @@ mod test_reporting {
indoc!(
r#"
main =
5 : I64
(\x -> x) : I64
3
"#
),
indoc!(
r#"
UNKNOWN OPERATOR
This looks like an operator, but it's not one I recognize!
1 main =
2 5 : I64
^
2 (\x -> x) : I64
^
The has-type operator : can only occur in a definition's type
signature, like
increment : I64 -> I64
increment = \x -> x + 1
"#
@ -5702,4 +5777,163 @@ mod test_reporting {
),
)
}
#[test]
fn provides_to_identifier() {
report_header_problem_as(
indoc!(
r#"
app "test-base64"
packages { base: "platform" }
imports [base.Task, Base64 ]
provides [ main, @Foo ] to base
"#
),
indoc!(
r#"
WEIRD PROVIDES
I am partway through parsing a provides list, but I got stuck here:
3 imports [base.Task, Base64 ]
4 provides [ main, @Foo ] to base
^
I was expecting a type name, value name or function name next, like
provides [ Animal, default, tame ]
"#
),
)
}
#[test]
fn exposes_identifier() {
report_header_problem_as(
indoc!(
r#"
interface Foobar
exposes [ main, @Foo ]
imports [base.Task, Base64 ]
"#
),
indoc!(
r#"
WEIRD EXPOSES
I am partway through parsing a exposes list, but I got stuck here:
1 interface Foobar
2 exposes [ main, @Foo ]
^
I was expecting a type name, value name or function name next, like
exposes [ Animal, default, tame ]
"#
),
)
}
#[test]
fn invalid_module_name() {
report_header_problem_as(
indoc!(
r#"
interface foobar
exposes [ main, @Foo ]
imports [base.Task, Base64 ]
"#
),
indoc!(
r#"
WEIRD MODULE NAME
I am partway through parsing a header, but got stuck here:
1 interface foobar
^
I am expecting a module name next, like BigNum or Main. Module names
must start with an uppercase letter.
"#
),
)
}
#[test]
fn invalid_app_name() {
report_header_problem_as(
indoc!(
r#"
app foobar
exposes [ main, @Foo ]
imports [base.Task, Base64 ]
"#
),
indoc!(
r#"
WEIRD APP NAME
I am partway through parsing a header, but got stuck here:
1 app foobar
^
I am expecting an application name next, like app "main" or
app "editor". App names are surrounded by quotation marks.
"#
),
)
}
#[test]
fn apply_unary_negative() {
report_problem_as(
indoc!(
r#"
foo = 3
-foo 1 2
"#
),
indoc!(
r#"
TOO MANY ARGS
This value is not a function, but it was given 2 arguments:
3 -foo 1 2
^^^^
Are there any missing commas? Or missing parentheses?
"#
),
)
}
#[test]
fn apply_unary_not() {
report_problem_as(
indoc!(
r#"
foo = True
!foo 1 2
"#
),
indoc!(
r#"
TOO MANY ARGS
This value is not a function, but it was given 2 arguments:
3 !foo 1 2
^^^^
Are there any missing commas? Or missing parentheses?
"#
),
)
}
}

View File

@ -3217,6 +3217,18 @@ mod solve_expr {
);
}
#[test]
fn max_i128() {
infer_eq_without_problem(
indoc!(
r#"
Num.maxI128
"#
),
"I128",
);
}
#[test]
fn reconstruct_path() {
infer_eq_without_problem(

View File

@ -318,13 +318,13 @@ fn unit_values() {
}
#[test]
fn singleton() {
fn single() {
assert_evals_to!(
indoc!(
r#"
myDict : Dict I64 {}
myDict =
Dict.singleton 0 {}
Dict.single 0 {}
Dict.len myDict
"#
@ -341,7 +341,7 @@ fn union() {
r#"
myDict : Dict I64 {}
myDict =
Dict.union (Dict.singleton 0 {}) (Dict.singleton 1 {})
Dict.union (Dict.single 0 {}) (Dict.single 1 {})
Dict.len myDict
"#
@ -358,7 +358,7 @@ fn union_prefer_first() {
r#"
myDict : Dict I64 I64
myDict =
Dict.union (Dict.singleton 0 100) (Dict.singleton 0 200)
Dict.union (Dict.single 0 100) (Dict.single 0 200)
Dict.values myDict
"#

View File

@ -568,6 +568,36 @@ fn list_map_closure() {
);
}
#[test]
fn list_map3_group() {
assert_evals_to!(
indoc!(
r#"
List.map3 [1,2,3] [3,2,1] [2,1,3] (\a, b, c -> Group a b c)
"#
),
RocList::from_slice(&[(1, 3, 2), (2, 2, 1), (3, 1, 3)]),
RocList<(i64, i64, i64)>
);
}
#[test]
fn list_map3_different_length() {
assert_evals_to!(
indoc!(
r#"
List.map3
["a", "b", "d"]
["b", "x"]
["c"]
(\a, b, c -> Str.concat a (Str.concat b c))
"#
),
RocList::from_slice(&[RocStr::from_slice("abc".as_bytes()),]),
RocList<RocStr>
);
}
#[test]
fn list_map2_pair() {
assert_evals_to!(
@ -708,7 +738,9 @@ fn list_repeat() {
RocList<i64>
);
assert_evals_to!("List.repeat 2 []", &[&[], &[]], &'static [&'static [i64]]);
let empty_lists: &'static [&'static [i64]] = &[&[], &[]];
assert_evals_to!("List.repeat 2 []", empty_lists, &'static [&'static [i64]]);
assert_evals_to!(
indoc!(
r#"
@ -719,7 +751,7 @@ fn list_repeat() {
List.repeat 2 noStrs
"#
),
&[&[], &[]],
empty_lists,
&'static [&'static [i64]]
);

View File

@ -1364,4 +1364,30 @@ mod gen_num {
assert_evals_to!("Num.shiftRightBy 2 0b0000_0010u8", 0b0000_0001, i64);
assert_evals_to!("Num.shiftRightBy 3 0b0000_1100u8", 0b0000_0011, i64);
}
#[test]
fn max_i128() {
assert_evals_to!(
indoc!(
r#"
Num.maxI128
"#
),
i128::MAX,
i128
);
}
#[test]
fn is_multiple_of() {
// true
assert_evals_to!("Num.isMultipleOf 5 1", true, bool);
assert_evals_to!("Num.isMultipleOf 5 -1", true, bool);
assert_evals_to!("Num.isMultipleOf 0 0", true, bool);
assert_evals_to!("Num.isMultipleOf 0 1", true, bool);
assert_evals_to!("Num.isMultipleOf 0 -1", true, bool);
// false
assert_evals_to!("Num.isMultipleOf 5 2", false, bool);
assert_evals_to!("Num.isMultipleOf 5 0", false, bool);
}
}

View File

@ -18,11 +18,11 @@ fn empty_len() {
}
#[test]
fn singleton_len() {
fn single_len() {
assert_evals_to!(
indoc!(
r#"
Set.len (Set.singleton 42)
Set.len (Set.single 42)
"#
),
1,
@ -31,11 +31,11 @@ fn singleton_len() {
}
#[test]
fn singleton_to_list() {
fn single_to_list() {
assert_evals_to!(
indoc!(
r#"
Set.toList (Set.singleton 42)
Set.toList (Set.single 42)
"#
),
&[42],
@ -45,7 +45,7 @@ fn singleton_to_list() {
assert_evals_to!(
indoc!(
r#"
Set.toList (Set.singleton 1)
Set.toList (Set.single 1)
"#
),
&[1],
@ -55,7 +55,7 @@ fn singleton_to_list() {
assert_evals_to!(
indoc!(
r#"
Set.toList (Set.singleton 1.0)
Set.toList (Set.single 1.0)
"#
),
&[1.0],
@ -196,6 +196,8 @@ fn contains() {
#[test]
fn from_list() {
let empty_list: &'static [i64] = &[];
assert_evals_to!(
indoc!(
r#"
@ -216,7 +218,7 @@ fn from_list() {
|> Set.toList
"#
),
&[],
empty_list,
&[i64]
);
@ -231,7 +233,7 @@ fn from_list() {
|> Set.toList
"#
),
&[],
empty_list,
&[i64]
);
}

View File

@ -105,7 +105,7 @@ pub fn aliases() -> MutMap<Symbol, BuiltinAlias> {
BuiltinAlias {
region: Region::zero(),
vars: Vec::new(),
typ: int_alias_content(signed128_type()),
typ: i128_alias_content(),
},
);
@ -410,6 +410,18 @@ fn u64_alias_content() -> SolvedType {
int_alias_content(unsigned64_type())
}
// I128
#[inline(always)]
pub fn i128_type() -> SolvedType {
SolvedType::Alias(Symbol::NUM_I128, vec![], Box::new(i128_alias_content()))
}
#[inline(always)]
fn i128_alias_content() -> SolvedType {
int_alias_content(signed128_type())
}
// INT
#[inline(always)]

View File

@ -178,7 +178,9 @@ pub fn set_caret_at_start(markup_node: &mut MarkupNode) {
ast_node_id: _,
children,
} => {
if let Some(child) = children.first_mut() { set_caret_at_start(child) }
if let Some(child) = children.first_mut() {
set_caret_at_start(child)
}
}
MarkupNode::Text {
content: _,

View File

@ -5,8 +5,10 @@ use crate::editor::syntax_highlight::HighlightStyle;
use crate::graphics::primitives::rect::Rect;
use crate::lang::ast::Expr2;
use crate::lang::expr::{str_to_expr2, Env};
use crate::lang::scope::Scope;
use bumpalo::collections::String as BumpString;
use bumpalo::Bump;
use roc_region::all::Region;
#[derive(Debug)]
pub struct EdModel<'a> {
@ -58,7 +60,11 @@ pub struct EdModule<'a> {
impl<'a> EdModule<'a> {
pub fn new(code_str: &'a str, mut env: Env<'a>, ast_arena: &'a Bump) -> EdResult<EdModule<'a>> {
if !code_str.is_empty() {
let expr2_result = str_to_expr2(&ast_arena, &code_str, &mut env);
let mut scope = Scope::new(env.home, env.pool, env.var_store);
let region = Region::new(0, 0, 0, 0);
let expr2_result = str_to_expr2(&ast_arena, &code_str, &mut env, &mut scope, region);
match expr2_result {
Ok((expr2, _output)) => Ok(EdModule {

View File

@ -16,9 +16,8 @@ use roc_module::ident::ModuleName;
use roc_module::low_level::LowLevel;
use roc_module::operator::CalledVia;
use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol};
use roc_parse::ast;
use roc_parse::ast::StrLiteral;
use roc_parse::ast::{self, Attempting};
use roc_parse::blankspace::space0_before;
use roc_parse::expr::expr;
use roc_parse::parser::{loc, Parser, State, SyntaxError};
use roc_problem::can::{Problem, RuntimeError};
@ -232,18 +231,13 @@ pub fn str_to_expr2<'a>(
arena: &'a Bump,
input: &'a str,
env: &mut Env<'a>,
scope: &mut Scope,
region: Region,
) -> Result<(Expr2, self::Output), SyntaxError<'a>> {
let state = State::new_in(arena, input.trim().as_bytes(), Attempting::Module);
let parser = space0_before(loc(expr(0)), 0);
let parse_res = parser.parse(&arena, state);
let mut scope = Scope::new(env.home, env.pool, env.var_store);
let region = Region::new(0, 0, 0, 0);
parse_res
.map(|(_, loc_expr, _)| arena.alloc(loc_expr.value))
.map(|loc_expr_val_ref| to_expr2(env, &mut scope, loc_expr_val_ref, region))
.map_err(|(_, fail, _)| fail)
match roc_parse::test_helpers::parse_loc_with(arena, input.trim()) {
Ok(loc_expr) => Ok(to_expr2(env, scope, arena.alloc(loc_expr.value), region)),
Err(fail) => Err(fail),
}
}
pub fn to_expr2<'a>(

View File

@ -2,7 +2,7 @@ use bumpalo::collections::Vec;
use bumpalo::Bump;
use roc_fmt::def::fmt_def;
use roc_fmt::module::fmt_module;
use roc_parse::ast::{Attempting, Def, Module};
use roc_parse::ast::{Def, Module};
use roc_parse::module::module_defs;
use roc_parse::parser;
use roc_parse::parser::{Parser, SyntaxError};
@ -36,11 +36,11 @@ impl<'a> File<'a> {
let allocation = arena.alloc(bytes);
let module_parse_state = parser::State::new_in(arena, allocation, Attempting::Module);
let parsed_module = roc_parse::module::header().parse(&arena, module_parse_state);
let module_parse_state = parser::State::new(allocation);
let parsed_module = roc_parse::module::parse_header(&arena, module_parse_state);
match parsed_module {
Ok((_, module, state)) => {
Ok((module, state)) => {
let parsed_defs = module_defs().parse(&arena, state);
match parsed_defs {
@ -52,7 +52,7 @@ impl<'a> File<'a> {
Err((_, error, _)) => Err(ReadError::ParseDefs(error)),
}
}
Err((_, error, _)) => Err(ReadError::ParseHeader(error)),
Err(error) => Err(ReadError::ParseHeader(SyntaxError::Header(error))),
}
}

4
examples/.gitignore vendored
View File

@ -8,4 +8,8 @@ benchmarks/nqueens
benchmarks/deriv
benchmarks/cfold
benchmarks/rbtree-insert
benchmarks/rbtree-del
benchmarks/closure
benchmarks/test-astar
benchmarks/test-base64
effect-example

View File

@ -15,8 +15,8 @@ initialModel : position -> Model position
initialModel = \start ->
{
evaluated : Set.empty,
openSet : Set.singleton start,
costs : Dict.singleton start 0,
openSet : Set.single start,
costs : Dict.single start 0,
cameFrom : Dict.empty
}

View File

@ -6,6 +6,20 @@ const expectEqual = testing.expectEqual;
const expect = testing.expect;
const maxInt = std.math.maxInt;
comptime {
// This is a workaround for https://github.com/ziglang/zig/issues/8218
// which is only necessary on macOS.
//
// Once that issue is fixed, we can undo the changes in
// 177cf12e0555147faa4d436e52fc15175c2c4ff0 and go back to passing
// -fcompiler-rt in link.rs instead of doing this. Note that this
// workaround is present in many host.zig files, so make sure to undo
// it everywhere!
if (std.builtin.os.tag == .macos) {
_ = @import("compiler_rt");
}
}
const mem = std.mem;
const Allocator = mem.Allocator;

View File

@ -5,6 +5,20 @@ const testing = std.testing;
const expectEqual = testing.expectEqual;
const expect = testing.expect;
comptime {
// This is a workaround for https://github.com/ziglang/zig/issues/8218
// which is only necessary on macOS.
//
// Once that issue is fixed, we can undo the changes in
// 177cf12e0555147faa4d436e52fc15175c2c4ff0 and go back to passing
// -fcompiler-rt in link.rs instead of doing this. Note that this
// workaround is present in many host.zig files, so make sure to undo
// it everywhere!
if (std.builtin.os.tag == .macos) {
_ = @import("compiler_rt");
}
}
const mem = std.mem;
const Allocator = mem.Allocator;

View File

@ -3,6 +3,20 @@ const testing = std.testing;
const expectEqual = testing.expectEqual;
const expect = testing.expect;
comptime {
// This is a workaround for https://github.com/ziglang/zig/issues/8218
// which is only necessary on macOS.
//
// Once that issue is fixed, we can undo the changes in
// 177cf12e0555147faa4d436e52fc15175c2c4ff0 and go back to passing
// -fcompiler-rt in link.rs instead of doing this. Note that this
// workaround is present in many host.zig files, so make sure to undo
// it everywhere!
if (std.builtin.os.tag == .macos) {
_ = @import("compiler_rt");
}
}
const mem = std.mem;
const Allocator = mem.Allocator;

View File

@ -5,6 +5,20 @@ const testing = std.testing;
const expectEqual = testing.expectEqual;
const expect = testing.expect;
comptime {
// This is a workaround for https://github.com/ziglang/zig/issues/8218
// which is only necessary on macOS.
//
// Once that issue is fixed, we can undo the changes in
// 177cf12e0555147faa4d436e52fc15175c2c4ff0 and go back to passing
// -fcompiler-rt in link.rs instead of doing this. Note that this
// workaround is present in many host.zig files, so make sure to undo
// it everywhere!
if (std.builtin.os.tag == .macos) {
_ = @import("compiler_rt");
}
}
const mem = std.mem;
const Allocator = mem.Allocator;