mirror of
https://github.com/roc-lang/roc.git
synced 2024-09-21 15:59:20 +03:00
Merge branch 'trunk' of github.com:rtfeldman/roc into markup
This commit is contained in:
commit
c0dbea6ab1
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -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",
|
||||
|
18
Earthfile
18
Earthfile
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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<'_> {
|
||||
|
@ -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()],
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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(),
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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");
|
||||
|
@ -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";
|
||||
|
@ -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)))),
|
||||
);
|
||||
|
||||
|
@ -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))
|
||||
});
|
||||
|
@ -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!
|
||||
|
@ -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();
|
||||
|
@ -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 {
|
||||
|
@ -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());
|
||||
|
||||
|
@ -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"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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]
|
||||
|
@ -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(' ');
|
||||
|
@ -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);
|
||||
|
@ -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(),
|
||||
|
@ -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>,
|
||||
|
@ -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) => {
|
||||
|
@ -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!(
|
||||
|
@ -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),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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"
|
||||
|
@ -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 => {
|
||||
|
@ -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));
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
@ -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
@ -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)
|
||||
|
@ -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)?;
|
||||
|
||||
|
@ -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)),
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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]
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
|
@ -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?
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
"#
|
||||
|
@ -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]]
|
||||
);
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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]
|
||||
);
|
||||
}
|
||||
|
@ -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)]
|
||||
|
@ -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: _,
|
||||
|
@ -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 {
|
||||
|
@ -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>(
|
||||
|
@ -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
4
examples/.gitignore
vendored
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user