mirror of
https://github.com/roc-lang/roc.git
synced 2024-11-09 22:54:44 +03:00
Merge branch 'trunk' of ssh://github.com/rtfeldman/roc into repl_history
This commit is contained in:
commit
ea846b5842
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@ -40,6 +40,12 @@ jobs:
|
||||
path: ~/.cargo/git
|
||||
key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}
|
||||
|
||||
- name: Cache compiled valgrind
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/valgrind-3.6.1/
|
||||
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
|
||||
|
||||
- uses: actions-rs/cargo@v1
|
||||
name: cargo fmt --check
|
||||
with:
|
||||
|
@ -16,6 +16,8 @@ To run the test suite (via `cargo test`), you additionally need to install:
|
||||
* [`valgrind`](https://www.valgrind.org/) (needs special treatment to [install on macOS](https://stackoverflow.com/a/61359781)
|
||||
Alternatively, you can use `cargo test --no-fail-fast` or `cargo test -p specific_tests` to skip over the valgrind failures & tests.
|
||||
|
||||
For debugging LLVM IR, we use [DebugIR](https://github.com/vaivaswatha/debugir). This dependency is only required to build with the `--debug` flag, and for normal developtment you should be fine without it.
|
||||
|
||||
### libunwind & libc++-dev
|
||||
|
||||
MacOS systems should already have `libunwind`, but other systems will need to install it (On Ubuntu, this can be donw with `sudo apt-get install libunwind-dev`).
|
||||
|
80
Cargo.lock
generated
80
Cargo.lock
generated
@ -151,7 +151,7 @@ dependencies = [
|
||||
"cfg-if 0.1.10",
|
||||
"libc",
|
||||
"miniz_oxide",
|
||||
"object",
|
||||
"object 0.20.0",
|
||||
"rustc-demangle",
|
||||
]
|
||||
|
||||
@ -510,6 +510,15 @@ dependencies = [
|
||||
"objc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "criterion"
|
||||
version = "0.3.3"
|
||||
@ -789,6 +798,18 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7411863d55df97a419aa64cb4d2f167103ea9d767e2c54a1868b7ac3f6b47129"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"crc32fast",
|
||||
"libc",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
@ -1759,6 +1780,18 @@ version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5"
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d3b63360ec3cb337817c2dbd47ab4a0f170d285d8e5a2064600f3def1402397"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"flate2",
|
||||
"indexmap",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.4.1"
|
||||
@ -2616,6 +2649,45 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roc_gen_dev"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"im",
|
||||
"im-rc",
|
||||
"indoc",
|
||||
"inlinable_string",
|
||||
"itertools",
|
||||
"libc",
|
||||
"libloading",
|
||||
"maplit",
|
||||
"object 0.22.0",
|
||||
"pretty_assertions",
|
||||
"quickcheck",
|
||||
"quickcheck_macros",
|
||||
"roc_build",
|
||||
"roc_builtins",
|
||||
"roc_can",
|
||||
"roc_collections",
|
||||
"roc_constrain",
|
||||
"roc_load",
|
||||
"roc_module",
|
||||
"roc_mono",
|
||||
"roc_parse",
|
||||
"roc_problem",
|
||||
"roc_region",
|
||||
"roc_reporting",
|
||||
"roc_solve",
|
||||
"roc_std",
|
||||
"roc_types",
|
||||
"roc_unify",
|
||||
"roc_uniq",
|
||||
"target-lexicon",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roc_load"
|
||||
version = "0.1.0"
|
||||
@ -3587,6 +3659,12 @@ version = "0.2.68"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d649a3145108d7d3fbcde896a468d1bd636791823c9921135218ad89be08307"
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.57.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32fddd575d477c6e9702484139cf9f23dcd554b06d185ed0f56c857dd3a47aa6"
|
||||
|
||||
[[package]]
|
||||
name = "wayland-client"
|
||||
version = "0.23.6"
|
||||
|
@ -18,6 +18,7 @@ members = [
|
||||
"compiler/mono",
|
||||
"compiler/load",
|
||||
"compiler/gen",
|
||||
"compiler/gen_dev",
|
||||
"compiler/build",
|
||||
"compiler/arena_pool",
|
||||
"vendor/ena",
|
||||
|
@ -59,7 +59,21 @@ esac
|
||||
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add -
|
||||
add-apt-repository "${REPO_NAME}"
|
||||
apt-get update
|
||||
apt-get install -y clang-$LLVM_VERSION lldb-$LLVM_VERSION lld-$LLVM_VERSION clangd-$LLVM_VERSION libc++abi-dev libunwind-dev valgrind
|
||||
apt-get install -y clang-$LLVM_VERSION lldb-$LLVM_VERSION lld-$LLVM_VERSION clangd-$LLVM_VERSION libc++abi-dev libunwind-dev libc6-dbg
|
||||
|
||||
wget https://sourceware.org/pub/valgrind/valgrind-3.16.1.tar.bz2
|
||||
tar -xf valgrind-3.16.1.tar.bz2
|
||||
mv valgrind-3.16.1 ~
|
||||
pushd ~/valgrind-3.16.1
|
||||
apt-get install -y autotools-dev automake
|
||||
./autogen.sh
|
||||
./configure
|
||||
make -j`nproc`
|
||||
sudo make install
|
||||
popd
|
||||
|
||||
# Report current valgrind version, to confirm it installed properly
|
||||
valgrind --version
|
||||
|
||||
# install zig - can't use apt-get since we require at least a specific commit later then the most recent tag (0.6.0)
|
||||
wget -c https://ziglang.org/builds/zig-linux-x86_64-0.6.0+0088efc4b.tar.xz --no-check-certificate
|
||||
|
@ -24,6 +24,7 @@ pub fn build_file(
|
||||
src_dir: PathBuf,
|
||||
roc_file_path: PathBuf,
|
||||
opt_level: OptLevel,
|
||||
emit_debug_info: bool,
|
||||
link_type: LinkType,
|
||||
) -> Result<PathBuf, LoadingProblem> {
|
||||
let compilation_start = SystemTime::now();
|
||||
@ -85,6 +86,9 @@ pub fn build_file(
|
||||
buf
|
||||
);
|
||||
|
||||
let cwd = app_o_file.parent().unwrap();
|
||||
let binary_path = cwd.join(&*loaded.output_path); // TODO should join ".exe" on Windows
|
||||
|
||||
program::gen_from_mono_module(
|
||||
&arena,
|
||||
loaded,
|
||||
@ -92,6 +96,7 @@ pub fn build_file(
|
||||
Triple::host(),
|
||||
&app_o_file,
|
||||
opt_level,
|
||||
emit_debug_info,
|
||||
);
|
||||
|
||||
println!("\nSuccess! 🎉\n\n\t➡ {}\n", app_o_file.display());
|
||||
@ -106,11 +111,8 @@ pub fn build_file(
|
||||
size,
|
||||
);
|
||||
|
||||
let cwd = app_o_file.parent().unwrap();
|
||||
|
||||
// Step 2: link the precompiled host and compiled app
|
||||
let host_input_path = cwd.join("platform").join("host.o");
|
||||
let binary_path = cwd.join("app"); // TODO should be app.exe on Windows
|
||||
|
||||
// TODO we should no longer need to do this once we have platforms on
|
||||
// a package repository, as we can then get precompiled hosts from there.
|
||||
|
@ -14,6 +14,7 @@ use target_lexicon::Triple;
|
||||
pub mod build;
|
||||
pub mod repl;
|
||||
|
||||
pub static FLAG_DEBUG: &str = "debug";
|
||||
pub static FLAG_OPTIMIZE: &str = "optimize";
|
||||
pub static FLAG_ROC_FILE: &str = "ROC_FILE";
|
||||
pub static DIRECTORY_OR_FILES: &str = "DIRECTORY_OR_FILES";
|
||||
@ -34,6 +35,12 @@ pub fn build_app<'a>() -> App<'a> {
|
||||
.help("Optimize the compiled program to run faster. (Optimization takes time to complete.)")
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(FLAG_DEBUG)
|
||||
.long(FLAG_DEBUG)
|
||||
.help("Store LLVM debug information in the generated program")
|
||||
.required(false),
|
||||
)
|
||||
)
|
||||
.subcommand(App::new("run")
|
||||
.about("Build and run a program")
|
||||
@ -48,6 +55,12 @@ pub fn build_app<'a>() -> App<'a> {
|
||||
.help("Optimize the compiled program to run faster. (Optimization takes time to complete.)")
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(FLAG_DEBUG)
|
||||
.long(FLAG_DEBUG)
|
||||
.help("Store LLVM debug information in the generated program")
|
||||
.required(false),
|
||||
)
|
||||
)
|
||||
.subcommand(App::new("repl")
|
||||
.about("Launch the interactive Read Eval Print Loop (REPL)")
|
||||
@ -70,6 +83,8 @@ pub fn build(target: &Triple, matches: &ArgMatches, run_after_build: bool) -> io
|
||||
} else {
|
||||
OptLevel::Normal
|
||||
};
|
||||
let emit_debug_info = matches.is_present(FLAG_DEBUG);
|
||||
|
||||
let path = Path::new(filename).canonicalize().unwrap();
|
||||
let src_dir = path.parent().unwrap().canonicalize().unwrap();
|
||||
|
||||
@ -92,8 +107,15 @@ pub fn build(target: &Triple, matches: &ArgMatches, run_after_build: bool) -> io
|
||||
}
|
||||
});
|
||||
|
||||
let binary_path = build::build_file(target, src_dir, path, opt_level, LinkType::Executable)
|
||||
.expect("TODO gracefully handle build_file failing");
|
||||
let binary_path = build::build_file(
|
||||
target,
|
||||
src_dir,
|
||||
path,
|
||||
opt_level,
|
||||
emit_debug_info,
|
||||
LinkType::Executable,
|
||||
)
|
||||
.expect("TODO gracefully handle build_file failing");
|
||||
|
||||
if run_after_build {
|
||||
// Run the compiled app
|
||||
|
@ -109,7 +109,7 @@ pub fn gen_and_eval(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<R
|
||||
Ok(ReplOutput::Problems(lines))
|
||||
} else {
|
||||
let context = Context::create();
|
||||
let module = arena.alloc(roc_gen::llvm::build::module_from_builtins(&context, "app"));
|
||||
let module = arena.alloc(roc_gen::llvm::build::module_from_builtins(&context, ""));
|
||||
let builder = context.create_builder();
|
||||
|
||||
// mark our zig-defined builtins as internal
|
||||
@ -274,7 +274,8 @@ pub fn gen_and_eval(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<R
|
||||
}
|
||||
|
||||
fn promote_expr_to_module(src: &str) -> String {
|
||||
let mut buffer = String::from("app Repl provides [ replOutput ] imports []\n\nreplOutput =\n");
|
||||
let mut buffer =
|
||||
String::from("app \"app\" provides [ replOutput ] to \"./platform\"\n\nreplOutput =\n");
|
||||
|
||||
for line in src.lines() {
|
||||
// indent the body!
|
||||
|
@ -17,7 +17,13 @@ mod cli_run {
|
||||
use serial_test::serial;
|
||||
use std::path::Path;
|
||||
|
||||
fn check_output(file: &Path, flags: &[&str], expected_ending: &str, use_valgrind: bool) {
|
||||
fn check_output(
|
||||
file: &Path,
|
||||
executable_filename: &str,
|
||||
flags: &[&str],
|
||||
expected_ending: &str,
|
||||
use_valgrind: bool,
|
||||
) {
|
||||
let compile_out = run_roc(&[&["build", file.to_str().unwrap()], flags].concat());
|
||||
if !compile_out.stderr.is_empty() {
|
||||
panic!(compile_out.stderr);
|
||||
@ -26,14 +32,31 @@ mod cli_run {
|
||||
|
||||
let out = if use_valgrind {
|
||||
let (valgrind_out, raw_xml) =
|
||||
run_with_valgrind(&[file.with_file_name("app").to_str().unwrap()]);
|
||||
let memory_errors = extract_valgrind_errors(&raw_xml);
|
||||
if !memory_errors.is_empty() {
|
||||
panic!("{:?}", memory_errors);
|
||||
run_with_valgrind(&[file.with_file_name(executable_filename).to_str().unwrap()]);
|
||||
|
||||
if valgrind_out.status.success() {
|
||||
let memory_errors = extract_valgrind_errors(&raw_xml).unwrap_or_else(|err| {
|
||||
panic!("failed to parse the `valgrind` xml output. Error was:\n\n{:?}\n\nvalgrind xml was: \"{}\"\n\nvalgrind stdout was: \"{}\"\n\nvalgrind stderr was: \"{}\"", err, raw_xml, valgrind_out.stdout, valgrind_out.stderr);
|
||||
});
|
||||
|
||||
if !memory_errors.is_empty() {
|
||||
panic!("{:?}", memory_errors);
|
||||
}
|
||||
} else {
|
||||
let exit_code = match valgrind_out.status.code() {
|
||||
Some(code) => format!("exit code {}", code),
|
||||
None => "no exit code".to_string(),
|
||||
};
|
||||
|
||||
panic!("`valgrind` exited with {}. valgrind stdout was: \"{}\"\n\nvalgrind stderr was: \"{}\"", exit_code, valgrind_out.stdout, valgrind_out.stderr);
|
||||
}
|
||||
|
||||
valgrind_out
|
||||
} else {
|
||||
run_cmd(file.with_file_name("app").to_str().unwrap(), &[])
|
||||
run_cmd(
|
||||
file.with_file_name(executable_filename).to_str().unwrap(),
|
||||
&[],
|
||||
)
|
||||
};
|
||||
if !&out.stdout.ends_with(expected_ending) {
|
||||
panic!(
|
||||
@ -49,6 +72,7 @@ mod cli_run {
|
||||
fn run_hello_world() {
|
||||
check_output(
|
||||
&example_file("hello-world", "Hello.roc"),
|
||||
"hello-world",
|
||||
&[],
|
||||
"Hello, World!!!!!!!!!!!!!\n",
|
||||
true,
|
||||
@ -60,6 +84,7 @@ mod cli_run {
|
||||
fn run_hello_world_optimized() {
|
||||
check_output(
|
||||
&example_file("hello-world", "Hello.roc"),
|
||||
"hello-world",
|
||||
&[],
|
||||
"Hello, World!!!!!!!!!!!!!\n",
|
||||
true,
|
||||
@ -71,6 +96,7 @@ mod cli_run {
|
||||
fn run_quicksort_not_optimized() {
|
||||
check_output(
|
||||
&example_file("quicksort", "Quicksort.roc"),
|
||||
"quicksort",
|
||||
&[],
|
||||
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
|
||||
false,
|
||||
@ -82,6 +108,7 @@ mod cli_run {
|
||||
fn run_quicksort_optimized() {
|
||||
check_output(
|
||||
&example_file("quicksort", "Quicksort.roc"),
|
||||
"quicksort",
|
||||
&["--optimize"],
|
||||
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
|
||||
false,
|
||||
@ -95,6 +122,7 @@ mod cli_run {
|
||||
fn run_quicksort_valgrind() {
|
||||
check_output(
|
||||
&example_file("quicksort", "Quicksort.roc"),
|
||||
"quicksort",
|
||||
&[],
|
||||
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
|
||||
true,
|
||||
@ -108,6 +136,7 @@ mod cli_run {
|
||||
fn run_quicksort_optimized_valgrind() {
|
||||
check_output(
|
||||
&example_file("quicksort", "Quicksort.roc"),
|
||||
"quicksort",
|
||||
&["--optimize"],
|
||||
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
|
||||
true,
|
||||
@ -119,6 +148,7 @@ mod cli_run {
|
||||
fn run_multi_module() {
|
||||
check_output(
|
||||
&example_file("multi-module", "Quicksort.roc"),
|
||||
"quicksort",
|
||||
&[],
|
||||
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
|
||||
false,
|
||||
@ -130,6 +160,7 @@ mod cli_run {
|
||||
fn run_multi_module_optimized() {
|
||||
check_output(
|
||||
&example_file("multi-module", "Quicksort.roc"),
|
||||
"quicksort",
|
||||
&["--optimize"],
|
||||
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
|
||||
false,
|
||||
@ -143,6 +174,7 @@ mod cli_run {
|
||||
fn run_multi_module_valgrind() {
|
||||
check_output(
|
||||
&example_file("multi-module", "Quicksort.roc"),
|
||||
"quicksort",
|
||||
&[],
|
||||
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
|
||||
true,
|
||||
@ -156,6 +188,7 @@ mod cli_run {
|
||||
fn run_multi_module_optimized_valgrind() {
|
||||
check_output(
|
||||
&example_file("multi-module", "Quicksort.roc"),
|
||||
"quicksort",
|
||||
&["--optimize"],
|
||||
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
|
||||
true,
|
||||
@ -178,6 +211,7 @@ mod cli_run {
|
||||
fn run_multi_dep_str_unoptimized() {
|
||||
check_output(
|
||||
&fixture_file("multi-dep-str", "Main.roc"),
|
||||
"multi-dep-str",
|
||||
&[],
|
||||
"I am Dep2.str2\n",
|
||||
true,
|
||||
@ -189,6 +223,7 @@ mod cli_run {
|
||||
fn run_multi_dep_str_optimized() {
|
||||
check_output(
|
||||
&fixture_file("multi-dep-str", "Main.roc"),
|
||||
"multi-dep-str",
|
||||
&["--optimize"],
|
||||
"I am Dep2.str2\n",
|
||||
true,
|
||||
@ -200,6 +235,7 @@ mod cli_run {
|
||||
fn run_multi_dep_thunk_unoptimized() {
|
||||
check_output(
|
||||
&fixture_file("multi-dep-thunk", "Main.roc"),
|
||||
"multi-dep-thunk",
|
||||
&[],
|
||||
"I am Dep2.value2\n",
|
||||
true,
|
||||
@ -211,6 +247,7 @@ mod cli_run {
|
||||
fn run_multi_dep_thunk_optimized() {
|
||||
check_output(
|
||||
&fixture_file("multi-dep-thunk", "Main.roc"),
|
||||
"multi-dep-thunk",
|
||||
&["--optimize"],
|
||||
"I am Dep2.value2\n",
|
||||
true,
|
||||
|
5
cli/tests/fixtures/.gitignore
vendored
5
cli/tests/fixtures/.gitignore
vendored
@ -1,4 +1,3 @@
|
||||
app
|
||||
host.o
|
||||
c_host.o
|
||||
app.dSYM
|
||||
*.o
|
||||
*.dSYM
|
||||
|
1
cli/tests/fixtures/multi-dep-str/.gitignore
vendored
Normal file
1
cli/tests/fixtures/multi-dep-str/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
multi-dep-str
|
2
cli/tests/fixtures/multi-dep-str/Main.roc
vendored
2
cli/tests/fixtures/multi-dep-str/Main.roc
vendored
@ -1,4 +1,4 @@
|
||||
app Main provides [ main ] imports [ Dep1 ]
|
||||
app "multi-dep-str" imports [ Dep1 ] provides [ main ] to "./platform"
|
||||
|
||||
main : Str
|
||||
main = Dep1.str1
|
||||
|
@ -1,5 +1,7 @@
|
||||
platform roc/quicksort
|
||||
provides []
|
||||
requires {}
|
||||
platform examples/multi-module
|
||||
requires { main : Str }
|
||||
exposes []
|
||||
packages {}
|
||||
imports []
|
||||
provides [ main ]
|
||||
effects Effect {}
|
||||
|
@ -3,7 +3,7 @@ use roc_std::RocStr;
|
||||
use std::str;
|
||||
|
||||
extern "C" {
|
||||
#[link_name = "Main_main_1_exposed"]
|
||||
#[link_name = "roc__main_1_exposed"]
|
||||
fn say_hello(output: &mut RocCallResult<RocStr>) -> ();
|
||||
}
|
||||
|
||||
|
1
cli/tests/fixtures/multi-dep-thunk/.gitignore
vendored
Normal file
1
cli/tests/fixtures/multi-dep-thunk/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
multi-dep-thunk
|
2
cli/tests/fixtures/multi-dep-thunk/Main.roc
vendored
2
cli/tests/fixtures/multi-dep-thunk/Main.roc
vendored
@ -1,4 +1,4 @@
|
||||
app Main provides [ main ] imports [ Dep1 ]
|
||||
app "multi-dep-thunk" imports [ Dep1 ] provides [ main ] to "./platform"
|
||||
|
||||
main : Str
|
||||
main = Dep1.value1 {}
|
||||
|
@ -1,5 +1,7 @@
|
||||
platform roc/quicksort
|
||||
provides []
|
||||
requires {}
|
||||
platform examples/multi-dep-thunk
|
||||
requires { main : Str }
|
||||
exposes []
|
||||
packages {}
|
||||
imports []
|
||||
provides [ main ]
|
||||
effects Effect {}
|
||||
|
@ -3,7 +3,7 @@ use roc_std::RocStr;
|
||||
use std::str;
|
||||
|
||||
extern "C" {
|
||||
#[link_name = "Main_main_1_exposed"]
|
||||
#[link_name = "roc__main_1_exposed"]
|
||||
fn say_hello(output: &mut RocCallResult<RocStr>) -> ();
|
||||
}
|
||||
|
||||
|
@ -161,18 +161,18 @@ pub struct ValgrindErrorXWhat {
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn extract_valgrind_errors(xml: &str) -> Vec<ValgrindError> {
|
||||
let parsed_xml: ValgrindOutput =
|
||||
from_str(xml).unwrap_or_else(|err|
|
||||
panic!("failed to parse the `valgrind` xml output. Error was:\n\n{:?}\n\nRaw valgrind output was:\n\n{}", err, xml));
|
||||
parsed_xml
|
||||
pub fn extract_valgrind_errors(xml: &str) -> Result<Vec<ValgrindError>, serde_xml_rs::Error> {
|
||||
let parsed_xml: ValgrindOutput = from_str(xml)?;
|
||||
let answer = parsed_xml
|
||||
.fields
|
||||
.iter()
|
||||
.filter_map(|field| match field {
|
||||
ValgrindField::Error(err) => Some(err.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
.collect();
|
||||
|
||||
Ok(answer)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
@ -64,12 +64,12 @@ mod repl_eval {
|
||||
|
||||
#[test]
|
||||
fn literal_0point0() {
|
||||
expect_success("0.0", "0 : Float");
|
||||
expect_success("0.0", "0 : F64");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_4point2() {
|
||||
expect_success("4.2", "4.2 : Float");
|
||||
expect_success("4.2", "4.2 : F64");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -84,7 +84,7 @@ mod repl_eval {
|
||||
|
||||
#[test]
|
||||
fn float_addition() {
|
||||
expect_success("1.1 + 2", "3.1 : Float");
|
||||
expect_success("1.1 + 2", "3.1 : F64");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -148,7 +148,7 @@ mod repl_eval {
|
||||
#[test]
|
||||
fn single_element_tag_union() {
|
||||
expect_success("True 1", "True 1 : [ True (Num *) ]*");
|
||||
expect_success("Foo 1 3.14", "Foo 1 3.14 : [ Foo (Num *) Float ]*");
|
||||
expect_success("Foo 1 3.14", "Foo 1 3.14 : [ Foo (Num *) F64 ]*");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -157,7 +157,7 @@ mod repl_eval {
|
||||
|
||||
expect_success(
|
||||
"if 1 == 1 then True 3 else False 3.14",
|
||||
"True 3 : [ False Float, True (Num *) ]*",
|
||||
"True 3 : [ False F64, True (Num *) ]*",
|
||||
)
|
||||
}
|
||||
|
||||
@ -206,7 +206,7 @@ mod repl_eval {
|
||||
|
||||
#[test]
|
||||
fn literal_float_list() {
|
||||
expect_success("[ 1.1, 2.2, 3.3 ]", "[ 1.1, 2.2, 3.3 ] : List Float");
|
||||
expect_success("[ 1.1, 2.2, 3.3 ]", "[ 1.1, 2.2, 3.3 ] : List F64");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -242,7 +242,7 @@ mod repl_eval {
|
||||
fn nested_float_list() {
|
||||
expect_success(
|
||||
r#"[ [ [ 4, 3, 2 ], [ 1, 0.0 ] ], [ [] ], [] ]"#,
|
||||
r#"[ [ [ 4, 3, 2 ], [ 1, 0 ] ], [ [] ], [] ] : List (List (List Float))"#,
|
||||
r#"[ [ [ 4, 3, 2 ], [ 1, 0 ] ], [ [] ], [] ] : List (List (List F64))"#,
|
||||
);
|
||||
}
|
||||
|
||||
@ -250,7 +250,7 @@ mod repl_eval {
|
||||
fn list_concat() {
|
||||
expect_success(
|
||||
"List.concat [ 1.1, 2.2 ] [ 3.3, 4.4, 5.5 ]",
|
||||
"[ 1.1, 2.2, 3.3, 4.4, 5.5 ] : List Float",
|
||||
"[ 1.1, 2.2, 3.3, 4.4, 5.5 ] : List F64",
|
||||
);
|
||||
}
|
||||
|
||||
@ -265,7 +265,7 @@ mod repl_eval {
|
||||
fn list_sum() {
|
||||
expect_success("List.sum []", "0 : Num *");
|
||||
expect_success("List.sum [ 1, 2, 3 ]", "6 : Num *");
|
||||
expect_success("List.sum [ 1.1, 2.2, 3.3 ]", "6.6 : Float");
|
||||
expect_success("List.sum [ 1.1, 2.2, 3.3 ]", "6.6 : F64");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -284,7 +284,7 @@ mod repl_eval {
|
||||
fn basic_1_field_f64_record() {
|
||||
// Even though this gets unwrapped at runtime, the repl should still
|
||||
// report it as a record
|
||||
expect_success("{ foo: 4.2 }", "{ foo: 4.2 } : { foo : Float }");
|
||||
expect_success("{ foo: 4.2 }", "{ foo: 4.2 } : { foo : F64 }");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -303,7 +303,7 @@ mod repl_eval {
|
||||
// report it as a record
|
||||
expect_success(
|
||||
"{ foo: { bar: { baz: 4.2 } } }",
|
||||
"{ foo: { bar: { baz: 4.2 } } } : { foo : { bar : { baz : Float } } }",
|
||||
"{ foo: { bar: { baz: 4.2 } } } : { foo : { bar : { baz : F64 } } }",
|
||||
);
|
||||
}
|
||||
|
||||
@ -319,7 +319,7 @@ mod repl_eval {
|
||||
fn basic_2_field_f64_record() {
|
||||
expect_success(
|
||||
"{ foo: 4.1, bar: 2.3 }",
|
||||
"{ bar: 2.3, foo: 4.1 } : { bar : Float, foo : Float }",
|
||||
"{ bar: 2.3, foo: 4.1 } : { bar : F64, foo : F64 }",
|
||||
);
|
||||
}
|
||||
|
||||
@ -327,7 +327,7 @@ mod repl_eval {
|
||||
fn basic_2_field_mixed_record() {
|
||||
expect_success(
|
||||
"{ foo: 4.1, bar: 2 }",
|
||||
"{ bar: 2, foo: 4.1 } : { bar : Num *, foo : Float }",
|
||||
"{ bar: 2, foo: 4.1 } : { bar : Num *, foo : F64 }",
|
||||
);
|
||||
}
|
||||
|
||||
@ -335,7 +335,7 @@ mod repl_eval {
|
||||
fn basic_3_field_record() {
|
||||
expect_success(
|
||||
"{ foo: 4.1, bar: 2, baz: 0x5 }",
|
||||
"{ bar: 2, baz: 5, foo: 4.1 } : { bar : Num *, baz : Int, foo : Float }",
|
||||
"{ bar: 2, baz: 5, foo: 4.1 } : { bar : Num *, baz : Int, foo : F64 }",
|
||||
);
|
||||
}
|
||||
|
||||
@ -350,7 +350,7 @@ mod repl_eval {
|
||||
fn list_of_2_field_records() {
|
||||
expect_success(
|
||||
"[ { foo: 4.1, bar: 2 } ]",
|
||||
"[ { bar: 2, foo: 4.1 } ] : List { bar : Num *, foo : Float }",
|
||||
"[ { bar: 2, foo: 4.1 } ] : List { bar : Num *, foo : F64 }",
|
||||
);
|
||||
}
|
||||
|
||||
@ -382,7 +382,7 @@ mod repl_eval {
|
||||
fn list_of_3_field_records() {
|
||||
expect_success(
|
||||
"[ { foo: 4.1, bar: 2, baz: 0x3 } ]",
|
||||
"[ { bar: 2, baz: 3, foo: 4.1 } ] : List { bar : Num *, baz : Int, foo : Float }",
|
||||
"[ { bar: 2, baz: 3, foo: 4.1 } ] : List { bar : Num *, baz : Int, foo : F64 }",
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,8 @@ use inkwell::module::Module;
|
||||
use inkwell::targets::{CodeModel, FileType, RelocMode};
|
||||
use libloading::{Error, Library};
|
||||
use roc_gen::llvm::build::OptLevel;
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Child, Command, Output};
|
||||
@ -25,7 +27,6 @@ pub fn link(
|
||||
) -> io::Result<(Child, PathBuf)> {
|
||||
match target {
|
||||
Triple {
|
||||
architecture: Architecture::X86_64,
|
||||
operating_system: OperatingSystem::Linux,
|
||||
..
|
||||
} => link_linux(target, output_path, input_paths, link_type),
|
||||
@ -46,9 +47,11 @@ pub fn rebuild_host(host_input_path: &Path) {
|
||||
let cargo_host_src = host_input_path.with_file_name("Cargo.toml");
|
||||
let host_dest = host_input_path.with_file_name("host.o");
|
||||
|
||||
let env_path = env::var("PATH").unwrap_or_else(|_| "".to_string());
|
||||
// Compile host.c
|
||||
let output = Command::new("clang")
|
||||
.env_clear()
|
||||
.env("PATH", &env_path)
|
||||
.args(&[
|
||||
"-c",
|
||||
c_host_src.to_str().unwrap(),
|
||||
@ -75,6 +78,7 @@ pub fn rebuild_host(host_input_path: &Path) {
|
||||
|
||||
let output = Command::new("ld")
|
||||
.env_clear()
|
||||
.env("PATH", &env_path)
|
||||
.args(&[
|
||||
"-r",
|
||||
"-L",
|
||||
@ -103,6 +107,7 @@ pub fn rebuild_host(host_input_path: &Path) {
|
||||
|
||||
let output = Command::new("ld")
|
||||
.env_clear()
|
||||
.env("PATH", &env_path)
|
||||
.args(&[
|
||||
"-r",
|
||||
c_host_dest.to_str().unwrap(),
|
||||
@ -145,18 +150,31 @@ fn link_linux(
|
||||
input_paths: &[&str],
|
||||
link_type: LinkType,
|
||||
) -> io::Result<(Child, PathBuf)> {
|
||||
let libcrt_path = if Path::new("/usr/lib/x86_64-linux-gnu").exists() {
|
||||
Path::new("/usr/lib/x86_64-linux-gnu")
|
||||
let usr_lib_path = Path::new("/usr/lib").to_path_buf();
|
||||
let usr_lib_gnu_path = usr_lib_path.join(format!("{}-linux-gnu", target.architecture));
|
||||
let lib_gnu_path = Path::new("/lib/").join(format!("{}-linux-gnu", target.architecture));
|
||||
|
||||
let libcrt_path = if usr_lib_gnu_path.exists() {
|
||||
&usr_lib_gnu_path
|
||||
} else {
|
||||
Path::new("/usr/lib")
|
||||
&usr_lib_path
|
||||
};
|
||||
|
||||
let libgcc_path = if Path::new("/lib/x86_64-linux-gnu/libgcc_s.so.1").exists() {
|
||||
Path::new("/lib/x86_64-linux-gnu/libgcc_s.so.1")
|
||||
} else if Path::new("/usr/lib/x86_64-linux-gnu/libgcc_s.so.1").exists() {
|
||||
Path::new("/usr/lib/x86_64-linux-gnu/libgcc_s.so.1")
|
||||
let libgcc_name = "libgcc_s.so.1";
|
||||
let libgcc_path = if lib_gnu_path.join(libgcc_name).exists() {
|
||||
lib_gnu_path.join(libgcc_name)
|
||||
} else if usr_lib_gnu_path.join(libgcc_name).exists() {
|
||||
usr_lib_gnu_path.join(libgcc_name)
|
||||
} else {
|
||||
Path::new("/usr/lib/libgcc_s.so.1")
|
||||
usr_lib_path.join(libgcc_name)
|
||||
};
|
||||
let ld_linux = match target.architecture {
|
||||
Architecture::X86_64 => "/lib64/ld-linux-x86-64.so.2",
|
||||
Architecture::Aarch64(_) => "/lib/ld-linux-aarch64.so.1",
|
||||
_ => panic!(
|
||||
"TODO gracefully handle unsupported linux architecture: {:?}",
|
||||
target.architecture
|
||||
),
|
||||
};
|
||||
|
||||
let mut soname;
|
||||
@ -194,12 +212,20 @@ fn link_linux(
|
||||
}
|
||||
};
|
||||
|
||||
let env_path = env::var("PATH").unwrap_or_else(|_| "".to_string());
|
||||
// NOTE: order of arguments to `ld` matters here!
|
||||
// The `-l` flags should go after the `.o` arguments
|
||||
Ok((
|
||||
Command::new("ld")
|
||||
// Don't allow LD_ env vars to affect this
|
||||
.env_clear()
|
||||
.env("PATH", &env_path)
|
||||
// Keep NIX_ env vars
|
||||
.envs(
|
||||
env::vars()
|
||||
.filter(|&(ref k, _)| k.starts_with("NIX_"))
|
||||
.collect::<HashMap<String, String>>(),
|
||||
)
|
||||
.args(&[
|
||||
"-arch",
|
||||
arch_str(target),
|
||||
@ -207,7 +233,7 @@ fn link_linux(
|
||||
libcrt_path.join("crtn.o").to_str().unwrap(),
|
||||
])
|
||||
.args(&base_args)
|
||||
.args(&["-dynamic-linker", "/lib64/ld-linux-x86-64.so.2"])
|
||||
.args(&["-dynamic-linker", ld_linux])
|
||||
.args(input_paths)
|
||||
.args(&[
|
||||
// Libraries - see https://github.com/rtfeldman/roc/pull/554#discussion_r496365925
|
||||
@ -220,6 +246,7 @@ fn link_linux(
|
||||
"-lutil",
|
||||
"-lc_nonshared",
|
||||
"-lc++",
|
||||
"-lc++abi",
|
||||
"-lunwind",
|
||||
libgcc_path.to_str().unwrap(),
|
||||
// Output
|
||||
|
@ -20,6 +20,7 @@ pub fn gen_from_mono_module(
|
||||
target: Triple,
|
||||
app_o_file: &Path,
|
||||
opt_level: OptLevel,
|
||||
emit_debug_info: bool,
|
||||
) {
|
||||
use roc_reporting::report::{
|
||||
can_problem, mono_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE,
|
||||
@ -159,15 +160,78 @@ pub fn gen_from_mono_module(
|
||||
// Uncomment this to see the module's optimized LLVM instruction output:
|
||||
// env.module.print_to_stderr();
|
||||
|
||||
// Emit the .o file
|
||||
// annotate the LLVM IR output with debug info
|
||||
// so errors are reported with the line number of the LLVM source
|
||||
if emit_debug_info {
|
||||
module.strip_debug_info();
|
||||
|
||||
let reloc = RelocMode::Default;
|
||||
let model = CodeModel::Default;
|
||||
let target_machine = target::target_machine(&target, opt_level.into(), reloc, model).unwrap();
|
||||
let mut app_ll_file = std::path::PathBuf::from(app_o_file);
|
||||
app_ll_file.set_extension("ll");
|
||||
|
||||
target_machine
|
||||
.write_to_file(&env.module, FileType::Object, &app_o_file)
|
||||
.expect("Writing .o file failed");
|
||||
let mut app_ll_dbg_file = std::path::PathBuf::from(app_o_file);
|
||||
app_ll_dbg_file.set_extension("dbg.ll");
|
||||
|
||||
let mut app_bc_file = std::path::PathBuf::from(app_o_file);
|
||||
app_bc_file.set_extension("bc");
|
||||
|
||||
use std::process::Command;
|
||||
|
||||
// write the ll code to a file, so we can modify it
|
||||
module.print_to_file(&app_ll_file).unwrap();
|
||||
|
||||
// run the debugir https://github.com/vaivaswatha/debugir tool
|
||||
match Command::new("debugir")
|
||||
.env_clear()
|
||||
.args(&[app_ll_file.to_str().unwrap()])
|
||||
.output()
|
||||
{
|
||||
Ok(_) => {}
|
||||
Err(error) => {
|
||||
use std::io::ErrorKind;
|
||||
match error.kind() {
|
||||
ErrorKind::NotFound => panic!(
|
||||
r"I could not find the `debugir` tool on the PATH, install it from https://github.com/vaivaswatha/debugir"
|
||||
),
|
||||
_ => panic!("{:?}", error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// assemble the .ll into a .bc
|
||||
let _ = Command::new("llvm-as-10")
|
||||
.env_clear()
|
||||
.args(&[
|
||||
app_ll_dbg_file.to_str().unwrap(),
|
||||
"-o",
|
||||
app_bc_file.to_str().unwrap(),
|
||||
])
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
// write the .o file. Note that this builds the .o for the local machine,
|
||||
// and ignores the `target_machine` entirely.
|
||||
let _ = Command::new("llc-10")
|
||||
.env_clear()
|
||||
.args(&[
|
||||
"-filetype=obj",
|
||||
app_bc_file.to_str().unwrap(),
|
||||
"-o",
|
||||
app_o_file.to_str().unwrap(),
|
||||
])
|
||||
.output()
|
||||
.unwrap();
|
||||
} else {
|
||||
// Emit the .o file
|
||||
|
||||
let reloc = RelocMode::Default;
|
||||
let model = CodeModel::Default;
|
||||
let target_machine =
|
||||
target::target_machine(&target, opt_level.into(), reloc, model).unwrap();
|
||||
|
||||
target_machine
|
||||
.write_to_file(&env.module, FileType::Object, &app_o_file)
|
||||
.expect("Writing .o file failed");
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FunctionIterator<'ctx> {
|
||||
|
@ -14,6 +14,11 @@ pub fn target_triple_str(target: &Triple) -> &'static str {
|
||||
operating_system: OperatingSystem::Linux,
|
||||
..
|
||||
} => "x86_64-unknown-linux-gnu",
|
||||
Triple {
|
||||
architecture: Architecture::Aarch64(_),
|
||||
operating_system: OperatingSystem::Linux,
|
||||
..
|
||||
} => "aarch64-unknown-linux-gnu",
|
||||
Triple {
|
||||
architecture: Architecture::X86_64,
|
||||
operating_system: OperatingSystem::Darwin,
|
||||
@ -36,6 +41,10 @@ pub fn arch_str(target: &Triple) -> &'static str {
|
||||
|
||||
"x86-64"
|
||||
}
|
||||
Architecture::Aarch64(_) => {
|
||||
Target::initialize_aarch64(&InitializationConfig::default());
|
||||
"aarch64"
|
||||
}
|
||||
Architecture::Arm(_) if cfg!(feature = "target-arm") => {
|
||||
// NOTE: why not enable arm and wasm by default?
|
||||
//
|
||||
@ -67,7 +76,7 @@ pub fn target_machine(
|
||||
|
||||
Target::from_name(arch).unwrap().create_target_machine(
|
||||
&TargetTriple::create(target_triple_str(target)),
|
||||
arch,
|
||||
"generic",
|
||||
"", // TODO: this probably should be TargetMachine::get_host_cpu_features() to enable all features.
|
||||
opt,
|
||||
reloc,
|
||||
|
@ -489,9 +489,22 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
||||
),
|
||||
);
|
||||
|
||||
// walkRight : List elem, (elem -> accum -> accum), accum -> accum
|
||||
// walk : List elem, (elem -> accum -> accum), accum -> accum
|
||||
add_type(
|
||||
Symbol::LIST_WALK_RIGHT,
|
||||
Symbol::LIST_WALK,
|
||||
top_level_function(
|
||||
vec![
|
||||
list_type(flex(TVAR1)),
|
||||
closure(vec![flex(TVAR1), flex(TVAR2)], TVAR3, Box::new(flex(TVAR2))),
|
||||
flex(TVAR2),
|
||||
],
|
||||
Box::new(flex(TVAR2)),
|
||||
),
|
||||
);
|
||||
|
||||
// walkBackwards : List elem, (elem -> accum -> accum), accum -> accum
|
||||
add_type(
|
||||
Symbol::LIST_WALK_BACKWARDS,
|
||||
top_level_function(
|
||||
vec![
|
||||
list_type(flex(TVAR1)),
|
||||
|
@ -777,11 +777,38 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
||||
)
|
||||
});
|
||||
|
||||
// walkRight : Attr (* | u) (List (Attr u a))
|
||||
// walk : Attr (* | u) (List (Attr u a))
|
||||
// , Attr Shared (Attr u a -> b -> b)
|
||||
// , b
|
||||
// -> b
|
||||
add_type(Symbol::LIST_WALK_RIGHT, {
|
||||
add_type(Symbol::LIST_WALK, {
|
||||
let_tvars! { u, a, b, star1, closure };
|
||||
|
||||
unique_function(
|
||||
vec![
|
||||
SolvedType::Apply(
|
||||
Symbol::ATTR_ATTR,
|
||||
vec![
|
||||
container(star1, vec![u]),
|
||||
SolvedType::Apply(Symbol::LIST_LIST, vec![attr_type(u, a)]),
|
||||
],
|
||||
),
|
||||
shared(SolvedType::Func(
|
||||
vec![attr_type(u, a), flex(b)],
|
||||
Box::new(flex(closure)),
|
||||
Box::new(flex(b)),
|
||||
)),
|
||||
flex(b),
|
||||
],
|
||||
flex(b),
|
||||
)
|
||||
});
|
||||
|
||||
// walkBackwards : Attr (* | u) (List (Attr u a))
|
||||
// , Attr Shared (Attr u a -> b -> b)
|
||||
// , b
|
||||
// -> b
|
||||
add_type(Symbol::LIST_WALK_BACKWARDS, {
|
||||
let_tvars! { u, a, b, star1, closure };
|
||||
|
||||
unique_function(
|
||||
@ -1150,7 +1177,7 @@ fn float_type(u: VarId) -> SolvedType {
|
||||
vec![
|
||||
flex(u),
|
||||
SolvedType::Alias(
|
||||
Symbol::NUM_FLOAT,
|
||||
Symbol::NUM_F64,
|
||||
Vec::new(),
|
||||
Box::new(builtin_aliases::num_type(SolvedType::Apply(
|
||||
Symbol::ATTR_ATTR,
|
||||
|
@ -71,7 +71,8 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap<Symbol, Def> {
|
||||
Symbol::LIST_JOIN => list_join,
|
||||
Symbol::LIST_MAP => list_map,
|
||||
Symbol::LIST_KEEP_IF => list_keep_if,
|
||||
Symbol::LIST_WALK_RIGHT => list_walk_right,
|
||||
Symbol::LIST_WALK => list_walk,
|
||||
Symbol::LIST_WALK_BACKWARDS => list_walk_backwards,
|
||||
Symbol::NUM_ADD => num_add,
|
||||
Symbol::NUM_ADD_CHECKED => num_add_checked,
|
||||
Symbol::NUM_ADD_WRAP => num_add_wrap,
|
||||
@ -1313,14 +1314,43 @@ fn list_join(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
)
|
||||
}
|
||||
|
||||
/// List.walkRight : List elem, (elem -> accum -> accum), accum -> accum
|
||||
fn list_walk_right(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
/// List.walk : List elem, (elem -> accum -> accum), accum -> accum
|
||||
fn list_walk(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let list_var = var_store.fresh();
|
||||
let func_var = var_store.fresh();
|
||||
let accum_var = var_store.fresh();
|
||||
|
||||
let body = RunLowLevel {
|
||||
op: LowLevel::ListWalkRight,
|
||||
op: LowLevel::ListWalk,
|
||||
args: vec![
|
||||
(list_var, Var(Symbol::ARG_1)),
|
||||
(func_var, Var(Symbol::ARG_2)),
|
||||
(accum_var, Var(Symbol::ARG_3)),
|
||||
],
|
||||
ret_var: accum_var,
|
||||
};
|
||||
|
||||
defn(
|
||||
symbol,
|
||||
vec![
|
||||
(list_var, Symbol::ARG_1),
|
||||
(func_var, Symbol::ARG_2),
|
||||
(accum_var, Symbol::ARG_3),
|
||||
],
|
||||
var_store,
|
||||
body,
|
||||
accum_var,
|
||||
)
|
||||
}
|
||||
|
||||
/// List.walkBackwards : List elem, (elem -> accum -> accum), accum -> accum
|
||||
fn list_walk_backwards(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let list_var = var_store.fresh();
|
||||
let func_var = var_store.fresh();
|
||||
let accum_var = var_store.fresh();
|
||||
|
||||
let body = RunLowLevel {
|
||||
op: LowLevel::ListWalkBackwards,
|
||||
args: vec![
|
||||
(list_var, Var(Symbol::ARG_1)),
|
||||
(func_var, Var(Symbol::ARG_2)),
|
||||
|
@ -74,7 +74,7 @@ pub fn str_type() -> Type {
|
||||
#[inline(always)]
|
||||
pub fn num_float() -> Type {
|
||||
Type::Alias(
|
||||
Symbol::NUM_FLOAT,
|
||||
Symbol::NUM_F64,
|
||||
vec![],
|
||||
Box::new(num_num(num_floatingpoint())),
|
||||
)
|
||||
|
@ -37,10 +37,6 @@ pub enum Newlines {
|
||||
No,
|
||||
}
|
||||
|
||||
pub fn fmt_annotation<'a>(buf: &mut String<'a>, annotation: &'a TypeAnnotation<'a>, indent: u16) {
|
||||
annotation.format(buf, indent);
|
||||
}
|
||||
|
||||
pub trait Formattable<'a> {
|
||||
fn is_multiline(&self) -> bool;
|
||||
|
||||
@ -85,13 +81,16 @@ where
|
||||
}
|
||||
|
||||
macro_rules! format_sequence {
|
||||
($buf: expr, $indent:expr, $start:expr, $end:expr, $items:expr, $t:ident) => {
|
||||
// is it a multiline type annotation?
|
||||
if $items.iter().any(|item| item.value.is_multiline()) {
|
||||
($buf: expr, $indent:expr, $start:expr, $end:expr, $items:expr, $final_comments:expr, $newline:expr, $t:ident) => {
|
||||
let is_multiline =
|
||||
$items.iter().any(|item| item.value.is_multiline()) || !$final_comments.is_empty();
|
||||
|
||||
if is_multiline {
|
||||
let braces_indent = $indent + INDENT;
|
||||
let item_indent = braces_indent + INDENT;
|
||||
|
||||
newline($buf, braces_indent);
|
||||
if ($newline == Newlines::Yes) {
|
||||
newline($buf, braces_indent);
|
||||
}
|
||||
$buf.push($start);
|
||||
|
||||
for item in $items.iter() {
|
||||
@ -139,10 +138,12 @@ macro_rules! format_sequence {
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt_comments_only($buf, $final_comments.iter(), NewlineAt::Top, item_indent);
|
||||
newline($buf, braces_indent);
|
||||
$buf.push($end);
|
||||
} else {
|
||||
// is_multiline == false
|
||||
// there is no comment to add
|
||||
$buf.push($start);
|
||||
let mut iter = $items.iter().peekable();
|
||||
while let Some(item) = iter.next() {
|
||||
@ -285,9 +286,9 @@ impl<'a> Formattable<'a> for TypeAnnotation<'a> {
|
||||
TagUnion {
|
||||
tags,
|
||||
ext,
|
||||
final_comments: _,
|
||||
final_comments,
|
||||
} => {
|
||||
format_sequence!(buf, indent, '[', ']', tags, Tag);
|
||||
format_sequence!(buf, indent, '[', ']', tags, final_comments, newlines, Tag);
|
||||
|
||||
if let Some(loc_ext_ann) = *ext {
|
||||
loc_ext_ann.value.format(buf, indent);
|
||||
@ -297,9 +298,18 @@ impl<'a> Formattable<'a> for TypeAnnotation<'a> {
|
||||
Record {
|
||||
fields,
|
||||
ext,
|
||||
final_comments: _,
|
||||
final_comments,
|
||||
} => {
|
||||
format_sequence!(buf, indent, '{', '}', fields, AssignedField);
|
||||
format_sequence!(
|
||||
buf,
|
||||
indent,
|
||||
'{',
|
||||
'}',
|
||||
fields,
|
||||
final_comments,
|
||||
newlines,
|
||||
AssignedField
|
||||
);
|
||||
|
||||
if let Some(loc_ext_ann) = *ext {
|
||||
loc_ext_ann.value.format(buf, indent);
|
||||
@ -313,8 +323,18 @@ impl<'a> Formattable<'a> for TypeAnnotation<'a> {
|
||||
rhs.value.format(buf, indent);
|
||||
}
|
||||
|
||||
SpaceBefore(ann, _spaces) | SpaceAfter(ann, _spaces) => {
|
||||
ann.format_with_options(buf, parens, newlines, indent)
|
||||
SpaceBefore(ann, spaces) => {
|
||||
newline(buf, indent + INDENT);
|
||||
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent + INDENT);
|
||||
ann.format_with_options(buf, parens, Newlines::No, indent)
|
||||
}
|
||||
SpaceAfter(ann, spaces) => {
|
||||
ann.format_with_options(buf, parens, newlines, indent);
|
||||
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
|
||||
// seems like this SpaceAfter is not constructible
|
||||
// so this branch hasn't be tested. Please add some test if
|
||||
// this branch is actually reached and remove this dbg_assert.
|
||||
debug_assert!(false);
|
||||
}
|
||||
|
||||
Malformed(raw) => buf.push_str(raw),
|
||||
|
@ -36,8 +36,23 @@ impl<'a> Formattable<'a> for Def<'a> {
|
||||
match self {
|
||||
Annotation(loc_pattern, loc_annotation) => {
|
||||
loc_pattern.format(buf, indent);
|
||||
buf.push_str(" : ");
|
||||
loc_annotation.format(buf, indent);
|
||||
if loc_annotation.is_multiline() {
|
||||
buf.push_str(" :");
|
||||
loc_annotation.format_with_options(
|
||||
buf,
|
||||
Parens::NotNeeded,
|
||||
Newlines::Yes,
|
||||
indent,
|
||||
);
|
||||
} else {
|
||||
buf.push_str(" : ");
|
||||
loc_annotation.format_with_options(
|
||||
buf,
|
||||
Parens::NotNeeded,
|
||||
Newlines::No,
|
||||
indent,
|
||||
);
|
||||
}
|
||||
}
|
||||
Alias { name, vars, ann } => {
|
||||
buf.push_str(name.value);
|
||||
|
@ -1,8 +1,7 @@
|
||||
use crate::spaces::{fmt_spaces, INDENT};
|
||||
use bumpalo::collections::{String, Vec};
|
||||
use roc_parse::ast::{
|
||||
AppHeader, ExposesEntry, ImportsEntry, InterfaceHeader, Module, PlatformHeader,
|
||||
};
|
||||
use roc_parse::ast::Module;
|
||||
use roc_parse::header::{AppHeader, ExposesEntry, ImportsEntry, InterfaceHeader, PlatformHeader};
|
||||
use roc_region::all::Located;
|
||||
|
||||
pub fn fmt_module<'a>(buf: &mut String<'a>, module: &'a Module<'a>) {
|
||||
@ -113,7 +112,7 @@ fn fmt_imports<'a>(
|
||||
|
||||
fn fmt_exposes<'a>(
|
||||
buf: &mut String<'a>,
|
||||
loc_entries: &'a Vec<'a, Located<ExposesEntry<'a>>>,
|
||||
loc_entries: &'a Vec<'a, Located<ExposesEntry<'a, &'a str>>>,
|
||||
indent: u16,
|
||||
) {
|
||||
buf.push('[');
|
||||
@ -137,11 +136,11 @@ fn fmt_exposes<'a>(
|
||||
buf.push(']');
|
||||
}
|
||||
|
||||
fn fmt_exposes_entry<'a>(buf: &mut String<'a>, entry: &'a ExposesEntry<'a>, indent: u16) {
|
||||
use roc_parse::ast::ExposesEntry::*;
|
||||
fn fmt_exposes_entry<'a>(buf: &mut String<'a>, entry: &'a ExposesEntry<'a, &'a str>, indent: u16) {
|
||||
use roc_parse::header::ExposesEntry::*;
|
||||
|
||||
match entry {
|
||||
Ident(ident) => buf.push_str(ident),
|
||||
Exposed(ident) => buf.push_str(ident),
|
||||
|
||||
SpaceBefore(sub_entry, spaces) => {
|
||||
fmt_spaces(buf, spaces.iter(), indent);
|
||||
@ -155,7 +154,7 @@ fn fmt_exposes_entry<'a>(buf: &mut String<'a>, entry: &'a ExposesEntry<'a>, inde
|
||||
}
|
||||
|
||||
fn fmt_imports_entry<'a>(buf: &mut String<'a>, entry: &'a ImportsEntry<'a>, indent: u16) {
|
||||
use roc_parse::ast::ImportsEntry::*;
|
||||
use roc_parse::header::ImportsEntry::*;
|
||||
|
||||
match entry {
|
||||
Module(module, loc_exposes_entries) => {
|
||||
@ -176,6 +175,10 @@ fn fmt_imports_entry<'a>(buf: &mut String<'a>, entry: &'a ImportsEntry<'a>, inde
|
||||
}
|
||||
}
|
||||
|
||||
Package(_pkg, _name, _entries) => {
|
||||
todo!("TODO Format imported package");
|
||||
}
|
||||
|
||||
SpaceBefore(sub_entry, spaces) => {
|
||||
fmt_spaces(buf, spaces.iter(), indent);
|
||||
fmt_imports_entry(buf, sub_entry, indent);
|
||||
|
@ -770,7 +770,7 @@ mod test_fmt {
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
f :
|
||||
f :
|
||||
{
|
||||
y : Int,
|
||||
x : Int,
|
||||
@ -781,25 +781,125 @@ mod test_fmt {
|
||||
);
|
||||
}
|
||||
|
||||
// // TODO This raises a parse error:
|
||||
// // NotYetImplemented("TODO the : in this declaration seems outdented")
|
||||
// #[test]
|
||||
// fn comments_in_record_annotation() {
|
||||
// expr_formats_to(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// f :
|
||||
// {}
|
||||
#[test]
|
||||
fn trailing_comma_in_record_annotation_same() {
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
f :
|
||||
{
|
||||
y : Int,
|
||||
x : Int,
|
||||
}
|
||||
|
||||
// f"#
|
||||
// ),
|
||||
// indoc!(
|
||||
// r#"
|
||||
// f : b {}
|
||||
// f"#
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
f"#
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiline_type_definition() {
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
f :
|
||||
Int
|
||||
|
||||
f"#
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiline_empty_record_type_definition() {
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
f :
|
||||
{}
|
||||
|
||||
f"#
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_definition_comment_after_colon() {
|
||||
expr_formats_to(
|
||||
indoc!(
|
||||
r#"
|
||||
f : # comment
|
||||
{}
|
||||
|
||||
f"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
f :
|
||||
# comment
|
||||
{}
|
||||
|
||||
f"#
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn final_comment_in_empty_record_type_definition() {
|
||||
expr_formats_to(
|
||||
indoc!(
|
||||
r#"
|
||||
f :
|
||||
{ # comment
|
||||
}
|
||||
|
||||
f"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
f :
|
||||
{
|
||||
# comment
|
||||
}
|
||||
|
||||
f"#
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiline_inside_empty_record_annotation() {
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
f :
|
||||
{
|
||||
}
|
||||
|
||||
f"#
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn final_comment_record_annotation() {
|
||||
expr_formats_to(
|
||||
indoc!(
|
||||
r#"
|
||||
f :
|
||||
{
|
||||
x: Int # comment 1
|
||||
,
|
||||
# comment 2
|
||||
}
|
||||
|
||||
f"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
f :
|
||||
{
|
||||
x : Int,
|
||||
# comment 1
|
||||
# comment 2
|
||||
}
|
||||
|
||||
f"#
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn def_closure() {
|
||||
|
@ -1,7 +1,7 @@
|
||||
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_if, list_len, list_map, list_prepend, list_repeat,
|
||||
list_reverse, list_set, list_single, list_sum, list_walk_right,
|
||||
list_reverse, list_set, list_single, list_sum, list_walk, list_walk_backwards,
|
||||
};
|
||||
use crate::llvm::build_str::{
|
||||
str_concat, str_count_graphemes, str_len, str_split, str_starts_with, CHAR_LAYOUT,
|
||||
@ -735,7 +735,12 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
|
||||
// Insert field exprs into struct_val
|
||||
for (index, field_val) in field_vals.into_iter().enumerate() {
|
||||
struct_val = builder
|
||||
.build_insert_value(struct_val, field_val, index as u32, "insert_field")
|
||||
.build_insert_value(
|
||||
struct_val,
|
||||
field_val,
|
||||
index as u32,
|
||||
"insert_record_field",
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
@ -785,7 +790,12 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
|
||||
// Insert field exprs into struct_val
|
||||
for (index, field_val) in field_vals.into_iter().enumerate() {
|
||||
struct_val = builder
|
||||
.build_insert_value(struct_val, field_val, index as u32, "insert_field")
|
||||
.build_insert_value(
|
||||
struct_val,
|
||||
field_val,
|
||||
index as u32,
|
||||
"insert_single_tag_field",
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
@ -848,7 +858,12 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
|
||||
// Insert field exprs into struct_val
|
||||
for (index, field_val) in field_vals.into_iter().enumerate() {
|
||||
struct_val = builder
|
||||
.build_insert_value(struct_val, field_val, index as u32, "insert_field")
|
||||
.build_insert_value(
|
||||
struct_val,
|
||||
field_val,
|
||||
index as u32,
|
||||
"insert_multi_tag_field",
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
@ -1707,12 +1722,10 @@ fn expose_function_to_host<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
roc_function: FunctionValue<'ctx>,
|
||||
) {
|
||||
let c_function_name: String = format!("{}_exposed", roc_function.get_name().to_str().unwrap());
|
||||
let c_function_name: String =
|
||||
format!("roc_{}_exposed", roc_function.get_name().to_str().unwrap());
|
||||
|
||||
let result = expose_function_to_host_help(env, roc_function, &c_function_name);
|
||||
|
||||
let subprogram = env.new_subprogram(&c_function_name);
|
||||
result.set_subprogram(subprogram);
|
||||
expose_function_to_host_help(env, roc_function, &c_function_name);
|
||||
}
|
||||
|
||||
fn expose_function_to_host_help<'a, 'ctx, 'env>(
|
||||
@ -1790,7 +1803,8 @@ fn expose_function_to_host_help<'a, 'ctx, 'env>(
|
||||
|
||||
// STEP 3: build a {} -> u64 function that gives the size of the return type
|
||||
let size_function_type = env.context.i64_type().fn_type(&[], false);
|
||||
let size_function_name: String = format!("{}_size", roc_function.get_name().to_str().unwrap());
|
||||
let size_function_name: String =
|
||||
format!("roc_{}_size", roc_function.get_name().to_str().unwrap());
|
||||
|
||||
let size_function = env.module.add_function(
|
||||
size_function_name.as_str(),
|
||||
@ -1798,10 +1812,30 @@ fn expose_function_to_host_help<'a, 'ctx, 'env>(
|
||||
Some(Linkage::External),
|
||||
);
|
||||
|
||||
let subprogram = env.new_subprogram(&size_function_name);
|
||||
size_function.set_subprogram(subprogram);
|
||||
|
||||
let entry = context.append_basic_block(size_function, "entry");
|
||||
|
||||
builder.position_at_end(entry);
|
||||
|
||||
let func_scope = size_function.get_subprogram().unwrap();
|
||||
let lexical_block = env.dibuilder.create_lexical_block(
|
||||
/* scope */ func_scope.as_debug_info_scope(),
|
||||
/* file */ env.compile_unit.get_file(),
|
||||
/* line_no */ 0,
|
||||
/* column_no */ 0,
|
||||
);
|
||||
|
||||
let loc = env.dibuilder.create_debug_location(
|
||||
env.context,
|
||||
/* line */ 0,
|
||||
/* column */ 0,
|
||||
/* current_scope */ lexical_block.as_debug_info_scope(),
|
||||
/* inlined_at */ None,
|
||||
);
|
||||
builder.set_current_debug_location(env.context, loc);
|
||||
|
||||
let size: BasicValueEnum = return_type.size_of().unwrap().into();
|
||||
builder.build_return(Some(&size));
|
||||
|
||||
@ -1996,6 +2030,9 @@ fn make_exception_catching_wrapper<'a, 'ctx, 'env>(
|
||||
env.module
|
||||
.add_function(&wrapper_function_name, wrapper_function_type, None);
|
||||
|
||||
let subprogram = env.new_subprogram(wrapper_function_name);
|
||||
wrapper_function.set_subprogram(subprogram);
|
||||
|
||||
// our exposed main function adheres to the C calling convention
|
||||
wrapper_function.set_call_conventions(FAST_CALL_CONV);
|
||||
|
||||
@ -2005,6 +2042,23 @@ fn make_exception_catching_wrapper<'a, 'ctx, 'env>(
|
||||
let basic_block = context.append_basic_block(wrapper_function, "entry");
|
||||
builder.position_at_end(basic_block);
|
||||
|
||||
let func_scope = wrapper_function.get_subprogram().unwrap();
|
||||
let lexical_block = env.dibuilder.create_lexical_block(
|
||||
/* scope */ func_scope.as_debug_info_scope(),
|
||||
/* file */ env.compile_unit.get_file(),
|
||||
/* line_no */ 0,
|
||||
/* column_no */ 0,
|
||||
);
|
||||
|
||||
let loc = env.dibuilder.create_debug_location(
|
||||
env.context,
|
||||
/* line */ 0,
|
||||
/* column */ 0,
|
||||
/* current_scope */ lexical_block.as_debug_info_scope(),
|
||||
/* inlined_at */ None,
|
||||
);
|
||||
builder.set_current_debug_location(env.context, loc);
|
||||
|
||||
let result = invoke_and_catch(
|
||||
env,
|
||||
wrapper_function,
|
||||
@ -2103,8 +2157,9 @@ pub fn build_closure_caller<'a, 'ctx, 'env>(
|
||||
|
||||
// STEP 1: build function header
|
||||
|
||||
// e.g. `roc__main_1_Fx_caller`
|
||||
let function_name = format!(
|
||||
"{}_{}_caller",
|
||||
"roc_{}_{}_caller",
|
||||
def_name,
|
||||
alias_symbol.ident_string(&env.interns)
|
||||
);
|
||||
@ -2184,7 +2239,7 @@ pub fn build_closure_caller<'a, 'ctx, 'env>(
|
||||
// STEP 3: build a {} -> u64 function that gives the size of the return type
|
||||
let size_function_type = env.context.i64_type().fn_type(&[], false);
|
||||
let size_function_name: String = format!(
|
||||
"{}_{}_size",
|
||||
"roc_{}_{}_size",
|
||||
def_name,
|
||||
alias_symbol.ident_string(&env.interns)
|
||||
);
|
||||
@ -2491,8 +2546,7 @@ fn run_low_level<'a, 'ctx, 'env>(
|
||||
|
||||
list_contains(env, parent, elem, elem_layout, list, list_layout)
|
||||
}
|
||||
ListWalkRight => {
|
||||
// List.walkRight : List elem, (elem -> accum -> accum), accum -> accum
|
||||
ListWalk => {
|
||||
debug_assert_eq!(args.len(), 3);
|
||||
|
||||
let (list, list_layout) = load_symbol_and_layout(env, scope, &args[0]);
|
||||
@ -2501,7 +2555,28 @@ fn run_low_level<'a, 'ctx, 'env>(
|
||||
|
||||
let (default, default_layout) = load_symbol_and_layout(env, scope, &args[2]);
|
||||
|
||||
list_walk_right(
|
||||
list_walk(
|
||||
env,
|
||||
parent,
|
||||
list,
|
||||
list_layout,
|
||||
func,
|
||||
func_layout,
|
||||
default,
|
||||
default_layout,
|
||||
)
|
||||
}
|
||||
ListWalkBackwards => {
|
||||
// List.walkBackwards : List elem, (elem -> accum -> accum), accum -> accum
|
||||
debug_assert_eq!(args.len(), 3);
|
||||
|
||||
let (list, list_layout) = load_symbol_and_layout(env, scope, &args[0]);
|
||||
|
||||
let (func, func_layout) = load_symbol_and_layout(env, scope, &args[1]);
|
||||
|
||||
let (default, default_layout) = load_symbol_and_layout(env, scope, &args[2]);
|
||||
|
||||
list_walk_backwards(
|
||||
env,
|
||||
parent,
|
||||
list,
|
||||
|
@ -809,9 +809,9 @@ pub fn list_sum<'a, 'ctx, 'env>(
|
||||
builder.build_load(accum_alloca, "load_final_acum")
|
||||
}
|
||||
|
||||
/// List.walkRight : List elem, (elem -> accum -> accum), accum -> accum
|
||||
/// List.walk : List elem, (elem -> accum -> accum), accum -> accum
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn list_walk_right<'a, 'ctx, 'env>(
|
||||
pub fn list_walk<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
list: BasicValueEnum<'ctx>,
|
||||
@ -901,6 +901,98 @@ pub fn list_walk_right<'a, 'ctx, 'env>(
|
||||
builder.build_load(accum_alloca, "load_final_acum")
|
||||
}
|
||||
|
||||
/// List.walkBackwards : List elem, (elem -> accum -> accum), accum -> accum
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn list_walk_backwards<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
list: BasicValueEnum<'ctx>,
|
||||
list_layout: &Layout<'a>,
|
||||
func: BasicValueEnum<'ctx>,
|
||||
func_layout: &Layout<'a>,
|
||||
default: BasicValueEnum<'ctx>,
|
||||
default_layout: &Layout<'a>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let ctx = env.context;
|
||||
let builder = env.builder;
|
||||
|
||||
let list_wrapper = list.into_struct_value();
|
||||
let len = list_len(env.builder, list_wrapper);
|
||||
|
||||
let accum_type = basic_type_from_layout(env.arena, ctx, default_layout, env.ptr_bytes);
|
||||
let accum_alloca = builder.build_alloca(accum_type, "alloca_walk_right_accum");
|
||||
builder.build_store(accum_alloca, default);
|
||||
|
||||
let then_block = ctx.append_basic_block(parent, "then");
|
||||
let cont_block = ctx.append_basic_block(parent, "branchcont");
|
||||
|
||||
let condition = builder.build_int_compare(
|
||||
IntPredicate::UGT,
|
||||
len,
|
||||
ctx.i64_type().const_zero(),
|
||||
"list_non_empty",
|
||||
);
|
||||
|
||||
builder.build_conditional_branch(condition, then_block, cont_block);
|
||||
|
||||
builder.position_at_end(then_block);
|
||||
|
||||
match (func, func_layout) {
|
||||
(BasicValueEnum::PointerValue(func_ptr), Layout::FunctionPointer(_, _)) => {
|
||||
let elem_layout = match list_layout {
|
||||
Layout::Builtin(Builtin::List(_, layout)) => layout,
|
||||
_ => unreachable!("can only fold over a list"),
|
||||
};
|
||||
|
||||
let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes);
|
||||
let elem_ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic);
|
||||
|
||||
let list_ptr = load_list_ptr(builder, list_wrapper, elem_ptr_type);
|
||||
|
||||
let walk_right_loop = |_, elem: BasicValueEnum<'ctx>| {
|
||||
// load current accumulator
|
||||
let current = builder.build_load(accum_alloca, "retrieve_accum");
|
||||
|
||||
let call_site_value =
|
||||
builder.build_call(func_ptr, &[elem, current], "#walk_right_func");
|
||||
|
||||
// set the calling convention explicitly for this call
|
||||
call_site_value.set_call_convention(crate::llvm::build::FAST_CALL_CONV);
|
||||
|
||||
let new_current = call_site_value
|
||||
.try_as_basic_value()
|
||||
.left()
|
||||
.unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer."));
|
||||
|
||||
builder.build_store(accum_alloca, new_current);
|
||||
};
|
||||
|
||||
decrementing_elem_loop(
|
||||
builder,
|
||||
ctx,
|
||||
parent,
|
||||
list_ptr,
|
||||
len,
|
||||
"#index",
|
||||
walk_right_loop,
|
||||
);
|
||||
}
|
||||
|
||||
_ => {
|
||||
unreachable!(
|
||||
"Invalid function basic value enum or layout for List.keepIf : {:?}",
|
||||
(func, func_layout)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
builder.build_unconditional_branch(cont_block);
|
||||
|
||||
builder.position_at_end(cont_block);
|
||||
|
||||
builder.build_load(accum_alloca, "load_final_acum")
|
||||
}
|
||||
|
||||
/// List.contains : List elem, elem -> Bool
|
||||
pub fn list_contains<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
@ -1537,6 +1629,7 @@ where
|
||||
let current_index = builder
|
||||
.build_load(index_alloca, index_name)
|
||||
.into_int_value();
|
||||
|
||||
let next_index = builder.build_int_sub(current_index, one, "nextindex");
|
||||
|
||||
builder.build_store(index_alloca, next_index);
|
||||
@ -1546,7 +1639,7 @@ where
|
||||
|
||||
// #index >= 0
|
||||
let condition = builder.build_int_compare(
|
||||
IntPredicate::UGE,
|
||||
IntPredicate::SGE,
|
||||
next_index,
|
||||
ctx.i64_type().const_zero(),
|
||||
"bounds_check",
|
||||
|
@ -237,11 +237,11 @@ mod gen_list {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_walk_right_empty_all_inline() {
|
||||
fn list_walk_backwards_empty_all_inline() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
List.walkRight [0x1] (\a, b -> a + b) 0
|
||||
List.walkBackwards [0x1] (\a, b -> a + b) 0
|
||||
"#
|
||||
),
|
||||
1,
|
||||
@ -255,7 +255,7 @@ mod gen_list {
|
||||
empty =
|
||||
[]
|
||||
|
||||
List.walkRight empty (\a, b -> a + b) 0
|
||||
List.walkBackwards empty (\a, b -> a + b) 0
|
||||
"#
|
||||
),
|
||||
0,
|
||||
@ -264,22 +264,22 @@ mod gen_list {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_walk_right_with_str() {
|
||||
fn list_walk_backwards_with_str() {
|
||||
assert_evals_to!(
|
||||
r#"List.walkRight [ "x", "y", "z" ] Str.concat "<""#,
|
||||
RocStr::from("zyx<"),
|
||||
r#"List.walkBackwards [ "x", "y", "z" ] Str.concat "<""#,
|
||||
RocStr::from("xyz<"),
|
||||
RocStr
|
||||
);
|
||||
|
||||
assert_evals_to!(
|
||||
r#"List.walkRight [ "Third", "Second", "First" ] Str.concat "Fourth""#,
|
||||
RocStr::from("FirstSecondThirdFourth"),
|
||||
r#"List.walkBackwards [ "Third", "Second", "First" ] Str.concat "Fourth""#,
|
||||
RocStr::from("ThirdSecondFirstFourth"),
|
||||
RocStr
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_walk_right_with_record() {
|
||||
fn list_walk_backwards_with_record() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
@ -295,7 +295,7 @@ mod gen_list {
|
||||
Zero -> { r & zeroes: r.zeroes + 1 }
|
||||
One -> { r & ones: r.ones + 1 }
|
||||
|
||||
finalCounts = List.walkRight byte acc initialCounts
|
||||
finalCounts = List.walkBackwards byte acc initialCounts
|
||||
|
||||
finalCounts.ones * 10 + finalCounts.zeroes
|
||||
"#
|
||||
@ -305,6 +305,26 @@ mod gen_list {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_walk_with_str() {
|
||||
assert_evals_to!(
|
||||
r#"List.walk [ "x", "y", "z" ] Str.concat "<""#,
|
||||
RocStr::from("zyx<"),
|
||||
RocStr
|
||||
);
|
||||
|
||||
assert_evals_to!(
|
||||
r#"List.walk [ "Third", "Second", "First" ] Str.concat "Fourth""#,
|
||||
RocStr::from("FirstSecondThirdFourth"),
|
||||
RocStr
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_walk_substraction() {
|
||||
assert_evals_to!(r#"List.walk [ 1, 2 ] Num.sub 1"#, 2, i64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_keep_if_empty_list_of_int() {
|
||||
assert_evals_to!(
|
||||
@ -599,7 +619,7 @@ mod gen_list {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
empty : List Float
|
||||
empty : List F64
|
||||
empty =
|
||||
[]
|
||||
|
||||
@ -1186,7 +1206,7 @@ mod gen_list {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app Quicksort provides [ main ] imports []
|
||||
app "quicksort" provides [ main ] to "./platform"
|
||||
|
||||
|
||||
swap : Int, Int, List a -> List a
|
||||
|
@ -293,7 +293,7 @@ mod gen_primitives {
|
||||
indoc!(
|
||||
r#"
|
||||
wrapper = \{} ->
|
||||
alwaysFloatIdentity : Int -> (Float -> Float)
|
||||
alwaysFloatIdentity : Int -> (F64 -> F64)
|
||||
alwaysFloatIdentity = \_ ->
|
||||
(\a -> a)
|
||||
|
||||
@ -535,7 +535,7 @@ mod gen_primitives {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app LinkedListLen0 provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
pi = 3.1415
|
||||
|
||||
@ -553,7 +553,7 @@ mod gen_primitives {
|
||||
assert_non_opt_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
LinkedList a : [ Nil, Cons a (LinkedList a) ]
|
||||
|
||||
@ -580,7 +580,7 @@ mod gen_primitives {
|
||||
assert_non_opt_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app LinkedListLenTwice0 provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
LinkedList a : [ Nil, Cons a (LinkedList a) ]
|
||||
|
||||
@ -607,7 +607,7 @@ mod gen_primitives {
|
||||
assert_non_opt_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
LinkedList a : [ Nil, Cons a (LinkedList a) ]
|
||||
|
||||
@ -634,7 +634,7 @@ mod gen_primitives {
|
||||
assert_non_opt_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
LinkedList a : [ Nil, Cons a (LinkedList a) ]
|
||||
|
||||
@ -661,7 +661,7 @@ mod gen_primitives {
|
||||
assert_non_opt_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
LinkedList a : [ Nil, Cons a (LinkedList a) ]
|
||||
|
||||
@ -689,7 +689,7 @@ mod gen_primitives {
|
||||
assert_non_opt_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
LinkedList a : [ Nil, Cons a (LinkedList a) ]
|
||||
|
||||
@ -717,7 +717,7 @@ mod gen_primitives {
|
||||
assert_non_opt_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
LinkedList a : [ Nil, Cons a (LinkedList a) ]
|
||||
|
||||
@ -744,7 +744,7 @@ mod gen_primitives {
|
||||
assert_non_opt_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
LinkedList a : [ Nil, Cons a (LinkedList a) ]
|
||||
|
||||
@ -907,7 +907,7 @@ mod gen_primitives {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
x = 42
|
||||
|
||||
@ -928,7 +928,7 @@ mod gen_primitives {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
foo = \{} ->
|
||||
x = 41
|
||||
@ -951,7 +951,7 @@ mod gen_primitives {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
foo = \{} ->
|
||||
x = 41
|
||||
@ -978,7 +978,7 @@ mod gen_primitives {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
foo = \{} ->
|
||||
x = 41
|
||||
@ -1006,7 +1006,7 @@ mod gen_primitives {
|
||||
assert_non_opt_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
Effect a : [ @Effect ({} -> a) ]
|
||||
|
||||
@ -1016,11 +1016,11 @@ mod gen_primitives {
|
||||
runEffect : Effect a -> a
|
||||
runEffect = \@Effect thunk -> thunk {}
|
||||
|
||||
foo : Effect Float
|
||||
foo : Effect F64
|
||||
foo =
|
||||
succeed 3.14
|
||||
|
||||
main : Float
|
||||
main : F64
|
||||
main =
|
||||
runEffect foo
|
||||
|
||||
@ -1036,19 +1036,19 @@ mod gen_primitives {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
# succeed : a -> ({} -> a)
|
||||
succeed = \x -> \{} -> x
|
||||
|
||||
foo : {} -> Float
|
||||
foo : {} -> F64
|
||||
foo =
|
||||
succeed 3.14
|
||||
|
||||
# runEffect : ({} -> a) -> a
|
||||
runEffect = \thunk -> thunk {}
|
||||
|
||||
main : Float
|
||||
main : F64
|
||||
main =
|
||||
runEffect foo
|
||||
"#
|
||||
@ -1063,7 +1063,7 @@ mod gen_primitives {
|
||||
assert_non_opt_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
Effect a : [ @Effect ({} -> a) ]
|
||||
|
||||
@ -1085,7 +1085,7 @@ mod gen_primitives {
|
||||
assert_non_opt_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
Effect a : [ @Effect ({} -> a) ]
|
||||
|
||||
@ -1110,7 +1110,7 @@ mod gen_primitives {
|
||||
assert_non_opt_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
ConsList a : [ Cons a (ConsList a), Nil ]
|
||||
|
||||
@ -1144,7 +1144,7 @@ mod gen_primitives {
|
||||
assert_non_opt_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
ConsList a : [ Cons a (ConsList a), Nil ]
|
||||
|
||||
@ -1175,7 +1175,7 @@ mod gen_primitives {
|
||||
assert_non_opt_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
State a : { count : Int, x : a }
|
||||
|
||||
@ -1202,7 +1202,7 @@ mod gen_primitives {
|
||||
assert_non_opt_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
NodeColor : [ Red, Black ]
|
||||
|
||||
@ -1284,7 +1284,7 @@ mod gen_primitives {
|
||||
assert_non_opt_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
NodeColor : [ Red, Black ]
|
||||
|
||||
@ -1323,7 +1323,7 @@ mod gen_primitives {
|
||||
assert_non_opt_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
Dict k : [ Node k (Dict k) (Dict k), Empty ]
|
||||
|
||||
@ -1353,7 +1353,7 @@ mod gen_primitives {
|
||||
assert_non_opt_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
NodeColor : [ Red, Black ]
|
||||
|
||||
@ -1393,7 +1393,7 @@ mod gen_primitives {
|
||||
assert_non_opt_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
NodeColor : [ Red, Black ]
|
||||
|
||||
@ -1444,7 +1444,7 @@ mod gen_primitives {
|
||||
assert_non_opt_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
ConsList a : [ Cons a (ConsList a), Nil ]
|
||||
|
||||
@ -1470,7 +1470,7 @@ mod gen_primitives {
|
||||
assert_non_opt_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
ConsList a : [ Cons a (ConsList a), Nil ]
|
||||
|
||||
@ -1498,7 +1498,7 @@ mod gen_primitives {
|
||||
assert_non_opt_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
ConsList a : [ Cons a (ConsList a), Nil ]
|
||||
|
||||
@ -1527,7 +1527,7 @@ mod gen_primitives {
|
||||
assert_non_opt_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
ConsList a : [ Cons a (ConsList a), Nil ]
|
||||
|
||||
@ -1552,7 +1552,7 @@ mod gen_primitives {
|
||||
assert_non_opt_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
BTree : [ Node BTree BTree, Leaf Int ]
|
||||
|
||||
|
@ -405,7 +405,7 @@ mod gen_records {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
f = \r ->
|
||||
when r is
|
||||
@ -455,7 +455,7 @@ mod gen_records {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
f = \r ->
|
||||
{ x ? 10, y } = r
|
||||
@ -492,7 +492,7 @@ mod gen_records {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
f = \r ->
|
||||
{ x ? 10, y } = r
|
||||
@ -512,7 +512,7 @@ mod gen_records {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
f = \r ->
|
||||
{ x ? 10, y } = r
|
||||
@ -565,7 +565,7 @@ mod gen_records {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
f = \{ x ? 10, y } -> x + y
|
||||
|
||||
@ -844,4 +844,49 @@ mod gen_records {
|
||||
(bool, bool)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alignment_in_record() {
|
||||
assert_evals_to!(
|
||||
indoc!("{ c: 32, b: if True then Red else if True then Green else Blue, a: 1 == 1 }"),
|
||||
(32i64, true, 2u8),
|
||||
(i64, bool, u8)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blue_and_present() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
f = \r ->
|
||||
when r is
|
||||
{ x: Blue, y ? 3 } -> y
|
||||
{ x: Red, y ? 5 } -> y
|
||||
|
||||
f { x: Blue, y: 7 }
|
||||
"#
|
||||
),
|
||||
7,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blue_and_absent() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
f = \r ->
|
||||
when r is
|
||||
{ x: Blue, y ? 3 } -> y
|
||||
{ x: Red, y ? 5 } -> y
|
||||
|
||||
f { x: Blue }
|
||||
"#
|
||||
),
|
||||
3,
|
||||
i64
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -417,7 +417,7 @@ mod gen_tags {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
Maybe a : [ Just a, Nothing ]
|
||||
|
||||
@ -481,7 +481,7 @@ mod gen_tags {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
wrapper = \{} ->
|
||||
wrapper = \{} ->
|
||||
when 2 is
|
||||
2 if False -> 0
|
||||
_ -> 42
|
||||
@ -499,7 +499,7 @@ mod gen_tags {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
wrapper = \{} ->
|
||||
wrapper = \{} ->
|
||||
when 2 is
|
||||
2 if True -> 42
|
||||
_ -> 0
|
||||
@ -517,7 +517,7 @@ mod gen_tags {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
wrapper = \{} ->
|
||||
wrapper = \{} ->
|
||||
when 2 is
|
||||
_ if False -> 0
|
||||
_ -> 42
|
||||
@ -630,14 +630,14 @@ mod gen_tags {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
Maybe a : [ Nothing, Just a ]
|
||||
|
||||
x : Maybe (Maybe Int)
|
||||
x = Just (Just 41)
|
||||
|
||||
main =
|
||||
main =
|
||||
x
|
||||
"#
|
||||
),
|
||||
@ -701,11 +701,11 @@ mod gen_tags {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
wrapper = \{} ->
|
||||
wrapper = \{} ->
|
||||
x : [ Red, White, Blue ]
|
||||
x = Blue
|
||||
|
||||
y =
|
||||
y =
|
||||
when x is
|
||||
Red -> 1
|
||||
White -> 2
|
||||
@ -726,8 +726,8 @@ mod gen_tags {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
wrapper = \{} ->
|
||||
y =
|
||||
wrapper = \{} ->
|
||||
y =
|
||||
when 1 + 2 is
|
||||
3 -> 3
|
||||
1 -> 1
|
||||
@ -745,7 +745,7 @@ mod gen_tags {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
y =
|
||||
y =
|
||||
if 1 + 2 > 0 then
|
||||
3
|
||||
else
|
||||
@ -758,4 +758,114 @@ mod gen_tags {
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alignment_in_single_tag_construction() {
|
||||
assert_evals_to!(indoc!("Three (1 == 1) 32"), (32i64, true), (i64, bool));
|
||||
|
||||
assert_evals_to!(
|
||||
indoc!("Three (1 == 1) (if True then Red else if True then Green else Blue) 32"),
|
||||
(32i64, true, 2u8),
|
||||
(i64, bool, u8)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alignment_in_single_tag_pattern_match() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r"#
|
||||
x = Three (1 == 1) 32
|
||||
|
||||
when x is
|
||||
Three bool int ->
|
||||
{ bool, int }
|
||||
#"
|
||||
),
|
||||
(32i64, true),
|
||||
(i64, bool)
|
||||
);
|
||||
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r"#
|
||||
x = Three (1 == 1) (if True then Red else if True then Green else Blue) 32
|
||||
|
||||
when x is
|
||||
Three bool color int ->
|
||||
{ bool, color, int }
|
||||
#"
|
||||
),
|
||||
(32i64, true, 2u8),
|
||||
(i64, bool, u8)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alignment_in_multi_tag_construction() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r"#
|
||||
x : [ Three Bool Int, Empty ]
|
||||
x = Three (1 == 1) 32
|
||||
|
||||
x
|
||||
|
||||
#"
|
||||
),
|
||||
(1, 32i64, true),
|
||||
(i64, i64, bool)
|
||||
);
|
||||
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r"#
|
||||
x : [ Three Bool [ Red, Green, Blue ] Int, Empty ]
|
||||
x = Three (1 == 1) (if True then Red else if True then Green else Blue) 32
|
||||
|
||||
x
|
||||
#"
|
||||
),
|
||||
(1, 32i64, true, 2u8),
|
||||
(i64, i64, bool, u8)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alignment_in_multi_tag_pattern_match() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r"#
|
||||
x : [ Three Bool Int, Empty ]
|
||||
x = Three (1 == 1) 32
|
||||
|
||||
when x is
|
||||
Three bool int ->
|
||||
{ bool, int }
|
||||
|
||||
Empty ->
|
||||
{ bool: False, int: 0 }
|
||||
#"
|
||||
),
|
||||
(32i64, true),
|
||||
(i64, bool)
|
||||
);
|
||||
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r"#
|
||||
x : [ Three Bool [ Red, Green, Blue ] Int, Empty ]
|
||||
x = Three (1 == 1) (if True then Red else if True then Green else Blue) 32
|
||||
|
||||
when x is
|
||||
Three bool color int ->
|
||||
{ bool, color, int }
|
||||
Empty ->
|
||||
{ bool: False, color: Red, int: 0 }
|
||||
#"
|
||||
),
|
||||
(32i64, true, 2u8),
|
||||
(i64, bool, u8)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ use roc_build::program::FunctionIterator;
|
||||
use roc_collections::all::{MutMap, MutSet};
|
||||
|
||||
fn promote_expr_to_module(src: &str) -> String {
|
||||
let mut buffer = String::from("app Test provides [ main ] imports []\n\nmain =\n");
|
||||
let mut buffer = String::from("app \"test\" provides [ main ] to \"./platform\"\n\nmain =\n");
|
||||
|
||||
for line in src.lines() {
|
||||
// indent the body!
|
||||
|
44
compiler/gen_dev/Cargo.toml
Normal file
44
compiler/gen_dev/Cargo.toml
Normal file
@ -0,0 +1,44 @@
|
||||
[package]
|
||||
name = "roc_gen_dev"
|
||||
version = "0.1.0"
|
||||
authors = ["Richard Feldman <oss@rtfeldman.com>"]
|
||||
edition = "2018"
|
||||
license = "Apache-2.0"
|
||||
|
||||
[dependencies]
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_region = { path = "../region" }
|
||||
roc_load = { path = "../load" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_problem = { path = "../problem" }
|
||||
roc_types = { path = "../types" }
|
||||
roc_builtins = { path = "../builtins" }
|
||||
roc_constrain = { path = "../constrain" }
|
||||
roc_uniq = { path = "../uniq" }
|
||||
roc_unify = { path = "../unify" }
|
||||
roc_solve = { path = "../solve" }
|
||||
roc_mono = { path = "../mono" }
|
||||
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!
|
||||
bumpalo = { version = "3.2", features = ["collections"] }
|
||||
inlinable_string = "0.1"
|
||||
target-lexicon = "0.10"
|
||||
libloading = "0.6"
|
||||
object = { version = "0.22", features = ["write"] }
|
||||
|
||||
[dev-dependencies]
|
||||
roc_can = { path = "../can" }
|
||||
roc_parse = { path = "../parse" }
|
||||
roc_reporting = { path = "../reporting" }
|
||||
roc_build = { path = "../build" }
|
||||
roc_std = { path = "../../roc_std" }
|
||||
pretty_assertions = "0.5.1"
|
||||
maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
quickcheck_macros = "0.8"
|
||||
tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded"] }
|
||||
bumpalo = { version = "3.2", features = ["collections"] }
|
||||
libc = "0.2"
|
||||
tempfile = "3.1.0"
|
||||
itertools = "0.9"
|
331
compiler/gen_dev/src/generic64/mod.rs
Normal file
331
compiler/gen_dev/src/generic64/mod.rs
Normal file
@ -0,0 +1,331 @@
|
||||
use crate::{Backend, Env, Relocation};
|
||||
use bumpalo::collections::Vec;
|
||||
use roc_collections::all::{ImSet, MutMap, MutSet};
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_mono::ir::{Literal, Stmt};
|
||||
use std::marker::PhantomData;
|
||||
use target_lexicon::Triple;
|
||||
|
||||
pub mod x86_64;
|
||||
|
||||
pub trait CallConv<GPReg> {
|
||||
fn gp_param_regs() -> &'static [GPReg];
|
||||
fn gp_return_regs() -> &'static [GPReg];
|
||||
fn gp_default_free_regs() -> &'static [GPReg];
|
||||
|
||||
// A linear scan of an array may be faster than a set technically.
|
||||
// That being said, fastest would likely be a trait based on calling convention/register.
|
||||
fn caller_saved_regs() -> ImSet<GPReg>;
|
||||
fn callee_saved_regs() -> ImSet<GPReg>;
|
||||
|
||||
fn stack_pointer() -> GPReg;
|
||||
fn frame_pointer() -> GPReg;
|
||||
|
||||
fn shadow_space_size() -> u8;
|
||||
// It may be worth ignoring the red zone and keeping things simpler.
|
||||
fn red_zone_size() -> u8;
|
||||
}
|
||||
|
||||
pub trait Assembler<GPReg> {
|
||||
fn add_register64bit_immediate32bit<'a>(buf: &mut Vec<'a, u8>, dst: GPReg, imm: i32);
|
||||
fn add_register64bit_register64bit<'a>(buf: &mut Vec<'a, u8>, dst: GPReg, src: GPReg);
|
||||
fn cmovl_register64bit_register64bit<'a>(buf: &mut Vec<'a, u8>, dst: GPReg, src: GPReg);
|
||||
fn mov_register64bit_immediate32bit<'a>(buf: &mut Vec<'a, u8>, dst: GPReg, imm: i32);
|
||||
fn mov_register64bit_immediate64bit<'a>(buf: &mut Vec<'a, u8>, dst: GPReg, imm: i64);
|
||||
fn mov_register64bit_register64bit<'a>(buf: &mut Vec<'a, u8>, dst: GPReg, src: GPReg);
|
||||
fn mov_register64bit_stackoffset32bit<'a>(buf: &mut Vec<'a, u8>, dst: GPReg, offset: i32);
|
||||
fn mov_stackoffset32bit_register64bit<'a>(buf: &mut Vec<'a, u8>, offset: i32, src: GPReg);
|
||||
fn neg_register64bit<'a>(buf: &mut Vec<'a, u8>, reg: GPReg);
|
||||
fn ret<'a>(buf: &mut Vec<'a, u8>);
|
||||
fn sub_register64bit_immediate32bit<'a>(buf: &mut Vec<'a, u8>, dst: GPReg, imm: i32);
|
||||
fn pop_register64bit<'a>(buf: &mut Vec<'a, u8>, reg: GPReg);
|
||||
fn push_register64bit<'a>(buf: &mut Vec<'a, u8>, reg: GPReg);
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
enum SymbolStorage<GPReg> {
|
||||
// These may need layout, but I am not sure.
|
||||
// I think whenever a symbol would be used, we specify layout anyways.
|
||||
GPRegeg(GPReg),
|
||||
Stack(i32),
|
||||
StackAndGPRegeg(GPReg, i32),
|
||||
}
|
||||
|
||||
pub trait GPRegTrait: Copy + Eq + std::hash::Hash + std::fmt::Debug + 'static {}
|
||||
|
||||
pub struct Backend64Bit<'a, GPReg: GPRegTrait, ASM: Assembler<GPReg>, CC: CallConv<GPReg>> {
|
||||
phantom_asm: PhantomData<ASM>,
|
||||
phantom_cc: PhantomData<CC>,
|
||||
env: &'a Env<'a>,
|
||||
buf: Vec<'a, u8>,
|
||||
|
||||
/// leaf_function is true if the only calls this function makes are tail calls.
|
||||
/// If that is the case, we can skip emitting the frame pointer and updating the stack.
|
||||
leaf_function: bool,
|
||||
|
||||
last_seen_map: MutMap<Symbol, *const Stmt<'a>>,
|
||||
free_map: MutMap<*const Stmt<'a>, Vec<'a, Symbol>>,
|
||||
symbols_map: MutMap<Symbol, SymbolStorage<GPReg>>,
|
||||
literal_map: MutMap<Symbol, Literal<'a>>,
|
||||
|
||||
// This should probably be smarter than a vec.
|
||||
// There are certain registers we should always use first. With pushing and poping, this could get mixed.
|
||||
gp_free_regs: Vec<'a, GPReg>,
|
||||
|
||||
// The last major thing we need is a way to decide what reg to free when all of them are full.
|
||||
// Theoretically we want a basic lru cache for the currently loaded symbols.
|
||||
// For now just a vec of used registers and the symbols they contain.
|
||||
gp_used_regs: Vec<'a, (GPReg, Symbol)>,
|
||||
|
||||
stack_size: i32,
|
||||
|
||||
// used callee saved regs must be tracked for pushing and popping at the beginning/end of the function.
|
||||
used_callee_saved_regs: MutSet<GPReg>,
|
||||
}
|
||||
|
||||
impl<'a, GPReg: GPRegTrait, ASM: Assembler<GPReg>, CC: CallConv<GPReg>> Backend<'a>
|
||||
for Backend64Bit<'a, GPReg, ASM, CC>
|
||||
{
|
||||
fn new(env: &'a Env, _target: &Triple) -> Result<Self, String> {
|
||||
Ok(Backend64Bit {
|
||||
phantom_asm: PhantomData,
|
||||
phantom_cc: PhantomData,
|
||||
env,
|
||||
leaf_function: true,
|
||||
buf: bumpalo::vec!(in env.arena),
|
||||
last_seen_map: MutMap::default(),
|
||||
free_map: MutMap::default(),
|
||||
symbols_map: MutMap::default(),
|
||||
literal_map: MutMap::default(),
|
||||
gp_free_regs: bumpalo::vec![in env.arena],
|
||||
gp_used_regs: bumpalo::vec![in env.arena],
|
||||
stack_size: 0,
|
||||
used_callee_saved_regs: MutSet::default(),
|
||||
})
|
||||
}
|
||||
|
||||
fn env(&self) -> &'a Env<'a> {
|
||||
self.env
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.stack_size = -(CC::red_zone_size() as i32);
|
||||
self.leaf_function = true;
|
||||
self.last_seen_map.clear();
|
||||
self.free_map.clear();
|
||||
self.symbols_map.clear();
|
||||
self.buf.clear();
|
||||
self.used_callee_saved_regs.clear();
|
||||
self.gp_free_regs.clear();
|
||||
self.gp_used_regs.clear();
|
||||
self.gp_free_regs
|
||||
.extend_from_slice(CC::gp_default_free_regs());
|
||||
}
|
||||
|
||||
fn set_not_leaf_function(&mut self) {
|
||||
self.leaf_function = false;
|
||||
// If this is not a leaf function, it can't use the shadow space.
|
||||
self.stack_size = CC::shadow_space_size() as i32 - CC::red_zone_size() as i32;
|
||||
}
|
||||
|
||||
fn literal_map(&mut self) -> &mut MutMap<Symbol, Literal<'a>> {
|
||||
&mut self.literal_map
|
||||
}
|
||||
|
||||
fn last_seen_map(&mut self) -> &mut MutMap<Symbol, *const Stmt<'a>> {
|
||||
&mut self.last_seen_map
|
||||
}
|
||||
|
||||
fn set_free_map(&mut self, map: MutMap<*const Stmt<'a>, Vec<'a, Symbol>>) {
|
||||
self.free_map = map;
|
||||
}
|
||||
|
||||
fn free_map(&mut self) -> &mut MutMap<*const Stmt<'a>, Vec<'a, Symbol>> {
|
||||
&mut self.free_map
|
||||
}
|
||||
|
||||
fn finalize(&mut self) -> Result<(&'a [u8], &[Relocation]), String> {
|
||||
let mut out = bumpalo::vec![in self.env.arena];
|
||||
|
||||
if !self.leaf_function {
|
||||
// I believe that this will have to move away from push and to mov to be generic across backends.
|
||||
ASM::push_register64bit(&mut out, CC::frame_pointer());
|
||||
ASM::mov_register64bit_register64bit(
|
||||
&mut out,
|
||||
CC::frame_pointer(),
|
||||
CC::stack_pointer(),
|
||||
);
|
||||
}
|
||||
// Save data in all callee saved regs.
|
||||
let mut pop_order = bumpalo::vec![in self.env.arena];
|
||||
for reg in &self.used_callee_saved_regs {
|
||||
ASM::push_register64bit(&mut out, *reg);
|
||||
pop_order.push(*reg);
|
||||
}
|
||||
if self.stack_size > 0 {
|
||||
ASM::sub_register64bit_immediate32bit(&mut out, CC::stack_pointer(), self.stack_size);
|
||||
}
|
||||
|
||||
// Add function body.
|
||||
out.extend(&self.buf);
|
||||
|
||||
if self.stack_size > 0 {
|
||||
ASM::add_register64bit_immediate32bit(&mut out, CC::stack_pointer(), self.stack_size);
|
||||
}
|
||||
// Restore data in callee saved regs.
|
||||
while let Some(reg) = pop_order.pop() {
|
||||
ASM::pop_register64bit(&mut out, reg);
|
||||
}
|
||||
if !self.leaf_function {
|
||||
ASM::pop_register64bit(&mut out, CC::frame_pointer());
|
||||
}
|
||||
ASM::ret(&mut out);
|
||||
|
||||
Ok((out.into_bump_slice(), &[]))
|
||||
}
|
||||
|
||||
fn build_num_abs_i64(&mut self, dst: &Symbol, src: &Symbol) -> Result<(), String> {
|
||||
let dst_reg = self.claim_gp_reg(dst)?;
|
||||
let src_reg = self.load_to_reg(src)?;
|
||||
ASM::mov_register64bit_register64bit(&mut self.buf, dst_reg, src_reg);
|
||||
ASM::neg_register64bit(&mut self.buf, dst_reg);
|
||||
ASM::cmovl_register64bit_register64bit(&mut self.buf, dst_reg, src_reg);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build_num_add_i64(
|
||||
&mut self,
|
||||
dst: &Symbol,
|
||||
src1: &Symbol,
|
||||
src2: &Symbol,
|
||||
) -> Result<(), String> {
|
||||
let dst_reg = self.claim_gp_reg(dst)?;
|
||||
let src1_reg = self.load_to_reg(src1)?;
|
||||
ASM::mov_register64bit_register64bit(&mut self.buf, dst_reg, src1_reg);
|
||||
let src2_reg = self.load_to_reg(src2)?;
|
||||
ASM::add_register64bit_register64bit(&mut self.buf, dst_reg, src2_reg);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load_literal(&mut self, sym: &Symbol, lit: &Literal<'a>) -> Result<(), String> {
|
||||
match lit {
|
||||
Literal::Int(x) => {
|
||||
let reg = self.claim_gp_reg(sym)?;
|
||||
let val = *x;
|
||||
ASM::mov_register64bit_immediate64bit(&mut self.buf, reg, val);
|
||||
Ok(())
|
||||
}
|
||||
x => Err(format!("loading literal, {:?}, is not yet implemented", x)),
|
||||
}
|
||||
}
|
||||
|
||||
fn free_symbol(&mut self, sym: &Symbol) {
|
||||
self.symbols_map.remove(sym);
|
||||
for i in 0..self.gp_used_regs.len() {
|
||||
let (reg, saved_sym) = self.gp_used_regs[i];
|
||||
if saved_sym == *sym {
|
||||
self.gp_free_regs.push(reg);
|
||||
self.gp_used_regs.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn return_symbol(&mut self, sym: &Symbol) -> Result<(), String> {
|
||||
let val = self.symbols_map.get(sym);
|
||||
match val {
|
||||
Some(SymbolStorage::GPRegeg(reg)) if *reg == CC::gp_return_regs()[0] => Ok(()),
|
||||
Some(SymbolStorage::GPRegeg(reg)) => {
|
||||
// If it fits in a general purpose register, just copy it over to.
|
||||
// Technically this can be optimized to produce shorter instructions if less than 64bits.
|
||||
ASM::mov_register64bit_register64bit(&mut self.buf, CC::gp_return_regs()[0], *reg);
|
||||
Ok(())
|
||||
}
|
||||
Some(x) => Err(format!(
|
||||
"returning symbol storage, {:?}, is not yet implemented",
|
||||
x
|
||||
)),
|
||||
None => Err(format!("Unknown return symbol: {}", sym)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This impl block is for ir related instructions that need backend specific information.
|
||||
/// For example, loading a symbol for doing a computation.
|
||||
impl<'a, GPReg: GPRegTrait, ASM: Assembler<GPReg>, CC: CallConv<GPReg>>
|
||||
Backend64Bit<'a, GPReg, ASM, CC>
|
||||
{
|
||||
fn claim_gp_reg(&mut self, sym: &Symbol) -> Result<GPReg, String> {
|
||||
let reg = if !self.gp_free_regs.is_empty() {
|
||||
let free_reg = self.gp_free_regs.pop().unwrap();
|
||||
if CC::callee_saved_regs().contains(&free_reg) {
|
||||
self.used_callee_saved_regs.insert(free_reg);
|
||||
}
|
||||
Ok(free_reg)
|
||||
} else if !self.gp_used_regs.is_empty() {
|
||||
let (reg, sym) = self.gp_used_regs.remove(0);
|
||||
self.free_to_stack(&sym)?;
|
||||
Ok(reg)
|
||||
} else {
|
||||
Err("completely out of registers".to_string())
|
||||
}?;
|
||||
|
||||
self.gp_used_regs.push((reg, *sym));
|
||||
self.symbols_map.insert(*sym, SymbolStorage::GPRegeg(reg));
|
||||
Ok(reg)
|
||||
}
|
||||
|
||||
fn load_to_reg(&mut self, sym: &Symbol) -> Result<GPReg, String> {
|
||||
let val = self.symbols_map.remove(sym);
|
||||
match val {
|
||||
Some(SymbolStorage::GPRegeg(reg)) => {
|
||||
self.symbols_map.insert(*sym, SymbolStorage::GPRegeg(reg));
|
||||
Ok(reg)
|
||||
}
|
||||
Some(SymbolStorage::StackAndGPRegeg(reg, offset)) => {
|
||||
self.symbols_map
|
||||
.insert(*sym, SymbolStorage::StackAndGPRegeg(reg, offset));
|
||||
Ok(reg)
|
||||
}
|
||||
Some(SymbolStorage::Stack(offset)) => {
|
||||
let reg = self.claim_gp_reg(sym)?;
|
||||
self.symbols_map
|
||||
.insert(*sym, SymbolStorage::StackAndGPRegeg(reg, offset));
|
||||
ASM::mov_register64bit_stackoffset32bit(&mut self.buf, reg, offset as i32);
|
||||
Ok(reg)
|
||||
}
|
||||
None => Err(format!("Unknown symbol: {}", sym)),
|
||||
}
|
||||
}
|
||||
|
||||
fn free_to_stack(&mut self, sym: &Symbol) -> Result<(), String> {
|
||||
let val = self.symbols_map.remove(sym);
|
||||
match val {
|
||||
Some(SymbolStorage::GPRegeg(reg)) => {
|
||||
let offset = self.stack_size;
|
||||
self.stack_size += 8;
|
||||
if let Some(size) = self.stack_size.checked_add(8) {
|
||||
self.stack_size = size;
|
||||
} else {
|
||||
return Err(format!(
|
||||
"Ran out of stack space while saving symbol: {}",
|
||||
sym
|
||||
));
|
||||
}
|
||||
ASM::mov_stackoffset32bit_register64bit(&mut self.buf, offset as i32, reg);
|
||||
self.symbols_map
|
||||
.insert(*sym, SymbolStorage::Stack(offset as i32));
|
||||
Ok(())
|
||||
}
|
||||
Some(SymbolStorage::StackAndGPRegeg(_, offset)) => {
|
||||
self.symbols_map.insert(*sym, SymbolStorage::Stack(offset));
|
||||
Ok(())
|
||||
}
|
||||
Some(SymbolStorage::Stack(offset)) => {
|
||||
self.symbols_map.insert(*sym, SymbolStorage::Stack(offset));
|
||||
Ok(())
|
||||
}
|
||||
None => Err(format!("Unknown symbol: {}", sym)),
|
||||
}
|
||||
}
|
||||
}
|
582
compiler/gen_dev/src/generic64/x86_64.rs
Normal file
582
compiler/gen_dev/src/generic64/x86_64.rs
Normal file
@ -0,0 +1,582 @@
|
||||
use crate::generic64::{Assembler, CallConv, GPRegTrait};
|
||||
use bumpalo::collections::Vec;
|
||||
use roc_collections::all::ImSet;
|
||||
|
||||
// Not sure exactly how I want to represent registers.
|
||||
// If we want max speed, we would likely make them structs that impl the same trait to avoid ifs.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
|
||||
pub enum X86_64GPReg {
|
||||
RAX = 0,
|
||||
RCX = 1,
|
||||
RDX = 2,
|
||||
RBX = 3,
|
||||
RSP = 4,
|
||||
RBP = 5,
|
||||
RSI = 6,
|
||||
RDI = 7,
|
||||
R8 = 8,
|
||||
R9 = 9,
|
||||
R10 = 10,
|
||||
R11 = 11,
|
||||
R12 = 12,
|
||||
R13 = 13,
|
||||
R14 = 14,
|
||||
R15 = 15,
|
||||
}
|
||||
|
||||
impl GPRegTrait for X86_64GPReg {}
|
||||
|
||||
const REX: u8 = 0x40;
|
||||
const REX_W: u8 = REX + 0x8;
|
||||
|
||||
fn add_rm_extension(reg: X86_64GPReg, byte: u8) -> u8 {
|
||||
if reg as u8 > 7 {
|
||||
byte + 1
|
||||
} else {
|
||||
byte
|
||||
}
|
||||
}
|
||||
|
||||
fn add_opcode_extension(reg: X86_64GPReg, byte: u8) -> u8 {
|
||||
add_rm_extension(reg, byte)
|
||||
}
|
||||
|
||||
fn add_reg_extension(reg: X86_64GPReg, byte: u8) -> u8 {
|
||||
if reg as u8 > 7 {
|
||||
byte + 4
|
||||
} else {
|
||||
byte
|
||||
}
|
||||
}
|
||||
|
||||
pub struct X86_64Assembler {}
|
||||
pub struct X86_64WindowsFastcall {}
|
||||
pub struct X86_64SystemV {}
|
||||
|
||||
impl CallConv<X86_64GPReg> for X86_64SystemV {
|
||||
fn gp_param_regs() -> &'static [X86_64GPReg] {
|
||||
&[
|
||||
X86_64GPReg::RDI,
|
||||
X86_64GPReg::RSI,
|
||||
X86_64GPReg::RDX,
|
||||
X86_64GPReg::RCX,
|
||||
X86_64GPReg::R8,
|
||||
X86_64GPReg::R9,
|
||||
]
|
||||
}
|
||||
fn gp_return_regs() -> &'static [X86_64GPReg] {
|
||||
&[X86_64GPReg::RAX, X86_64GPReg::RDX]
|
||||
}
|
||||
fn gp_default_free_regs() -> &'static [X86_64GPReg] {
|
||||
&[
|
||||
// The regs we want to use first should be at the end of this vec.
|
||||
// We will use pop to get which reg to use next
|
||||
// Use callee saved regs last.
|
||||
X86_64GPReg::RBX,
|
||||
// Don't use frame pointer: X86_64GPReg::RBP,
|
||||
X86_64GPReg::R12,
|
||||
X86_64GPReg::R13,
|
||||
X86_64GPReg::R14,
|
||||
X86_64GPReg::R15,
|
||||
// Use caller saved regs first.
|
||||
X86_64GPReg::RAX,
|
||||
X86_64GPReg::RCX,
|
||||
X86_64GPReg::RDX,
|
||||
// Don't use stack pionter: X86_64GPReg::RSP,
|
||||
X86_64GPReg::RSI,
|
||||
X86_64GPReg::RDI,
|
||||
X86_64GPReg::R8,
|
||||
X86_64GPReg::R9,
|
||||
X86_64GPReg::R10,
|
||||
X86_64GPReg::R11,
|
||||
]
|
||||
}
|
||||
fn caller_saved_regs() -> ImSet<X86_64GPReg> {
|
||||
// TODO: stop using vec! here. I was just have trouble with some errors, but it shouldn't be needed.
|
||||
ImSet::from(vec![
|
||||
X86_64GPReg::RAX,
|
||||
X86_64GPReg::RCX,
|
||||
X86_64GPReg::RDX,
|
||||
X86_64GPReg::RSP,
|
||||
X86_64GPReg::RSI,
|
||||
X86_64GPReg::RDI,
|
||||
X86_64GPReg::R8,
|
||||
X86_64GPReg::R9,
|
||||
X86_64GPReg::R10,
|
||||
X86_64GPReg::R11,
|
||||
])
|
||||
}
|
||||
fn callee_saved_regs() -> ImSet<X86_64GPReg> {
|
||||
// TODO: stop using vec! here. I was just have trouble with some errors, but it shouldn't be needed.
|
||||
ImSet::from(vec![
|
||||
X86_64GPReg::RBX,
|
||||
X86_64GPReg::RBP,
|
||||
X86_64GPReg::R12,
|
||||
X86_64GPReg::R13,
|
||||
X86_64GPReg::R14,
|
||||
X86_64GPReg::R15,
|
||||
])
|
||||
}
|
||||
fn stack_pointer() -> X86_64GPReg {
|
||||
X86_64GPReg::RSP
|
||||
}
|
||||
fn frame_pointer() -> X86_64GPReg {
|
||||
X86_64GPReg::RBP
|
||||
}
|
||||
fn shadow_space_size() -> u8 {
|
||||
0
|
||||
}
|
||||
fn red_zone_size() -> u8 {
|
||||
128
|
||||
}
|
||||
}
|
||||
|
||||
impl CallConv<X86_64GPReg> for X86_64WindowsFastcall {
|
||||
fn gp_param_regs() -> &'static [X86_64GPReg] {
|
||||
&[
|
||||
X86_64GPReg::RCX,
|
||||
X86_64GPReg::RDX,
|
||||
X86_64GPReg::R8,
|
||||
X86_64GPReg::R9,
|
||||
]
|
||||
}
|
||||
fn gp_return_regs() -> &'static [X86_64GPReg] {
|
||||
&[X86_64GPReg::RAX]
|
||||
}
|
||||
fn gp_default_free_regs() -> &'static [X86_64GPReg] {
|
||||
&[
|
||||
// The regs we want to use first should be at the end of this vec.
|
||||
// We will use pop to get which reg to use next
|
||||
// Use callee saved regs last.
|
||||
X86_64GPReg::RBX,
|
||||
// Don't use frame pointer: X86_64GPReg::RBP,
|
||||
X86_64GPReg::RSI,
|
||||
// Don't use stack pionter: X86_64GPReg::RSP,
|
||||
X86_64GPReg::RDI,
|
||||
X86_64GPReg::R12,
|
||||
X86_64GPReg::R13,
|
||||
X86_64GPReg::R14,
|
||||
X86_64GPReg::R15,
|
||||
// Use caller saved regs first.
|
||||
X86_64GPReg::RAX,
|
||||
X86_64GPReg::RCX,
|
||||
X86_64GPReg::RDX,
|
||||
X86_64GPReg::R8,
|
||||
X86_64GPReg::R9,
|
||||
X86_64GPReg::R10,
|
||||
X86_64GPReg::R11,
|
||||
]
|
||||
}
|
||||
fn caller_saved_regs() -> ImSet<X86_64GPReg> {
|
||||
// TODO: stop using vec! here. I was just have trouble with some errors, but it shouldn't be needed.
|
||||
ImSet::from(vec![
|
||||
X86_64GPReg::RAX,
|
||||
X86_64GPReg::RCX,
|
||||
X86_64GPReg::RDX,
|
||||
X86_64GPReg::R8,
|
||||
X86_64GPReg::R9,
|
||||
X86_64GPReg::R10,
|
||||
X86_64GPReg::R11,
|
||||
])
|
||||
}
|
||||
fn callee_saved_regs() -> ImSet<X86_64GPReg> {
|
||||
// TODO: stop using vec! here. I was just have trouble with some errors, but it shouldn't be needed.
|
||||
ImSet::from(vec![
|
||||
X86_64GPReg::RBX,
|
||||
X86_64GPReg::RBP,
|
||||
X86_64GPReg::RSI,
|
||||
X86_64GPReg::RSP,
|
||||
X86_64GPReg::RDI,
|
||||
X86_64GPReg::R12,
|
||||
X86_64GPReg::R13,
|
||||
X86_64GPReg::R14,
|
||||
X86_64GPReg::R15,
|
||||
])
|
||||
}
|
||||
fn stack_pointer() -> X86_64GPReg {
|
||||
X86_64GPReg::RSP
|
||||
}
|
||||
fn frame_pointer() -> X86_64GPReg {
|
||||
X86_64GPReg::RBP
|
||||
}
|
||||
fn shadow_space_size() -> u8 {
|
||||
32
|
||||
}
|
||||
fn red_zone_size() -> u8 {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
impl Assembler<X86_64GPReg> for X86_64Assembler {
|
||||
// Below here are the functions for all of the assembly instructions.
|
||||
// Their names are based on the instruction and operators combined.
|
||||
// You should call `buf.reserve()` if you push or extend more than once.
|
||||
// Unit tests are added at the bottom of the file to ensure correct asm generation.
|
||||
// Please keep these in alphanumeric order.
|
||||
|
||||
/// `ADD r/m64, imm32` -> Add imm32 sign-extended to 64-bits from r/m64.
|
||||
fn add_register64bit_immediate32bit<'a>(buf: &mut Vec<'a, u8>, dst: X86_64GPReg, imm: i32) {
|
||||
// This can be optimized if the immediate is 1 byte.
|
||||
let rex = add_rm_extension(dst, REX_W);
|
||||
let dst_mod = dst as u8 % 8;
|
||||
buf.reserve(7);
|
||||
buf.extend(&[rex, 0x81, 0xC0 + dst_mod]);
|
||||
buf.extend(&imm.to_le_bytes());
|
||||
}
|
||||
|
||||
/// `ADD r/m64,r64` -> Add r64 to r/m64.
|
||||
fn add_register64bit_register64bit<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
dst: X86_64GPReg,
|
||||
src: X86_64GPReg,
|
||||
) {
|
||||
let rex = add_rm_extension(dst, REX_W);
|
||||
let rex = add_reg_extension(src, rex);
|
||||
let dst_mod = dst as u8 % 8;
|
||||
let src_mod = (src as u8 % 8) << 3;
|
||||
buf.extend(&[rex, 0x01, 0xC0 + dst_mod + src_mod]);
|
||||
}
|
||||
|
||||
/// `CMOVL r64,r/m64` -> Move if less (SF≠ OF).
|
||||
fn cmovl_register64bit_register64bit<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
dst: X86_64GPReg,
|
||||
src: X86_64GPReg,
|
||||
) {
|
||||
let rex = add_reg_extension(dst, REX_W);
|
||||
let rex = add_rm_extension(src, rex);
|
||||
let dst_mod = (dst as u8 % 8) << 3;
|
||||
let src_mod = src as u8 % 8;
|
||||
buf.extend(&[rex, 0x0F, 0x4C, 0xC0 + dst_mod + src_mod]);
|
||||
}
|
||||
|
||||
/// `MOV r/m64, imm32` -> Move imm32 sign extended to 64-bits to r/m64.
|
||||
fn mov_register64bit_immediate32bit<'a>(buf: &mut Vec<'a, u8>, dst: X86_64GPReg, imm: i32) {
|
||||
let rex = add_rm_extension(dst, REX_W);
|
||||
let dst_mod = dst as u8 % 8;
|
||||
buf.reserve(7);
|
||||
buf.extend(&[rex, 0xC7, 0xC0 + dst_mod]);
|
||||
buf.extend(&imm.to_le_bytes());
|
||||
}
|
||||
|
||||
/// `MOV r64, imm64` -> Move imm64 to r64.
|
||||
fn mov_register64bit_immediate64bit<'a>(buf: &mut Vec<'a, u8>, dst: X86_64GPReg, imm: i64) {
|
||||
if imm <= i32::MAX as i64 && imm >= i32::MIN as i64 {
|
||||
Self::mov_register64bit_immediate32bit(buf, dst, imm as i32)
|
||||
} else {
|
||||
let rex = add_opcode_extension(dst, REX_W);
|
||||
let dst_mod = dst as u8 % 8;
|
||||
buf.reserve(10);
|
||||
buf.extend(&[rex, 0xB8 + dst_mod]);
|
||||
buf.extend(&imm.to_le_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
/// `MOV r/m64,r64` -> Move r64 to r/m64.
|
||||
fn mov_register64bit_register64bit<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
dst: X86_64GPReg,
|
||||
src: X86_64GPReg,
|
||||
) {
|
||||
let rex = add_rm_extension(dst, REX_W);
|
||||
let rex = add_reg_extension(src, rex);
|
||||
let dst_mod = dst as u8 % 8;
|
||||
let src_mod = (src as u8 % 8) << 3;
|
||||
buf.extend(&[rex, 0x89, 0xC0 + dst_mod + src_mod]);
|
||||
}
|
||||
|
||||
/// `MOV r64,r/m64` -> Move r/m64 to r64.
|
||||
fn mov_register64bit_stackoffset32bit<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
dst: X86_64GPReg,
|
||||
offset: i32,
|
||||
) {
|
||||
// This can be optimized based on how many bytes the offset actually is.
|
||||
// This function can probably be made to take any memory offset, I didn't feel like figuring it out rn.
|
||||
// Also, this may technically be faster genration since stack operations should be so common.
|
||||
let rex = add_reg_extension(dst, REX_W);
|
||||
let dst_mod = (dst as u8 % 8) << 3;
|
||||
buf.reserve(8);
|
||||
buf.extend(&[rex, 0x8B, 0x84 + dst_mod, 0x24]);
|
||||
buf.extend(&offset.to_le_bytes());
|
||||
}
|
||||
|
||||
/// `MOV r/m64,r64` -> Move r64 to r/m64.
|
||||
fn mov_stackoffset32bit_register64bit<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
offset: i32,
|
||||
src: X86_64GPReg,
|
||||
) {
|
||||
// This can be optimized based on how many bytes the offset actually is.
|
||||
// This function can probably be made to take any memory offset, I didn't feel like figuring it out rn.
|
||||
// Also, this may technically be faster genration since stack operations should be so common.
|
||||
let rex = add_reg_extension(src, REX_W);
|
||||
let src_mod = (src as u8 % 8) << 3;
|
||||
buf.reserve(8);
|
||||
buf.extend(&[rex, 0x89, 0x84 + src_mod, 0x24]);
|
||||
buf.extend(&offset.to_le_bytes());
|
||||
}
|
||||
|
||||
/// `NEG r/m64` -> Two's complement negate r/m64.
|
||||
fn neg_register64bit<'a>(buf: &mut Vec<'a, u8>, reg: X86_64GPReg) {
|
||||
let rex = add_rm_extension(reg, REX_W);
|
||||
let reg_mod = reg as u8 % 8;
|
||||
buf.extend(&[rex, 0xF7, 0xD8 + reg_mod]);
|
||||
}
|
||||
|
||||
/// `RET` -> Near return to calling procedure.
|
||||
fn ret<'a>(buf: &mut Vec<'a, u8>) {
|
||||
buf.push(0xC3);
|
||||
}
|
||||
|
||||
/// `SUB r/m64, imm32` -> Subtract imm32 sign-extended to 64-bits from r/m64.
|
||||
fn sub_register64bit_immediate32bit<'a>(buf: &mut Vec<'a, u8>, dst: X86_64GPReg, imm: i32) {
|
||||
// This can be optimized if the immediate is 1 byte.
|
||||
let rex = add_rm_extension(dst, REX_W);
|
||||
let dst_mod = dst as u8 % 8;
|
||||
buf.reserve(7);
|
||||
buf.extend(&[rex, 0x81, 0xE8 + dst_mod]);
|
||||
buf.extend(&imm.to_le_bytes());
|
||||
}
|
||||
|
||||
/// `POP r64` -> Pop top of stack into r64; increment stack pointer. Cannot encode 32-bit operand size.
|
||||
fn pop_register64bit<'a>(buf: &mut Vec<'a, u8>, reg: X86_64GPReg) {
|
||||
let reg_mod = reg as u8 % 8;
|
||||
if reg as u8 > 7 {
|
||||
let rex = add_opcode_extension(reg, REX);
|
||||
buf.extend(&[rex, 0x58 + reg_mod]);
|
||||
} else {
|
||||
buf.push(0x58 + reg_mod);
|
||||
}
|
||||
}
|
||||
|
||||
/// `PUSH r64` -> Push r64,
|
||||
fn push_register64bit<'a>(buf: &mut Vec<'a, u8>, reg: X86_64GPReg) {
|
||||
let reg_mod = reg as u8 % 8;
|
||||
if reg as u8 > 7 {
|
||||
let rex = add_opcode_extension(reg, REX);
|
||||
buf.extend(&[rex, 0x50 + reg_mod]);
|
||||
} else {
|
||||
buf.push(0x50 + reg_mod);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When writing tests, it is a good idea to test both a number and unnumbered register.
|
||||
// This is because R8-R15 often have special instruction prefixes.
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
const TEST_I32: i32 = 0x12345678;
|
||||
const TEST_I64: i64 = 0x12345678_9ABCDEF0;
|
||||
|
||||
#[test]
|
||||
fn test_add_register64bit_immediate32bit() {
|
||||
let arena = bumpalo::Bump::new();
|
||||
let mut buf = bumpalo::vec![in &arena];
|
||||
for (dst, expected) in &[
|
||||
(X86_64GPReg::RAX, [0x48, 0x81, 0xC0]),
|
||||
(X86_64GPReg::R15, [0x49, 0x81, 0xC7]),
|
||||
] {
|
||||
buf.clear();
|
||||
X86_64Assembler::add_register64bit_immediate32bit(&mut buf, *dst, TEST_I32);
|
||||
assert_eq!(expected, &buf[..3]);
|
||||
assert_eq!(TEST_I32.to_le_bytes(), &buf[3..]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_register64bit_register64bit() {
|
||||
let arena = bumpalo::Bump::new();
|
||||
let mut buf = bumpalo::vec![in &arena];
|
||||
for ((dst, src), expected) in &[
|
||||
((X86_64GPReg::RAX, X86_64GPReg::RAX), [0x48, 0x01, 0xC0]),
|
||||
((X86_64GPReg::RAX, X86_64GPReg::R15), [0x4C, 0x01, 0xF8]),
|
||||
((X86_64GPReg::R15, X86_64GPReg::RAX), [0x49, 0x01, 0xC7]),
|
||||
((X86_64GPReg::R15, X86_64GPReg::R15), [0x4D, 0x01, 0xFF]),
|
||||
] {
|
||||
buf.clear();
|
||||
X86_64Assembler::add_register64bit_register64bit(&mut buf, *dst, *src);
|
||||
assert_eq!(expected, &buf[..]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cmovl_register64bit_register64bit() {
|
||||
let arena = bumpalo::Bump::new();
|
||||
let mut buf = bumpalo::vec![in &arena];
|
||||
for ((dst, src), expected) in &[
|
||||
(
|
||||
(X86_64GPReg::RAX, X86_64GPReg::RAX),
|
||||
[0x48, 0x0F, 0x4C, 0xC0],
|
||||
),
|
||||
(
|
||||
(X86_64GPReg::RAX, X86_64GPReg::R15),
|
||||
[0x49, 0x0F, 0x4C, 0xC7],
|
||||
),
|
||||
(
|
||||
(X86_64GPReg::R15, X86_64GPReg::RAX),
|
||||
[0x4C, 0x0F, 0x4C, 0xF8],
|
||||
),
|
||||
(
|
||||
(X86_64GPReg::R15, X86_64GPReg::R15),
|
||||
[0x4D, 0x0F, 0x4C, 0xFF],
|
||||
),
|
||||
] {
|
||||
buf.clear();
|
||||
X86_64Assembler::cmovl_register64bit_register64bit(&mut buf, *dst, *src);
|
||||
assert_eq!(expected, &buf[..]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mov_register64bit_immediate32bit() {
|
||||
let arena = bumpalo::Bump::new();
|
||||
let mut buf = bumpalo::vec![in &arena];
|
||||
for (dst, expected) in &[
|
||||
(X86_64GPReg::RAX, [0x48, 0xC7, 0xC0]),
|
||||
(X86_64GPReg::R15, [0x49, 0xC7, 0xC7]),
|
||||
] {
|
||||
buf.clear();
|
||||
X86_64Assembler::mov_register64bit_immediate32bit(&mut buf, *dst, TEST_I32);
|
||||
assert_eq!(expected, &buf[..3]);
|
||||
assert_eq!(TEST_I32.to_le_bytes(), &buf[3..]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mov_register64bit_immediate64bit() {
|
||||
let arena = bumpalo::Bump::new();
|
||||
let mut buf = bumpalo::vec![in &arena];
|
||||
for (dst, expected) in &[
|
||||
(X86_64GPReg::RAX, [0x48, 0xB8]),
|
||||
(X86_64GPReg::R15, [0x49, 0xBF]),
|
||||
] {
|
||||
buf.clear();
|
||||
X86_64Assembler::mov_register64bit_immediate64bit(&mut buf, *dst, TEST_I64);
|
||||
assert_eq!(expected, &buf[..2]);
|
||||
assert_eq!(TEST_I64.to_le_bytes(), &buf[2..]);
|
||||
}
|
||||
for (dst, expected) in &[
|
||||
(X86_64GPReg::RAX, [0x48, 0xC7, 0xC0]),
|
||||
(X86_64GPReg::R15, [0x49, 0xC7, 0xC7]),
|
||||
] {
|
||||
buf.clear();
|
||||
X86_64Assembler::mov_register64bit_immediate64bit(&mut buf, *dst, TEST_I32 as i64);
|
||||
assert_eq!(expected, &buf[..3]);
|
||||
assert_eq!(TEST_I32.to_le_bytes(), &buf[3..]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mov_register64bit_register64bit() {
|
||||
let arena = bumpalo::Bump::new();
|
||||
let mut buf = bumpalo::vec![in &arena];
|
||||
for ((dst, src), expected) in &[
|
||||
((X86_64GPReg::RAX, X86_64GPReg::RAX), [0x48, 0x89, 0xC0]),
|
||||
((X86_64GPReg::RAX, X86_64GPReg::R15), [0x4C, 0x89, 0xF8]),
|
||||
((X86_64GPReg::R15, X86_64GPReg::RAX), [0x49, 0x89, 0xC7]),
|
||||
((X86_64GPReg::R15, X86_64GPReg::R15), [0x4D, 0x89, 0xFF]),
|
||||
] {
|
||||
buf.clear();
|
||||
X86_64Assembler::mov_register64bit_register64bit(&mut buf, *dst, *src);
|
||||
assert_eq!(expected, &buf[..]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mov_register64bit_stackoffset32bit() {
|
||||
let arena = bumpalo::Bump::new();
|
||||
let mut buf = bumpalo::vec![in &arena];
|
||||
for ((dst, offset), expected) in &[
|
||||
((X86_64GPReg::RAX, TEST_I32), [0x48, 0x8B, 0x84, 0x24]),
|
||||
((X86_64GPReg::R15, TEST_I32), [0x4C, 0x8B, 0xBC, 0x24]),
|
||||
] {
|
||||
buf.clear();
|
||||
X86_64Assembler::mov_register64bit_stackoffset32bit(&mut buf, *dst, *offset);
|
||||
assert_eq!(expected, &buf[..4]);
|
||||
assert_eq!(TEST_I32.to_le_bytes(), &buf[4..]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mov_stackoffset32bit_register64bit() {
|
||||
let arena = bumpalo::Bump::new();
|
||||
let mut buf = bumpalo::vec![in &arena];
|
||||
for ((offset, src), expected) in &[
|
||||
((TEST_I32, X86_64GPReg::RAX), [0x48, 0x89, 0x84, 0x24]),
|
||||
((TEST_I32, X86_64GPReg::R15), [0x4C, 0x89, 0xBC, 0x24]),
|
||||
] {
|
||||
buf.clear();
|
||||
X86_64Assembler::mov_stackoffset32bit_register64bit(&mut buf, *offset, *src);
|
||||
assert_eq!(expected, &buf[..4]);
|
||||
assert_eq!(TEST_I32.to_le_bytes(), &buf[4..]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_neg_register64bit() {
|
||||
let arena = bumpalo::Bump::new();
|
||||
let mut buf = bumpalo::vec![in &arena];
|
||||
for (reg, expected) in &[
|
||||
(X86_64GPReg::RAX, [0x48, 0xF7, 0xD8]),
|
||||
(X86_64GPReg::R15, [0x49, 0xF7, 0xDF]),
|
||||
] {
|
||||
buf.clear();
|
||||
X86_64Assembler::neg_register64bit(&mut buf, *reg);
|
||||
assert_eq!(expected, &buf[..]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ret() {
|
||||
let arena = bumpalo::Bump::new();
|
||||
let mut buf = bumpalo::vec![in &arena];
|
||||
X86_64Assembler::ret(&mut buf);
|
||||
assert_eq!(&[0xC3], &buf[..]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sub_register64bit_immediate32bit() {
|
||||
let arena = bumpalo::Bump::new();
|
||||
let mut buf = bumpalo::vec![in &arena];
|
||||
for (dst, expected) in &[
|
||||
(X86_64GPReg::RAX, [0x48, 0x81, 0xE8]),
|
||||
(X86_64GPReg::R15, [0x49, 0x81, 0xEF]),
|
||||
] {
|
||||
buf.clear();
|
||||
X86_64Assembler::sub_register64bit_immediate32bit(&mut buf, *dst, TEST_I32);
|
||||
assert_eq!(expected, &buf[..3]);
|
||||
assert_eq!(TEST_I32.to_le_bytes(), &buf[3..]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pop_register64bit() {
|
||||
let arena = bumpalo::Bump::new();
|
||||
let mut buf = bumpalo::vec![in &arena];
|
||||
for (dst, expected) in &[
|
||||
(X86_64GPReg::RAX, vec![0x58]),
|
||||
(X86_64GPReg::R15, vec![0x41, 0x5F]),
|
||||
] {
|
||||
buf.clear();
|
||||
X86_64Assembler::pop_register64bit(&mut buf, *dst);
|
||||
assert_eq!(&expected[..], &buf[..]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_push_register64bit() {
|
||||
let arena = bumpalo::Bump::new();
|
||||
let mut buf = bumpalo::vec![in &arena];
|
||||
for (src, expected) in &[
|
||||
(X86_64GPReg::RAX, vec![0x50]),
|
||||
(X86_64GPReg::R15, vec![0x41, 0x57]),
|
||||
] {
|
||||
buf.clear();
|
||||
X86_64Assembler::push_register64bit(&mut buf, *src);
|
||||
assert_eq!(&expected[..], &buf[..]);
|
||||
}
|
||||
}
|
||||
}
|
388
compiler/gen_dev/src/lib.rs
Normal file
388
compiler/gen_dev/src/lib.rs
Normal file
@ -0,0 +1,388 @@
|
||||
#![warn(clippy::all, clippy::dbg_macro)]
|
||||
// I'm skeptical that clippy:large_enum_variant is a good lint to have globally enabled.
|
||||
//
|
||||
// It warns about a performance problem where the only quick remediation is
|
||||
// to allocate more on the heap, which has lots of tradeoffs - including making it
|
||||
// long-term unclear which allocations *need* to happen for compilation's sake
|
||||
// (e.g. recursive structures) versus those which were only added to appease clippy.
|
||||
//
|
||||
// Effectively optimizing data struture memory layout isn't a quick fix,
|
||||
// and encouraging shortcuts here creates bad incentives. I would rather temporarily
|
||||
// re-enable this when working on performance optimizations than have it block PRs.
|
||||
#![allow(clippy::large_enum_variant)]
|
||||
|
||||
use bumpalo::{collections::Vec, Bump};
|
||||
use roc_collections::all::{MutMap, MutSet};
|
||||
use roc_module::ident::TagName;
|
||||
use roc_module::low_level::LowLevel;
|
||||
use roc_module::symbol::{Interns, Symbol};
|
||||
use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt};
|
||||
use roc_mono::layout::{Builtin, Layout};
|
||||
use target_lexicon::Triple;
|
||||
|
||||
mod generic64;
|
||||
mod object_builder;
|
||||
pub use object_builder::build_module;
|
||||
mod run_roc;
|
||||
|
||||
pub struct Env<'a> {
|
||||
pub arena: &'a Bump,
|
||||
pub interns: Interns,
|
||||
pub exposed_to_host: MutSet<Symbol>,
|
||||
pub lazy_literals: bool,
|
||||
}
|
||||
|
||||
// INLINED_SYMBOLS is a set of all of the functions we automatically inline if seen.
|
||||
const INLINED_SYMBOLS: [Symbol; 2] = [Symbol::NUM_ABS, Symbol::NUM_ADD];
|
||||
|
||||
// These relocations likely will need a length.
|
||||
// They may even need more definition, but this should be at least good enough for how we will use elf.
|
||||
#[allow(dead_code)]
|
||||
enum Relocation<'a> {
|
||||
LocalData { offset: u64, data: &'a [u8] },
|
||||
LinkedFunction { offset: u64, name: &'a str },
|
||||
LinkedData { offset: u64, name: &'a str },
|
||||
}
|
||||
|
||||
trait Backend<'a>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
/// new creates a new backend that will output to the specific Object.
|
||||
fn new(env: &'a Env, target: &Triple) -> Result<Self, String>;
|
||||
|
||||
fn env(&self) -> &'a Env<'a>;
|
||||
|
||||
/// reset resets any registers or other values that may be occupied at the end of a procedure.
|
||||
fn reset(&mut self);
|
||||
|
||||
/// finalize does any setup and cleanup that should happen around the procedure.
|
||||
/// finalize does setup because things like stack size and jump locations are not know until the function is written.
|
||||
/// For example, this can store the frame pionter and setup stack space.
|
||||
/// finalize is run at the end of build_proc when all internal code is finalized.
|
||||
fn finalize(&mut self) -> Result<(&'a [u8], &[Relocation]), String>;
|
||||
|
||||
/// build_proc creates a procedure and outputs it to the wrapped object writer.
|
||||
fn build_proc(&mut self, proc: Proc<'a>) -> Result<(&'a [u8], &[Relocation]), String> {
|
||||
self.reset();
|
||||
// TODO: let the backend know of all the arguments.
|
||||
// let start = std::time::Instant::now();
|
||||
self.scan_ast(&proc.body);
|
||||
self.create_free_map();
|
||||
// let duration = start.elapsed();
|
||||
// println!("Time to calculate lifetimes: {:?}", duration);
|
||||
// println!("{:?}", self.last_seen_map());
|
||||
self.build_stmt(&proc.body)?;
|
||||
self.finalize()
|
||||
}
|
||||
|
||||
/// build_stmt builds a statement and outputs at the end of the buffer.
|
||||
fn build_stmt(&mut self, stmt: &Stmt<'a>) -> Result<(), String> {
|
||||
match stmt {
|
||||
Stmt::Let(sym, expr, layout, following) => {
|
||||
self.build_expr(sym, expr, layout)?;
|
||||
self.free_symbols(stmt);
|
||||
self.build_stmt(following)?;
|
||||
Ok(())
|
||||
}
|
||||
Stmt::Ret(sym) => {
|
||||
self.load_literal_symbols(&[*sym])?;
|
||||
self.return_symbol(sym)?;
|
||||
self.free_symbols(stmt);
|
||||
Ok(())
|
||||
}
|
||||
x => Err(format!("the statement, {:?}, is not yet implemented", x)),
|
||||
}
|
||||
}
|
||||
|
||||
/// build_expr builds the expressions for the specified symbol.
|
||||
/// The builder must keep track of the symbol because it may be refered to later.
|
||||
fn build_expr(
|
||||
&mut self,
|
||||
sym: &Symbol,
|
||||
expr: &Expr<'a>,
|
||||
layout: &Layout<'a>,
|
||||
) -> Result<(), String> {
|
||||
match expr {
|
||||
Expr::Literal(lit) => {
|
||||
if self.env().lazy_literals {
|
||||
self.literal_map().insert(*sym, lit.clone());
|
||||
} else {
|
||||
self.load_literal(sym, lit)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Expr::FunctionCall {
|
||||
call_type: CallType::ByName(func_sym),
|
||||
args,
|
||||
..
|
||||
} => {
|
||||
match *func_sym {
|
||||
Symbol::NUM_ABS => {
|
||||
// Instead of calling the function, just inline it.
|
||||
self.build_expr(sym, &Expr::RunLowLevel(LowLevel::NumAbs, args), layout)
|
||||
}
|
||||
Symbol::NUM_ADD => {
|
||||
// Instead of calling the function, just inline it.
|
||||
self.build_expr(sym, &Expr::RunLowLevel(LowLevel::NumAdd, args), layout)
|
||||
}
|
||||
x => Err(format!("the function, {:?}, is not yet implemented", x)),
|
||||
}
|
||||
}
|
||||
Expr::RunLowLevel(lowlevel, args) => {
|
||||
self.build_run_low_level(sym, lowlevel, args, layout)
|
||||
}
|
||||
x => Err(format!("the expression, {:?}, is not yet implemented", x)),
|
||||
}
|
||||
}
|
||||
|
||||
/// build_run_low_level builds the low level opertation and outputs to the specified symbol.
|
||||
/// The builder must keep track of the symbol because it may be refered to later.
|
||||
fn build_run_low_level(
|
||||
&mut self,
|
||||
sym: &Symbol,
|
||||
lowlevel: &LowLevel,
|
||||
args: &'a [Symbol],
|
||||
layout: &Layout<'a>,
|
||||
) -> Result<(), String> {
|
||||
// Now that the arguments are needed, load them if they are literals.
|
||||
self.load_literal_symbols(args)?;
|
||||
match lowlevel {
|
||||
LowLevel::NumAbs => {
|
||||
// TODO: when this is expanded to floats. deal with typecasting here, and then call correct low level method.
|
||||
match layout {
|
||||
Layout::Builtin(Builtin::Int64) => self.build_num_abs_i64(sym, &args[0]),
|
||||
x => Err(format!("layout, {:?}, not implemented yet", x)),
|
||||
}
|
||||
}
|
||||
LowLevel::NumAdd => {
|
||||
// TODO: when this is expanded to floats. deal with typecasting here, and then call correct low level method.
|
||||
match layout {
|
||||
Layout::Builtin(Builtin::Int64) => {
|
||||
self.build_num_add_i64(sym, &args[0], &args[1])
|
||||
}
|
||||
x => Err(format!("layout, {:?}, not implemented yet", x)),
|
||||
}
|
||||
}
|
||||
x => Err(format!("low level, {:?}. is not yet implemented", x)),
|
||||
}
|
||||
}
|
||||
|
||||
/// build_num_abs_i64 stores the absolute value of src into dst.
|
||||
/// It only deals with inputs and outputs of i64 type.
|
||||
fn build_num_abs_i64(&mut self, dst: &Symbol, src: &Symbol) -> Result<(), String>;
|
||||
|
||||
/// build_num_add_i64 stores the absolute value of src into dst.
|
||||
/// It only deals with inputs and outputs of i64 type.
|
||||
fn build_num_add_i64(
|
||||
&mut self,
|
||||
dst: &Symbol,
|
||||
src1: &Symbol,
|
||||
src2: &Symbol,
|
||||
) -> Result<(), String>;
|
||||
|
||||
/// literal_map gets the map from symbol to literal, used for lazy loading and literal folding.
|
||||
fn literal_map(&mut self) -> &mut MutMap<Symbol, Literal<'a>>;
|
||||
|
||||
fn load_literal_symbols(&mut self, syms: &[Symbol]) -> Result<(), String> {
|
||||
if self.env().lazy_literals {
|
||||
for sym in syms {
|
||||
if let Some(lit) = self.literal_map().remove(sym) {
|
||||
self.load_literal(sym, &lit)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// load_literal sets a symbol to be equal to a literal.
|
||||
fn load_literal(&mut self, sym: &Symbol, lit: &Literal<'a>) -> Result<(), String>;
|
||||
|
||||
/// return_symbol moves a symbol to the correct return location for the backend.
|
||||
fn return_symbol(&mut self, sym: &Symbol) -> Result<(), String>;
|
||||
|
||||
/// free_symbols will free all symbols for the given statement.
|
||||
fn free_symbols(&mut self, stmt: &Stmt<'a>) {
|
||||
if let Some(syms) = self.free_map().remove(&(stmt as *const Stmt<'a>)) {
|
||||
for sym in syms {
|
||||
//println!("Freeing symbol: {:?}", sym);
|
||||
self.free_symbol(&sym);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// free_symbol frees any registers or stack space used to hold a symbol.
|
||||
fn free_symbol(&mut self, sym: &Symbol);
|
||||
|
||||
/// set_last_seen sets the statement a symbol was last seen in.
|
||||
fn set_last_seen(&mut self, sym: Symbol, stmt: &Stmt<'a>) {
|
||||
self.last_seen_map().insert(sym, stmt);
|
||||
}
|
||||
|
||||
/// last_seen_map gets the map from symbol to when it is last seen in the function.
|
||||
fn last_seen_map(&mut self) -> &mut MutMap<Symbol, *const Stmt<'a>>;
|
||||
|
||||
fn create_free_map(&mut self) {
|
||||
let mut free_map = MutMap::default();
|
||||
let arena = self.env().arena;
|
||||
for (sym, stmt) in self.last_seen_map() {
|
||||
let vals = free_map
|
||||
.entry(*stmt)
|
||||
.or_insert_with(|| bumpalo::vec![in arena]);
|
||||
vals.push(*sym);
|
||||
}
|
||||
self.set_free_map(free_map);
|
||||
}
|
||||
|
||||
/// free_map gets the map statement to the symbols that are free after they run.
|
||||
fn free_map(&mut self) -> &mut MutMap<*const Stmt<'a>, Vec<'a, Symbol>>;
|
||||
|
||||
/// set_free_map sets the free map to the given map.
|
||||
fn set_free_map(&mut self, map: MutMap<*const Stmt<'a>, Vec<'a, Symbol>>);
|
||||
|
||||
/// set_not_leaf_function lets the backend know that it is not a leaf function.
|
||||
fn set_not_leaf_function(&mut self);
|
||||
|
||||
/// scan_ast runs through the ast and fill the last seen map.
|
||||
/// It also checks if the function is a leaf function or not.
|
||||
/// This must iterate through the ast in the same way that build_stmt does. i.e. then before else.
|
||||
fn scan_ast(&mut self, stmt: &Stmt<'a>) {
|
||||
match stmt {
|
||||
Stmt::Let(sym, expr, _, following) => {
|
||||
self.set_last_seen(*sym, stmt);
|
||||
match expr {
|
||||
Expr::Literal(_) => {}
|
||||
Expr::FunctionPointer(sym, _) => self.set_last_seen(*sym, stmt),
|
||||
Expr::FunctionCall {
|
||||
call_type, args, ..
|
||||
} => {
|
||||
for sym in *args {
|
||||
self.set_last_seen(*sym, stmt);
|
||||
}
|
||||
match call_type {
|
||||
CallType::ByName(sym) => {
|
||||
// For functions that we won't inline, we should not be a leaf function.
|
||||
if !INLINED_SYMBOLS.contains(sym) {
|
||||
self.set_not_leaf_function();
|
||||
}
|
||||
}
|
||||
CallType::ByPointer(sym) => {
|
||||
self.set_not_leaf_function();
|
||||
self.set_last_seen(*sym, stmt);
|
||||
}
|
||||
}
|
||||
}
|
||||
Expr::RunLowLevel(_, args) => {
|
||||
for sym in *args {
|
||||
self.set_last_seen(*sym, stmt);
|
||||
}
|
||||
}
|
||||
Expr::ForeignCall { arguments, .. } => {
|
||||
for sym in *arguments {
|
||||
self.set_last_seen(*sym, stmt);
|
||||
}
|
||||
self.set_not_leaf_function();
|
||||
}
|
||||
Expr::Tag { arguments, .. } => {
|
||||
for sym in *arguments {
|
||||
self.set_last_seen(*sym, stmt);
|
||||
}
|
||||
}
|
||||
Expr::Struct(syms) => {
|
||||
for sym in *syms {
|
||||
self.set_last_seen(*sym, stmt);
|
||||
}
|
||||
}
|
||||
Expr::AccessAtIndex { structure, .. } => {
|
||||
self.set_last_seen(*structure, stmt);
|
||||
}
|
||||
Expr::Array { elems, .. } => {
|
||||
for sym in *elems {
|
||||
self.set_last_seen(*sym, stmt);
|
||||
}
|
||||
}
|
||||
Expr::Reuse {
|
||||
symbol,
|
||||
arguments,
|
||||
tag_name,
|
||||
..
|
||||
} => {
|
||||
self.set_last_seen(*symbol, stmt);
|
||||
match tag_name {
|
||||
TagName::Closure(sym) => {
|
||||
self.set_last_seen(*sym, stmt);
|
||||
}
|
||||
TagName::Private(sym) => {
|
||||
self.set_last_seen(*sym, stmt);
|
||||
}
|
||||
TagName::Global(_) => {}
|
||||
}
|
||||
for sym in *arguments {
|
||||
self.set_last_seen(*sym, stmt);
|
||||
}
|
||||
}
|
||||
Expr::Reset(sym) => {
|
||||
self.set_last_seen(*sym, stmt);
|
||||
}
|
||||
Expr::EmptyArray => {}
|
||||
Expr::RuntimeErrorFunction(_) => {}
|
||||
}
|
||||
self.scan_ast(following);
|
||||
}
|
||||
Stmt::Switch {
|
||||
cond_symbol,
|
||||
branches,
|
||||
default_branch,
|
||||
..
|
||||
} => {
|
||||
self.set_last_seen(*cond_symbol, stmt);
|
||||
for (_, branch) in *branches {
|
||||
self.scan_ast(branch);
|
||||
}
|
||||
self.scan_ast(default_branch);
|
||||
}
|
||||
Stmt::Cond {
|
||||
cond_symbol,
|
||||
branching_symbol,
|
||||
pass,
|
||||
fail,
|
||||
..
|
||||
} => {
|
||||
self.set_last_seen(*cond_symbol, stmt);
|
||||
self.set_last_seen(*branching_symbol, stmt);
|
||||
self.scan_ast(pass);
|
||||
self.scan_ast(fail);
|
||||
}
|
||||
Stmt::Ret(sym) => {
|
||||
self.set_last_seen(*sym, stmt);
|
||||
}
|
||||
Stmt::Inc(sym, following) => {
|
||||
self.set_last_seen(*sym, stmt);
|
||||
self.scan_ast(following);
|
||||
}
|
||||
Stmt::Dec(sym, following) => {
|
||||
self.set_last_seen(*sym, stmt);
|
||||
self.scan_ast(following);
|
||||
}
|
||||
Stmt::Join {
|
||||
parameters,
|
||||
continuation,
|
||||
remainder,
|
||||
..
|
||||
} => {
|
||||
for param in *parameters {
|
||||
self.set_last_seen(param.symbol, stmt);
|
||||
}
|
||||
self.scan_ast(continuation);
|
||||
self.scan_ast(remainder);
|
||||
}
|
||||
Stmt::Jump(JoinPointId(sym), symbols) => {
|
||||
self.set_last_seen(*sym, stmt);
|
||||
for sym in *symbols {
|
||||
self.set_last_seen(*sym, stmt);
|
||||
}
|
||||
}
|
||||
Stmt::RuntimeError(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
154
compiler/gen_dev/src/object_builder.rs
Normal file
154
compiler/gen_dev/src/object_builder.rs
Normal file
@ -0,0 +1,154 @@
|
||||
use crate::generic64::{x86_64, Backend64Bit};
|
||||
use crate::{Backend, Env, Relocation, INLINED_SYMBOLS};
|
||||
use bumpalo::collections::Vec;
|
||||
use object::write;
|
||||
use object::write::{Object, StandardSection, Symbol, SymbolSection};
|
||||
use object::{
|
||||
Architecture, BinaryFormat, Endianness, RelocationEncoding, RelocationKind, SectionKind,
|
||||
SymbolFlags, SymbolKind, SymbolScope,
|
||||
};
|
||||
use roc_collections::all::MutMap;
|
||||
use roc_module::symbol;
|
||||
use roc_mono::ir::Proc;
|
||||
use roc_mono::layout::Layout;
|
||||
use target_lexicon::{Architecture as TargetArch, BinaryFormat as TargetBF, Triple};
|
||||
|
||||
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
/// build_module is the high level builder/delegator.
|
||||
/// It takes the request to build a module and output the object file for the module.
|
||||
pub fn build_module<'a>(
|
||||
env: &'a Env,
|
||||
target: &Triple,
|
||||
procedures: MutMap<(symbol::Symbol, Layout<'a>), Proc<'a>>,
|
||||
) -> Result<Object, String> {
|
||||
let (mut output, mut backend) = match target {
|
||||
Triple {
|
||||
architecture: TargetArch::X86_64,
|
||||
binary_format: TargetBF::Elf,
|
||||
..
|
||||
} => {
|
||||
let backend: Backend64Bit<
|
||||
x86_64::X86_64GPReg,
|
||||
x86_64::X86_64Assembler,
|
||||
x86_64::X86_64SystemV,
|
||||
> = Backend::new(env, target)?;
|
||||
Ok((
|
||||
Object::new(BinaryFormat::Elf, Architecture::X86_64, Endianness::Little),
|
||||
backend,
|
||||
))
|
||||
}
|
||||
x => Err(format! {
|
||||
"the target, {:?}, is not yet implemented",
|
||||
x}),
|
||||
}?;
|
||||
let text = output.section_id(StandardSection::Text);
|
||||
let data_section = output.section_id(StandardSection::Data);
|
||||
let comment = output.add_section(vec![], b"comment".to_vec(), SectionKind::OtherString);
|
||||
output.append_section_data(
|
||||
comment,
|
||||
format!("\0roc dev backend version {} \0", VERSION).as_bytes(),
|
||||
1,
|
||||
);
|
||||
|
||||
// Setup layout_ids for procedure calls.
|
||||
let mut layout_ids = roc_mono::layout::LayoutIds::default();
|
||||
let mut procs = Vec::with_capacity_in(procedures.len(), env.arena);
|
||||
for ((sym, layout), proc) in procedures {
|
||||
// This is temporary until we support passing args to functions.
|
||||
if INLINED_SYMBOLS.contains(&sym) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let fn_name = layout_ids
|
||||
.get(sym, &layout)
|
||||
.to_symbol_string(sym, &env.interns);
|
||||
|
||||
let proc_symbol = Symbol {
|
||||
name: fn_name.as_bytes().to_vec(),
|
||||
value: 0,
|
||||
size: 0,
|
||||
kind: SymbolKind::Text,
|
||||
// TODO: Depending on whether we are building a static or dynamic lib, this should change.
|
||||
// We should use Dynamic -> anyone, Linkage -> static link, Compilation -> this module only.
|
||||
scope: if env.exposed_to_host.contains(&sym) {
|
||||
SymbolScope::Dynamic
|
||||
} else {
|
||||
SymbolScope::Linkage
|
||||
},
|
||||
weak: false,
|
||||
section: SymbolSection::Section(text),
|
||||
flags: SymbolFlags::None,
|
||||
};
|
||||
let proc_id = output.add_symbol(proc_symbol);
|
||||
procs.push((fn_name, proc_id, proc));
|
||||
}
|
||||
|
||||
// Build procedures.
|
||||
for (fn_name, proc_id, proc) in procs {
|
||||
let mut local_data_index = 0;
|
||||
let (proc_data, relocations) = backend.build_proc(proc)?;
|
||||
let proc_offset = output.add_symbol_data(proc_id, text, proc_data, 16);
|
||||
for reloc in relocations {
|
||||
let elfreloc = match reloc {
|
||||
Relocation::LocalData { offset, data } => {
|
||||
let data_symbol = write::Symbol {
|
||||
name: format!("{}.data{}", fn_name, local_data_index)
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
value: 0,
|
||||
size: 0,
|
||||
kind: SymbolKind::Data,
|
||||
scope: SymbolScope::Compilation,
|
||||
weak: false,
|
||||
section: write::SymbolSection::Section(data_section),
|
||||
flags: SymbolFlags::None,
|
||||
};
|
||||
local_data_index += 1;
|
||||
let data_id = output.add_symbol(data_symbol);
|
||||
output.add_symbol_data(data_id, data_section, data, 4);
|
||||
write::Relocation {
|
||||
offset: offset + proc_offset,
|
||||
size: 32,
|
||||
kind: RelocationKind::Relative,
|
||||
encoding: RelocationEncoding::Generic,
|
||||
symbol: data_id,
|
||||
addend: -4,
|
||||
}
|
||||
}
|
||||
Relocation::LinkedData { offset, name } => {
|
||||
if let Some(sym_id) = output.symbol_id(name.as_bytes()) {
|
||||
write::Relocation {
|
||||
offset: offset + proc_offset,
|
||||
size: 32,
|
||||
kind: RelocationKind::GotRelative,
|
||||
encoding: RelocationEncoding::Generic,
|
||||
symbol: sym_id,
|
||||
addend: -4,
|
||||
}
|
||||
} else {
|
||||
return Err(format!("failed to find symbol for {:?}", name));
|
||||
}
|
||||
}
|
||||
Relocation::LinkedFunction { offset, name } => {
|
||||
if let Some(sym_id) = output.symbol_id(name.as_bytes()) {
|
||||
write::Relocation {
|
||||
offset: offset + proc_offset,
|
||||
size: 32,
|
||||
kind: RelocationKind::PltRelative,
|
||||
encoding: RelocationEncoding::Generic,
|
||||
symbol: sym_id,
|
||||
addend: -4,
|
||||
}
|
||||
} else {
|
||||
return Err(format!("failed to find symbol for {:?}", name));
|
||||
}
|
||||
}
|
||||
};
|
||||
output
|
||||
.add_relocation(text, elfreloc)
|
||||
.map_err(|e| format!("{:?}", e))?;
|
||||
}
|
||||
}
|
||||
Ok(output)
|
||||
}
|
31
compiler/gen_dev/src/run_roc.rs
Normal file
31
compiler/gen_dev/src/run_roc.rs
Normal file
@ -0,0 +1,31 @@
|
||||
#[macro_export]
|
||||
/// run_jit_function_raw runs an unwrapped jit function.
|
||||
/// The function could throw an exception and break things, or worse, it could not throw an exception and break things.
|
||||
/// This functions is generally a bad idea with an untrused backend, but is being used for now for development purposes.
|
||||
macro_rules! run_jit_function_raw {
|
||||
($lib: expr, $main_fn_name: expr, $ty:ty, $transform:expr) => {{
|
||||
let v: std::vec::Vec<roc_problem::can::Problem> = std::vec::Vec::new();
|
||||
run_jit_function_raw!($lib, $main_fn_name, $ty, $transform, v)
|
||||
}};
|
||||
|
||||
($lib: expr, $main_fn_name: expr, $ty:ty, $transform:expr, $errors:expr) => {{
|
||||
unsafe {
|
||||
let main: libloading::Symbol<unsafe extern "C" fn() -> $ty> = $lib
|
||||
.get($main_fn_name.as_bytes())
|
||||
.ok()
|
||||
.ok_or(format!("Unable to JIT compile `{}`", $main_fn_name))
|
||||
.expect("errored");
|
||||
|
||||
let result = main();
|
||||
|
||||
assert_eq!(
|
||||
$errors,
|
||||
std::vec::Vec::new(),
|
||||
"Encountered errors: {:?}",
|
||||
$errors
|
||||
);
|
||||
|
||||
$transform(result)
|
||||
}
|
||||
}};
|
||||
}
|
802
compiler/gen_dev/tests/gen_num.rs
Normal file
802
compiler/gen_dev/tests/gen_num.rs
Normal file
@ -0,0 +1,802 @@
|
||||
#[macro_use]
|
||||
extern crate pretty_assertions;
|
||||
#[macro_use]
|
||||
extern crate indoc;
|
||||
|
||||
extern crate bumpalo;
|
||||
extern crate libc;
|
||||
|
||||
#[macro_use]
|
||||
mod helpers;
|
||||
|
||||
#[cfg(all(test, target_os = "linux", target_arch = "x86_64"))]
|
||||
mod gen_num {
|
||||
//use roc_std::RocOrder;
|
||||
|
||||
#[test]
|
||||
fn i64_values() {
|
||||
assert_evals_to!("0", 0, i64);
|
||||
assert_evals_to!("-0", 0, i64);
|
||||
assert_evals_to!("-1", -1, i64);
|
||||
assert_evals_to!("1", 1, i64);
|
||||
assert_evals_to!("9_000_000_000_000", 9_000_000_000_000, i64);
|
||||
assert_evals_to!("-9_000_000_000_000", -9_000_000_000_000, i64);
|
||||
assert_evals_to!("0b1010", 0b1010, i64);
|
||||
assert_evals_to!("0o17", 0o17, i64);
|
||||
assert_evals_to!("0x1000_0000_0000_0000", 0x1000_0000_0000_0000, i64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_add_i64() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
1 + 2 + 3
|
||||
"#
|
||||
),
|
||||
6,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn i64_force_stack() {
|
||||
// This claims 33 registers. One more than Arm and RISC-V, and many more than x86-64.
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
a = 0
|
||||
b = 1
|
||||
c = 2
|
||||
d = 3
|
||||
e = 4
|
||||
f = 5
|
||||
g = 6
|
||||
h = 7
|
||||
i = 8
|
||||
j = 9
|
||||
k = 10
|
||||
l = 11
|
||||
m = 12
|
||||
n = 13
|
||||
o = 14
|
||||
p = 15
|
||||
q = 16
|
||||
r = 17
|
||||
s = 18
|
||||
t = 19
|
||||
u = 20
|
||||
v = 21
|
||||
w = 22
|
||||
x = 23
|
||||
y = 24
|
||||
z = 25
|
||||
aa = 26
|
||||
ab = 27
|
||||
ac = 28
|
||||
ad = 29
|
||||
ae = 30
|
||||
af = 31
|
||||
ag = 32
|
||||
|
||||
# This can't be one line because it causes a stack overflow in the frontend :(
|
||||
tmp = a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q
|
||||
tmp + r + s + t + u + v + w + x + y + z + aa + ab + ac + ad + ae + af + ag
|
||||
"#
|
||||
),
|
||||
528,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn i64_abs() {
|
||||
assert_evals_to!("Num.abs -6", 6, i64);
|
||||
assert_evals_to!("Num.abs 7", 7, i64);
|
||||
assert_evals_to!("Num.abs 0", 0, i64);
|
||||
assert_evals_to!("Num.abs -0", 0, i64);
|
||||
assert_evals_to!("Num.abs -1", 1, i64);
|
||||
assert_evals_to!("Num.abs 1", 1, i64);
|
||||
assert_evals_to!("Num.abs 9_000_000_000_000", 9_000_000_000_000, i64);
|
||||
assert_evals_to!("Num.abs -9_000_000_000_000", 9_000_000_000_000, i64);
|
||||
}
|
||||
|
||||
/*
|
||||
#[test]
|
||||
fn f64_sqrt() {
|
||||
// FIXME this works with normal types, but fails when checking uniqueness types
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when Num.sqrt 100 is
|
||||
Ok val -> val
|
||||
Err _ -> -1
|
||||
"#
|
||||
),
|
||||
10.0,
|
||||
f64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn f64_round_old() {
|
||||
assert_evals_to!("Num.round 3.6", 4, i64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn f64_abs() {
|
||||
assert_evals_to!("Num.abs -4.7", 4.7, f64);
|
||||
assert_evals_to!("Num.abs 5.8", 5.8, f64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_if_fn() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
limitedNegate = \num ->
|
||||
x =
|
||||
if num == 1 then
|
||||
-1
|
||||
else if num == -1 then
|
||||
1
|
||||
else
|
||||
num
|
||||
x
|
||||
|
||||
limitedNegate 1
|
||||
"#
|
||||
),
|
||||
-1,
|
||||
i64
|
||||
);
|
||||
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
limitedNegate = \num ->
|
||||
if num == 1 then
|
||||
-1
|
||||
else if num == -1 then
|
||||
1
|
||||
else
|
||||
num
|
||||
|
||||
limitedNegate 1
|
||||
"#
|
||||
),
|
||||
-1,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_float_eq() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
1.0 == 1.0
|
||||
"#
|
||||
),
|
||||
true,
|
||||
bool
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_add_f64() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
1.1 + 2.4 + 3
|
||||
"#
|
||||
),
|
||||
6.5,
|
||||
f64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_wrap_add_nums() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
add2 = \num1, num2 -> num1 + num2
|
||||
|
||||
add2 4 5
|
||||
"#
|
||||
),
|
||||
9,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_div_f64() {
|
||||
// FIXME this works with normal types, but fails when checking uniqueness types
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when 48 / 2 is
|
||||
Ok val -> val
|
||||
Err _ -> -1
|
||||
"#
|
||||
),
|
||||
24.0,
|
||||
f64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_int_eq() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
4 == 4
|
||||
"#
|
||||
),
|
||||
true,
|
||||
bool
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_int_neq() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
4 != 5
|
||||
"#
|
||||
),
|
||||
true,
|
||||
bool
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_wrap_int_neq() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
wrappedNotEq : a, a -> Bool
|
||||
wrappedNotEq = \num1, num2 ->
|
||||
num1 != num2
|
||||
|
||||
wrappedNotEq 2 3
|
||||
"#
|
||||
),
|
||||
true,
|
||||
bool
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_sub_f64() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
1.5 - 2.4 - 3
|
||||
"#
|
||||
),
|
||||
-3.9,
|
||||
f64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_sub_i64() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
1 - 2 - 3
|
||||
"#
|
||||
),
|
||||
-4,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_mul_i64() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
2 * 4 * 6
|
||||
"#
|
||||
),
|
||||
48,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_div_i64() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when 1000 // 10 is
|
||||
Ok val -> val
|
||||
Err _ -> -1
|
||||
"#
|
||||
),
|
||||
100,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_div_by_zero_i64() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when 1000 // 0 is
|
||||
Err DivByZero -> 99
|
||||
_ -> -24
|
||||
"#
|
||||
),
|
||||
99,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_rem_i64() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when Num.rem 8 3 is
|
||||
Ok val -> val
|
||||
Err _ -> -1
|
||||
"#
|
||||
),
|
||||
2,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_rem_div_by_zero_i64() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when Num.rem 8 0 is
|
||||
Err DivByZero -> 4
|
||||
Ok _ -> -23
|
||||
"#
|
||||
),
|
||||
4,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_is_zero_i64() {
|
||||
assert_evals_to!("Num.isZero 0", true, bool);
|
||||
assert_evals_to!("Num.isZero 1", false, bool);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_is_positive_i64() {
|
||||
assert_evals_to!("Num.isPositive 0", false, bool);
|
||||
assert_evals_to!("Num.isPositive 1", true, bool);
|
||||
assert_evals_to!("Num.isPositive -5", false, bool);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_is_negative_i64() {
|
||||
assert_evals_to!("Num.isNegative 0", false, bool);
|
||||
assert_evals_to!("Num.isNegative 3", false, bool);
|
||||
assert_evals_to!("Num.isNegative -2", true, bool);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_is_positive_f64() {
|
||||
assert_evals_to!("Num.isPositive 0.0", false, bool);
|
||||
assert_evals_to!("Num.isPositive 4.7", true, bool);
|
||||
assert_evals_to!("Num.isPositive -8.5", false, bool);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_is_negative_f64() {
|
||||
assert_evals_to!("Num.isNegative 0.0", false, bool);
|
||||
assert_evals_to!("Num.isNegative 9.9", false, bool);
|
||||
assert_evals_to!("Num.isNegative -4.4", true, bool);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_is_zero_f64() {
|
||||
assert_evals_to!("Num.isZero 0", true, bool);
|
||||
assert_evals_to!("Num.isZero 0_0", true, bool);
|
||||
assert_evals_to!("Num.isZero 0.0", true, bool);
|
||||
assert_evals_to!("Num.isZero 1", false, bool);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_is_odd() {
|
||||
assert_evals_to!("Num.isOdd 4", false, bool);
|
||||
assert_evals_to!("Num.isOdd 5", true, bool);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_is_even() {
|
||||
assert_evals_to!("Num.isEven 6", true, bool);
|
||||
assert_evals_to!("Num.isEven 7", false, bool);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sin() {
|
||||
assert_evals_to!("Num.sin 0", 0.0, f64);
|
||||
assert_evals_to!("Num.sin 1.41421356237", 0.9877659459922529, f64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cos() {
|
||||
assert_evals_to!("Num.cos 0", 1.0, f64);
|
||||
assert_evals_to!("Num.cos 3.14159265359", -1.0, f64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tan() {
|
||||
assert_evals_to!("Num.tan 0", 0.0, f64);
|
||||
assert_evals_to!("Num.tan 1", 1.557407724654902, f64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lt_i64() {
|
||||
assert_evals_to!("1 < 2", true, bool);
|
||||
assert_evals_to!("1 < 1", false, bool);
|
||||
assert_evals_to!("2 < 1", false, bool);
|
||||
assert_evals_to!("0 < 0", false, bool);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lte_i64() {
|
||||
assert_evals_to!("1 <= 1", true, bool);
|
||||
assert_evals_to!("2 <= 1", false, bool);
|
||||
assert_evals_to!("1 <= 2", true, bool);
|
||||
assert_evals_to!("0 <= 0", true, bool);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gt_i64() {
|
||||
assert_evals_to!("2 > 1", true, bool);
|
||||
assert_evals_to!("2 > 2", false, bool);
|
||||
assert_evals_to!("1 > 1", false, bool);
|
||||
assert_evals_to!("0 > 0", false, bool);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gte_i64() {
|
||||
assert_evals_to!("1 >= 1", true, bool);
|
||||
assert_evals_to!("1 >= 2", false, bool);
|
||||
assert_evals_to!("2 >= 1", true, bool);
|
||||
assert_evals_to!("0 >= 0", true, bool);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lt_f64() {
|
||||
assert_evals_to!("1.1 < 1.2", true, bool);
|
||||
assert_evals_to!("1.1 < 1.1", false, bool);
|
||||
assert_evals_to!("1.2 < 1.1", false, bool);
|
||||
assert_evals_to!("0.0 < 0.0", false, bool);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lte_f64() {
|
||||
assert_evals_to!("1.1 <= 1.1", true, bool);
|
||||
assert_evals_to!("1.2 <= 1.1", false, bool);
|
||||
assert_evals_to!("1.1 <= 1.2", true, bool);
|
||||
assert_evals_to!("0.0 <= 0.0", true, bool);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gt_f64() {
|
||||
assert_evals_to!("2.2 > 1.1", true, bool);
|
||||
assert_evals_to!("2.2 > 2.2", false, bool);
|
||||
assert_evals_to!("1.1 > 2.2", false, bool);
|
||||
assert_evals_to!("0.0 > 0.0", false, bool);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gte_f64() {
|
||||
assert_evals_to!("1.1 >= 1.1", true, bool);
|
||||
assert_evals_to!("1.1 >= 1.2", false, bool);
|
||||
assert_evals_to!("1.2 >= 1.1", true, bool);
|
||||
assert_evals_to!("0.0 >= 0.0", true, bool);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_order_of_arithmetic_ops() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
1 + 3 * 7 - 2
|
||||
"#
|
||||
),
|
||||
20,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_order_of_arithmetic_ops_complex_float() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
3 - 48 * 2.0
|
||||
"#
|
||||
),
|
||||
-93.0,
|
||||
f64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn if_guard_bind_variable_false() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
wrapper = \{} ->
|
||||
when 10 is
|
||||
x if x == 5 -> 0
|
||||
_ -> 42
|
||||
|
||||
wrapper {}
|
||||
"#
|
||||
),
|
||||
42,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn if_guard_bind_variable_true() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
wrapper = \{} ->
|
||||
when 10 is
|
||||
x if x == 10 -> 42
|
||||
_ -> 0
|
||||
|
||||
wrapper {}
|
||||
"#
|
||||
),
|
||||
42,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tail_call_elimination() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
sum = \n, accum ->
|
||||
when n is
|
||||
0 -> accum
|
||||
_ -> sum (n - 1) (n + accum)
|
||||
|
||||
sum 1_000_000 0
|
||||
"#
|
||||
),
|
||||
500000500000,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn int_negate() {
|
||||
assert_evals_to!("Num.neg 123", -123, i64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_wrap_int_neg() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
wrappedNeg = \num -> -num
|
||||
|
||||
wrappedNeg 3
|
||||
"#
|
||||
),
|
||||
-3,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_basic_fn() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
always42 : Num.Num Num.Integer -> Num.Num Num.Integer
|
||||
always42 = \_ -> 42
|
||||
|
||||
always42 5
|
||||
"#
|
||||
),
|
||||
42,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn int_to_float() {
|
||||
assert_evals_to!("Num.toFloat 0x9", 9.0, f64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn num_to_float() {
|
||||
assert_evals_to!("Num.toFloat 9", 9.0, f64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn float_to_float() {
|
||||
assert_evals_to!("Num.toFloat 0.5", 0.5, f64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn int_compare() {
|
||||
assert_evals_to!("Num.compare 0 1", RocOrder::Lt, RocOrder);
|
||||
assert_evals_to!("Num.compare 1 1", RocOrder::Eq, RocOrder);
|
||||
assert_evals_to!("Num.compare 1 0", RocOrder::Gt, RocOrder);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn float_compare() {
|
||||
assert_evals_to!("Num.compare 0.01 3.14", RocOrder::Lt, RocOrder);
|
||||
assert_evals_to!("Num.compare 3.14 3.14", RocOrder::Eq, RocOrder);
|
||||
assert_evals_to!("Num.compare 3.14 0.01", RocOrder::Gt, RocOrder);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pow() {
|
||||
assert_evals_to!("Num.pow 2.0 2.0", 4.0, f64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ceiling() {
|
||||
assert_evals_to!("Num.ceiling 1.1", 2, i64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn floor() {
|
||||
assert_evals_to!("Num.floor 1.9", 1, i64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pow_int() {
|
||||
assert_evals_to!("Num.powInt 2 3", 8, i64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn atan() {
|
||||
assert_evals_to!("Num.atan 10", 1.4711276743037347, f64);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// #[should_panic(expected = r#"Roc failed with message: "integer addition overflowed!"#)]
|
||||
// fn int_overflow() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// 9_223_372_036_854_775_807 + 1
|
||||
// "#
|
||||
// ),
|
||||
// 0,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn int_add_checked() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when Num.addChecked 1 2 is
|
||||
Ok v -> v
|
||||
_ -> -1
|
||||
"#
|
||||
),
|
||||
3,
|
||||
i64
|
||||
);
|
||||
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when Num.addChecked 9_223_372_036_854_775_807 1 is
|
||||
Err Overflow -> -1
|
||||
Ok v -> v
|
||||
"#
|
||||
),
|
||||
-1,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn int_add_wrap() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
Num.addWrap 9_223_372_036_854_775_807 1
|
||||
"#
|
||||
),
|
||||
std::i64::MIN,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn float_add_checked_pass() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when Num.addChecked 1.0 0.0 is
|
||||
Ok v -> v
|
||||
Err Overflow -> -1.0
|
||||
"#
|
||||
),
|
||||
1.0,
|
||||
f64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn float_add_checked_fail() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when Num.addChecked 1.7976931348623157e308 1.7976931348623157e308 is
|
||||
Err Overflow -> -1
|
||||
Ok v -> v
|
||||
"#
|
||||
),
|
||||
-1.0,
|
||||
f64
|
||||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// #[should_panic(expected = r#"Roc failed with message: "float addition overflowed!"#)]
|
||||
// fn float_overflow() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// 1.7976931348623157e308 + 1.7976931348623157e308
|
||||
// "#
|
||||
// ),
|
||||
// 0.0,
|
||||
// f64
|
||||
// );
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn num_max_int() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
Num.maxInt
|
||||
"#
|
||||
),
|
||||
i64::MAX,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn num_min_int() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
Num.minInt
|
||||
"#
|
||||
),
|
||||
i64::MIN,
|
||||
i64
|
||||
);
|
||||
}
|
||||
*/
|
||||
}
|
233
compiler/gen_dev/tests/helpers/eval.rs
Normal file
233
compiler/gen_dev/tests/helpers/eval.rs
Normal file
@ -0,0 +1,233 @@
|
||||
use libloading::Library;
|
||||
use roc_build::link::{link, LinkType};
|
||||
use roc_collections::all::MutMap;
|
||||
use tempfile::tempdir;
|
||||
|
||||
fn promote_expr_to_module(src: &str) -> String {
|
||||
let mut buffer = String::from("app \"test\" provides [ main ] to \"./platform\"\n\nmain =\n");
|
||||
|
||||
for line in src.lines() {
|
||||
// indent the body!
|
||||
buffer.push_str(" ");
|
||||
buffer.push_str(line);
|
||||
buffer.push('\n');
|
||||
}
|
||||
|
||||
buffer
|
||||
}
|
||||
|
||||
pub fn helper<'a>(
|
||||
arena: &'a bumpalo::Bump,
|
||||
src: &str,
|
||||
stdlib: roc_builtins::std::StdLib,
|
||||
_leak: bool,
|
||||
lazy_literals: bool,
|
||||
) -> (String, Vec<roc_problem::can::Problem>, Library) {
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
//let stdlib_mode = stdlib.mode;
|
||||
let dir = tempdir().unwrap();
|
||||
let filename = PathBuf::from("Test.roc");
|
||||
let src_dir = Path::new("fake/test/path");
|
||||
let app_o_file = dir.path().join("app.o");
|
||||
|
||||
let module_src;
|
||||
let temp;
|
||||
if src.starts_with("app") {
|
||||
// this is already a module
|
||||
module_src = src;
|
||||
} else {
|
||||
// this is an expression, promote it to a module
|
||||
temp = promote_expr_to_module(src);
|
||||
module_src = &temp;
|
||||
}
|
||||
|
||||
let exposed_types = MutMap::default();
|
||||
let loaded = roc_load::file::load_and_monomorphize_from_str(
|
||||
arena,
|
||||
filename,
|
||||
&module_src,
|
||||
stdlib,
|
||||
src_dir,
|
||||
exposed_types,
|
||||
);
|
||||
|
||||
let mut loaded = loaded.expect("failed to load module");
|
||||
|
||||
use roc_load::file::MonomorphizedModule;
|
||||
let MonomorphizedModule {
|
||||
procedures,
|
||||
interns,
|
||||
exposed_to_host,
|
||||
..
|
||||
} = loaded;
|
||||
|
||||
/*
|
||||
println!("=========== Procedures ==========");
|
||||
println!("{:?}", procedures);
|
||||
println!("=================================\n");
|
||||
|
||||
println!("=========== Interns ==========");
|
||||
println!("{:?}", interns);
|
||||
println!("=================================\n");
|
||||
|
||||
println!("=========== Exposed ==========");
|
||||
println!("{:?}", exposed_to_host);
|
||||
println!("=================================\n");
|
||||
*/
|
||||
|
||||
debug_assert_eq!(exposed_to_host.len(), 1);
|
||||
let main_fn_symbol = exposed_to_host.keys().copied().nth(0).unwrap();
|
||||
|
||||
let (_, main_fn_layout) = procedures
|
||||
.keys()
|
||||
.find(|(s, _)| *s == main_fn_symbol)
|
||||
.unwrap()
|
||||
.clone();
|
||||
let mut layout_ids = roc_mono::layout::LayoutIds::default();
|
||||
let main_fn_name = layout_ids
|
||||
.get(main_fn_symbol, &main_fn_layout)
|
||||
.to_symbol_string(main_fn_symbol, &interns);
|
||||
|
||||
let mut lines = Vec::new();
|
||||
// errors whose reporting we delay (so we can see that code gen generates runtime errors)
|
||||
let mut delayed_errors = Vec::new();
|
||||
|
||||
for (home, (module_path, src)) in loaded.sources {
|
||||
use roc_reporting::report::{
|
||||
can_problem, mono_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE,
|
||||
};
|
||||
|
||||
let can_problems = loaded.can_problems.remove(&home).unwrap_or_default();
|
||||
let type_problems = loaded.type_problems.remove(&home).unwrap_or_default();
|
||||
let mono_problems = loaded.mono_problems.remove(&home).unwrap_or_default();
|
||||
|
||||
let error_count = can_problems.len() + type_problems.len() + mono_problems.len();
|
||||
|
||||
if error_count == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let src_lines: Vec<&str> = src.split('\n').collect();
|
||||
let palette = DEFAULT_PALETTE;
|
||||
|
||||
// Report parsing and canonicalization problems
|
||||
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
|
||||
|
||||
use roc_problem::can::Problem::*;
|
||||
for problem in can_problems.into_iter() {
|
||||
// Ignore "unused" problems
|
||||
match problem {
|
||||
UnusedDef(_, _) | UnusedArgument(_, _, _) | UnusedImport(_, _) => {
|
||||
delayed_errors.push(problem);
|
||||
continue;
|
||||
}
|
||||
_ => {
|
||||
let report = can_problem(&alloc, module_path.clone(), problem);
|
||||
let mut buf = String::new();
|
||||
|
||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||
|
||||
lines.push(buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for problem in type_problems {
|
||||
let report = type_problem(&alloc, module_path.clone(), problem);
|
||||
let mut buf = String::new();
|
||||
|
||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||
|
||||
lines.push(buf);
|
||||
}
|
||||
|
||||
for problem in mono_problems {
|
||||
let report = mono_problem(&alloc, module_path.clone(), problem);
|
||||
let mut buf = String::new();
|
||||
|
||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||
|
||||
lines.push(buf);
|
||||
}
|
||||
}
|
||||
|
||||
if !lines.is_empty() {
|
||||
println!("{}", lines.join("\n"));
|
||||
assert_eq!(0, 1, "Mistakes were made");
|
||||
}
|
||||
|
||||
let env = roc_gen_dev::Env {
|
||||
arena,
|
||||
interns,
|
||||
exposed_to_host: exposed_to_host.keys().copied().collect(),
|
||||
lazy_literals,
|
||||
};
|
||||
|
||||
let target = target_lexicon::Triple::host();
|
||||
let module_object =
|
||||
roc_gen_dev::build_module(&env, &target, procedures).expect("failed to compile module");
|
||||
|
||||
let module_out = module_object
|
||||
.write()
|
||||
.expect("failed to build output object");
|
||||
std::fs::write(&app_o_file, module_out).expect("failed to write object to file");
|
||||
|
||||
let (mut child, dylib_path) = link(
|
||||
&target,
|
||||
app_o_file.clone(),
|
||||
&[app_o_file.to_str().unwrap()],
|
||||
LinkType::Dylib,
|
||||
)
|
||||
.expect("failed to link dynamic library");
|
||||
|
||||
child.wait().unwrap();
|
||||
|
||||
// Load the dylib
|
||||
let path = dylib_path.as_path().to_str().unwrap();
|
||||
|
||||
// std::fs::copy(&app_o_file, "/tmp/app.o").unwrap();
|
||||
// std::fs::copy(&path, "/tmp/libapp.so").unwrap();
|
||||
|
||||
let lib = Library::new(path).expect("failed to load shared library");
|
||||
|
||||
(main_fn_name, delayed_errors, lib)
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! assert_evals_to {
|
||||
($src:expr, $expected:expr, $ty:ty) => {{
|
||||
assert_evals_to!($src, $expected, $ty, (|val| val));
|
||||
}};
|
||||
($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
|
||||
// Same as above, except with an additional transformation argument.
|
||||
{
|
||||
assert_evals_to!($src, $expected, $ty, $transform, true);
|
||||
}
|
||||
};
|
||||
($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr) => {
|
||||
// Run both with and without lazy literal optimization.
|
||||
{
|
||||
assert_evals_to!($src, $expected, $ty, $transform, $leak, false);
|
||||
}
|
||||
{
|
||||
assert_evals_to!($src, $expected, $ty, $transform, $leak, true);
|
||||
}
|
||||
};
|
||||
($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr, $lazy_literals:expr) => {
|
||||
use bumpalo::Bump;
|
||||
use roc_gen_dev::run_jit_function_raw;
|
||||
let stdlib = roc_builtins::std::standard_stdlib();
|
||||
|
||||
let arena = Bump::new();
|
||||
let (main_fn_name, errors, lib) =
|
||||
$crate::helpers::eval::helper(&arena, $src, stdlib, $leak, $lazy_literals);
|
||||
|
||||
let transform = |success| {
|
||||
let expected = $expected;
|
||||
let given = $transform(success);
|
||||
assert_eq!(&given, &expected);
|
||||
};
|
||||
run_jit_function_raw!(lib, main_fn_name, $ty, transform, errors)
|
||||
};
|
||||
}
|
44
compiler/gen_dev/tests/helpers/mod.rs
Normal file
44
compiler/gen_dev/tests/helpers/mod.rs
Normal file
@ -0,0 +1,44 @@
|
||||
extern crate bumpalo;
|
||||
|
||||
#[macro_use]
|
||||
pub mod eval;
|
||||
|
||||
/// Used in the with_larger_debug_stack() function, for tests that otherwise
|
||||
/// run out of stack space in debug builds (but don't in --release builds)
|
||||
#[allow(dead_code)]
|
||||
const EXPANDED_STACK_SIZE: usize = 8 * 1024 * 1024;
|
||||
|
||||
/// Without this, some tests pass in `cargo test --release` but fail without
|
||||
/// the --release flag because they run out of stack space. This increases
|
||||
/// stack size for debug builds only, while leaving the stack space at the default
|
||||
/// amount for release builds.
|
||||
#[allow(dead_code)]
|
||||
#[cfg(debug_assertions)]
|
||||
pub fn with_larger_debug_stack<F>(run_test: F)
|
||||
where
|
||||
F: FnOnce() -> (),
|
||||
F: Send,
|
||||
F: 'static,
|
||||
{
|
||||
std::thread::Builder::new()
|
||||
.stack_size(EXPANDED_STACK_SIZE)
|
||||
.spawn(run_test)
|
||||
.expect("Error while spawning expanded dev stack size thread")
|
||||
.join()
|
||||
.expect("Error while joining expanded dev stack size thread")
|
||||
}
|
||||
|
||||
/// In --release builds, don't increase the stack size. Run the test normally.
|
||||
/// This way, we find out if any of our tests are blowing the stack even after
|
||||
/// optimizations in release builds.
|
||||
#[allow(dead_code)]
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[inline(always)]
|
||||
pub fn with_larger_debug_stack<F>(run_test: F)
|
||||
where
|
||||
F: FnOnce() -> (),
|
||||
F: Send,
|
||||
F: 'static,
|
||||
{
|
||||
run_test()
|
||||
}
|
@ -19,9 +19,8 @@ use roc_mono::ir::{
|
||||
CapturedSymbols, ExternalSpecializations, PartialProc, PendingSpecialization, Proc, Procs,
|
||||
};
|
||||
use roc_mono::layout::{Layout, LayoutCache};
|
||||
use roc_parse::ast::{
|
||||
self, Attempting, ExposesEntry, ImportsEntry, PlatformHeader, TypeAnnotation, TypedIdent,
|
||||
};
|
||||
use roc_parse::ast::{self, Attempting, StrLiteral, TypeAnnotation};
|
||||
use roc_parse::header::{ExposesEntry, ImportsEntry, PlatformHeader, TypedIdent};
|
||||
use roc_parse::module::module_defs;
|
||||
use roc_parse::parser::{self, Fail, Parser};
|
||||
use roc_region::all::{Located, Region};
|
||||
@ -40,6 +39,9 @@ use std::str::from_utf8_unchecked;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
/// Default name for the binary generated for an app, if an invalid one was specified.
|
||||
const DEFAULT_APP_OUTPUT_PATH: &str = "app";
|
||||
|
||||
/// Filename extension for normal Roc modules
|
||||
const ROC_FILE_EXTENSION: &str = "roc";
|
||||
|
||||
@ -534,7 +536,7 @@ pub enum BuildProblem<'a> {
|
||||
#[derive(Debug)]
|
||||
struct ModuleHeader<'a> {
|
||||
module_id: ModuleId,
|
||||
module_name: ModuleName,
|
||||
module_name: AppOrInterfaceName<'a>,
|
||||
module_path: PathBuf,
|
||||
exposed_ident_ids: IdentIds,
|
||||
deps_by_name: MutMap<ModuleName, ModuleId>,
|
||||
@ -581,6 +583,7 @@ pub struct MonomorphizedModule<'a> {
|
||||
pub module_id: ModuleId,
|
||||
pub interns: Interns,
|
||||
pub subs: Subs,
|
||||
pub output_path: Box<str>,
|
||||
pub can_problems: MutMap<ModuleId, Vec<roc_problem::can::Problem>>,
|
||||
pub type_problems: MutMap<ModuleId, Vec<solve::TypeError>>,
|
||||
pub mono_problems: MutMap<ModuleId, Vec<roc_mono::ir::MonoProblem>>,
|
||||
@ -599,7 +602,7 @@ pub struct VariablySizedLayouts<'a> {
|
||||
#[derive(Debug)]
|
||||
struct ParsedModule<'a> {
|
||||
module_id: ModuleId,
|
||||
module_name: ModuleName,
|
||||
module_name: AppOrInterfaceName<'a>,
|
||||
module_path: PathBuf,
|
||||
src: &'a str,
|
||||
module_timing: ModuleTiming,
|
||||
@ -618,7 +621,7 @@ enum Msg<'a> {
|
||||
CanonicalizedAndConstrained {
|
||||
constrained_module: ConstrainedModule,
|
||||
canonicalization_problems: Vec<roc_problem::can::Problem>,
|
||||
module_docs: ModuleDocumentation,
|
||||
module_docs: Option<ModuleDocumentation>,
|
||||
},
|
||||
MadeEffectModule {
|
||||
constrained_module: ConstrainedModule,
|
||||
@ -672,6 +675,7 @@ struct State<'a> {
|
||||
pub goal_phase: Phase,
|
||||
pub stdlib: StdLib,
|
||||
pub exposed_types: SubsByModule,
|
||||
pub output_path: Option<&'a str>,
|
||||
|
||||
pub headers_parsed: MutSet<ModuleId>,
|
||||
|
||||
@ -1243,6 +1247,7 @@ where
|
||||
root_id,
|
||||
goal_phase,
|
||||
stdlib,
|
||||
output_path: None,
|
||||
module_cache: ModuleCache::default(),
|
||||
dependencies: Dependencies::default(),
|
||||
procedures: MutMap::default(),
|
||||
@ -1427,6 +1432,22 @@ fn update<'a>(
|
||||
.sources
|
||||
.insert(parsed.module_id, (parsed.module_path.clone(), parsed.src));
|
||||
|
||||
// If this was an app module, set the output path to be
|
||||
// the module's declared "name".
|
||||
//
|
||||
// e.g. for `app "blah"` we should generate an output file named "blah"
|
||||
match &parsed.module_name {
|
||||
AppOrInterfaceName::App(output_str) => match output_str {
|
||||
StrLiteral::PlainLine(path) => {
|
||||
state.output_path = Some(path);
|
||||
}
|
||||
_ => {
|
||||
todo!("TODO gracefully handle a malformed string literal after `app` keyword.");
|
||||
}
|
||||
},
|
||||
AppOrInterfaceName::Interface(_) => {}
|
||||
}
|
||||
|
||||
let module_id = parsed.module_id;
|
||||
|
||||
state.module_cache.parsed.insert(parsed.module_id, parsed);
|
||||
@ -1450,10 +1471,9 @@ fn update<'a>(
|
||||
.can_problems
|
||||
.insert(module_id, canonicalization_problems);
|
||||
|
||||
state
|
||||
.module_cache
|
||||
.documentation
|
||||
.insert(module_id, module_docs);
|
||||
if let Some(docs) = module_docs {
|
||||
state.module_cache.documentation.insert(module_id, docs);
|
||||
}
|
||||
|
||||
state
|
||||
.module_cache
|
||||
@ -1751,6 +1771,7 @@ fn finish_specialization<'a>(
|
||||
let State {
|
||||
procedures,
|
||||
module_cache,
|
||||
output_path,
|
||||
..
|
||||
} = state;
|
||||
|
||||
@ -1771,6 +1792,7 @@ fn finish_specialization<'a>(
|
||||
can_problems,
|
||||
mono_problems,
|
||||
type_problems,
|
||||
output_path: output_path.unwrap_or(DEFAULT_APP_OUTPUT_PATH).into(),
|
||||
exposed_to_host,
|
||||
module_id: state.root_id,
|
||||
subs,
|
||||
@ -1967,7 +1989,10 @@ fn parse_header<'a>(
|
||||
|
||||
match parsed {
|
||||
Ok((ast::Module::Interface { header }, parse_state)) => Ok(send_header(
|
||||
header.name,
|
||||
Located {
|
||||
region: header.name.region,
|
||||
value: AppOrInterfaceName::Interface(header.name.value),
|
||||
},
|
||||
filename,
|
||||
header.exposes.into_bump_slice(),
|
||||
header.imports.into_bump_slice(),
|
||||
@ -1981,7 +2006,10 @@ fn parse_header<'a>(
|
||||
pkg_config_dir.pop();
|
||||
|
||||
let (module_id, app_module_header_msg) = send_header(
|
||||
header.name,
|
||||
Located {
|
||||
region: header.name.region,
|
||||
value: AppOrInterfaceName::App(header.name.value),
|
||||
},
|
||||
filename,
|
||||
header.provides.into_bump_slice(),
|
||||
header.imports.into_bump_slice(),
|
||||
@ -2083,21 +2111,35 @@ fn load_from_str<'a>(
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum AppOrInterfaceName<'a> {
|
||||
/// A filename
|
||||
App(StrLiteral<'a>),
|
||||
Interface(roc_parse::header::ModuleName<'a>),
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn send_header<'a>(
|
||||
name: Located<roc_parse::header::ModuleName<'a>>,
|
||||
loc_name: Located<AppOrInterfaceName<'a>>,
|
||||
filename: PathBuf,
|
||||
exposes: &'a [Located<ExposesEntry<'a>>],
|
||||
exposes: &'a [Located<ExposesEntry<'a, &'a str>>],
|
||||
imports: &'a [Located<ImportsEntry<'a>>],
|
||||
parse_state: parser::State<'a>,
|
||||
module_ids: Arc<Mutex<ModuleIds>>,
|
||||
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
|
||||
module_timing: ModuleTiming,
|
||||
) -> (ModuleId, Msg<'a>) {
|
||||
let declared_name: ModuleName = name.value.as_str().into();
|
||||
use AppOrInterfaceName::*;
|
||||
|
||||
// TODO check to see if declared_name is consistent with filename.
|
||||
// If it isn't, report a problem!
|
||||
let declared_name: ModuleName = match &loc_name.value {
|
||||
App(_) => ModuleName::APP.into(),
|
||||
Interface(module_name) => {
|
||||
// TODO check to see if module_name is consistent with filename.
|
||||
// If it isn't, report a problem!
|
||||
|
||||
module_name.as_str().into()
|
||||
}
|
||||
};
|
||||
|
||||
let mut imported: Vec<(ModuleName, Vec<Ident>, Region)> = Vec::with_capacity(imports.len());
|
||||
let mut imported_modules: MutSet<ModuleId> = MutSet::default();
|
||||
@ -2200,15 +2242,13 @@ fn send_header<'a>(
|
||||
// We always need to send these, even if deps is empty,
|
||||
// because the coordinator thread needs to receive this message
|
||||
// to decrement its "pending" count.
|
||||
|
||||
// Send the header the main thread for processing,
|
||||
(
|
||||
home,
|
||||
Msg::Header(ModuleHeader {
|
||||
module_id: home,
|
||||
module_path: filename,
|
||||
exposed_ident_ids: ident_ids,
|
||||
module_name: declared_name,
|
||||
module_name: loc_name.value,
|
||||
imported_modules,
|
||||
deps_by_name,
|
||||
exposes: exposed,
|
||||
@ -2619,8 +2659,14 @@ fn canonicalize_and_constrain<'a>(
|
||||
|
||||
// Generate documentation information
|
||||
// TODO: store timing information?
|
||||
let module_docs =
|
||||
crate::docs::generate_module_docs(module_name, &exposed_ident_ids, &parsed_defs);
|
||||
let module_docs = match module_name {
|
||||
AppOrInterfaceName::App(_) => None,
|
||||
AppOrInterfaceName::Interface(name) => Some(crate::docs::generate_module_docs(
|
||||
name.as_str().into(),
|
||||
&exposed_ident_ids,
|
||||
&parsed_defs,
|
||||
)),
|
||||
};
|
||||
|
||||
let mut var_store = VarStore::default();
|
||||
let canonicalized = canonicalize_module_defs(
|
||||
@ -2737,7 +2783,7 @@ fn parse<'a>(arena: &'a Bump, header: ModuleHeader<'a>) -> Result<Msg<'a>, Loadi
|
||||
}
|
||||
|
||||
fn exposed_from_import(entry: &ImportsEntry<'_>) -> (ModuleName, Vec<Ident>) {
|
||||
use roc_parse::ast::ImportsEntry::*;
|
||||
use roc_parse::header::ImportsEntry::*;
|
||||
|
||||
match entry {
|
||||
Module(module_name, exposes) => {
|
||||
@ -2750,6 +2796,10 @@ fn exposed_from_import(entry: &ImportsEntry<'_>) -> (ModuleName, Vec<Ident>) {
|
||||
(module_name.as_str().into(), exposed)
|
||||
}
|
||||
|
||||
Package(_package_name, _module_name, _exposes) => {
|
||||
todo!("TODO support exposing package-qualified module names.");
|
||||
}
|
||||
|
||||
SpaceBefore(sub_entry, _) | SpaceAfter(sub_entry, _) => {
|
||||
// Ignore spaces.
|
||||
exposed_from_import(*sub_entry)
|
||||
@ -2757,11 +2807,11 @@ fn exposed_from_import(entry: &ImportsEntry<'_>) -> (ModuleName, Vec<Ident>) {
|
||||
}
|
||||
}
|
||||
|
||||
fn ident_from_exposed(entry: &ExposesEntry<'_>) -> Ident {
|
||||
use roc_parse::ast::ExposesEntry::*;
|
||||
fn ident_from_exposed(entry: &ExposesEntry<'_, &str>) -> Ident {
|
||||
use roc_parse::header::ExposesEntry::*;
|
||||
|
||||
match entry {
|
||||
Ident(ident) => (*ident).into(),
|
||||
Exposed(ident) => (*ident).into(),
|
||||
SpaceBefore(sub_entry, _) | SpaceAfter(sub_entry, _) => ident_from_exposed(sub_entry),
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ interface AStar
|
||||
Model position :
|
||||
{ evaluated : Set position
|
||||
, openSet : Set position
|
||||
, costs : Map.Map position Float
|
||||
, costs : Map.Map position F64
|
||||
, cameFrom : Map.Map position position
|
||||
}
|
||||
|
||||
@ -22,7 +22,7 @@ initialModel = \start ->
|
||||
}
|
||||
|
||||
|
||||
cheapestOpen : (position -> Float), Model position -> Result position [ KeyNotFound ]*
|
||||
cheapestOpen : (position -> F64), Model position -> Result position [ KeyNotFound ]*
|
||||
cheapestOpen = \costFunction, model ->
|
||||
|
||||
folder = \position, resSmallestSoFar ->
|
||||
@ -80,12 +80,12 @@ updateCost = \current, neighbour, model ->
|
||||
model
|
||||
|
||||
|
||||
findPath : { costFunction: (position, position -> Float), moveFunction: (position -> Set position), start : position, end : position } -> Result (List position) [ KeyNotFound ]*
|
||||
findPath : { costFunction: (position, position -> F64), moveFunction: (position -> Set position), start : position, end : position } -> Result (List position) [ KeyNotFound ]*
|
||||
findPath = \{ costFunction, moveFunction, start, end } ->
|
||||
astar costFunction moveFunction end (initialModel start)
|
||||
|
||||
|
||||
astar : (position, position -> Float), (position -> Set position), position, Model position -> [ Err [ KeyNotFound ]*, Ok (List position) ]*
|
||||
astar : (position, position -> F64), (position -> Set position), position, Model position -> [ Err [ KeyNotFound ]*, Ok (List position) ]*
|
||||
astar = \costFn, moveFn, goal, model ->
|
||||
when cheapestOpen (\position -> costFn goal position) model is
|
||||
Err _ ->
|
||||
|
@ -1,6 +1,7 @@
|
||||
app Primary
|
||||
provides [ blah2, blah3, str, alwaysThree, identity, z, w, succeed, withDefault, yay ]
|
||||
app "primary"
|
||||
packages { blah: "./blah" }
|
||||
imports [ Dep1, Dep2.{ two, foo }, Dep3.Blah.{ bar }, Res ]
|
||||
provides [ blah2, blah3, str, alwaysThree, identity, z, w, succeed, withDefault, yay ] to blah
|
||||
|
||||
blah2 = Dep2.two
|
||||
blah3 = bar
|
||||
@ -12,7 +13,7 @@ alwaysThree = \_ -> "foo"
|
||||
|
||||
identity = \a -> a
|
||||
|
||||
z = identity (Primary.alwaysThree {})
|
||||
z = identity (alwaysThree {})
|
||||
|
||||
w : Dep1.Identity {}
|
||||
w = Identity {}
|
||||
|
@ -1,6 +1,4 @@
|
||||
app Quicksort
|
||||
provides [ swap, partition, partitionHelp, quicksort ]
|
||||
imports []
|
||||
app "quicksort" provides [ swap, partition, partitionHelp, quicksort ] to "./platform"
|
||||
|
||||
quicksort : List (Num a), Int, Int -> List (Num a)
|
||||
quicksort = \list, low, high ->
|
||||
|
@ -1,4 +1,4 @@
|
||||
app QuicksortOneDef provides [ quicksort ] imports []
|
||||
app "quicksort" provides [ quicksort ] to "./platform"
|
||||
|
||||
quicksort = \originalList ->
|
||||
quicksortHelp : List (Num a), Int, Int -> List (Num a)
|
||||
@ -53,5 +53,5 @@ quicksort = \originalList ->
|
||||
|
||||
|
||||
|
||||
n = List.len originalList
|
||||
n = List.len originalList
|
||||
quicksortHelp originalList 0 (n - 1)
|
||||
|
@ -8,7 +8,7 @@ interface AStar
|
||||
Model position :
|
||||
{ evaluated : Set position
|
||||
, openSet : Set position
|
||||
, costs : Map.Map position Float
|
||||
, costs : Map.Map position F64
|
||||
, cameFrom : Map.Map position position
|
||||
}
|
||||
|
||||
@ -22,7 +22,7 @@ initialModel = \start ->
|
||||
}
|
||||
|
||||
|
||||
cheapestOpen : (position -> Float), Model position -> Result position [ KeyNotFound ]*
|
||||
cheapestOpen : (position -> F64), Model position -> Result position [ KeyNotFound ]*
|
||||
cheapestOpen = \costFunction, model ->
|
||||
|
||||
folder = \position, resSmallestSoFar ->
|
||||
@ -80,12 +80,12 @@ updateCost = \current, neighbour, model ->
|
||||
model
|
||||
|
||||
|
||||
findPath : { costFunction: (position, position -> Float), moveFunction: (position -> Set position), start : position, end : position } -> Result (List position) [ KeyNotFound ]*
|
||||
findPath : { costFunction: (position, position -> F64), moveFunction: (position -> Set position), start : position, end : position } -> Result (List position) [ KeyNotFound ]*
|
||||
findPath = \{ costFunction, moveFunction, start, end } ->
|
||||
astar costFunction moveFunction end (initialModel start)
|
||||
|
||||
|
||||
astar : (position, position -> Float), (position -> Set position), position, Model position -> [ Err [ KeyNotFound ]*, Ok (List position) ]*
|
||||
astar : (position, position -> F64), (position -> Set position), position, Model position -> [ Err [ KeyNotFound ]*, Ok (List position) ]*
|
||||
astar = \costFn, moveFn, goal, model ->
|
||||
when cheapestOpen (\position -> costFn goal position) model is
|
||||
Err _ ->
|
||||
|
@ -23,6 +23,7 @@ mod test_load {
|
||||
use roc_collections::all::MutMap;
|
||||
use roc_constrain::module::SubsByModule;
|
||||
use roc_load::file::LoadedModule;
|
||||
use roc_module::ident::ModuleName;
|
||||
use roc_module::symbol::{Interns, ModuleId};
|
||||
use roc_types::pretty_print::{content_to_string, name_all_type_vars};
|
||||
use roc_types::subs::Subs;
|
||||
@ -148,7 +149,10 @@ mod test_load {
|
||||
.get_name(loaded_module.module_id)
|
||||
.expect("Test ModuleID not found in module_ids");
|
||||
|
||||
assert_eq!(expected_name, &InlinableString::from(module_name));
|
||||
// App module names are hardcoded and not based on anything user-specified
|
||||
if expected_name != ModuleName::APP {
|
||||
assert_eq!(expected_name, &InlinableString::from(module_name));
|
||||
}
|
||||
|
||||
loaded_module
|
||||
}
|
||||
@ -238,31 +242,34 @@ mod test_load {
|
||||
"RBTree",
|
||||
indoc!(
|
||||
r#"
|
||||
interface RBTree exposes [ Dict, empty ] imports []
|
||||
interface RBTree exposes [ Dict, empty ] imports []
|
||||
|
||||
# The color of a node. Leaves are considered Black.
|
||||
NodeColor : [ Red, Black ]
|
||||
# The color of a node. Leaves are considered Black.
|
||||
NodeColor : [ Red, Black ]
|
||||
|
||||
Dict k v : [ Node NodeColor k v (Dict k v) (Dict k v), Empty ]
|
||||
Dict k v : [ Node NodeColor k v (Dict k v) (Dict k v), Empty ]
|
||||
|
||||
# Create an empty dictionary.
|
||||
empty : Dict k v
|
||||
empty =
|
||||
Empty
|
||||
"#
|
||||
# Create an empty dictionary.
|
||||
empty : Dict k v
|
||||
empty =
|
||||
Empty
|
||||
"#
|
||||
),
|
||||
),
|
||||
(
|
||||
"Main",
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports [ RBTree ]
|
||||
app "test-app"
|
||||
packages { blah: "./blah" }
|
||||
imports [ RBTree ]
|
||||
provides [ main ] to blah
|
||||
|
||||
empty : RBTree.Dict Int Int
|
||||
empty = RBTree.empty
|
||||
empty : RBTree.Dict Int Int
|
||||
empty = RBTree.empty
|
||||
|
||||
main = empty
|
||||
"#
|
||||
main = empty
|
||||
"#
|
||||
),
|
||||
),
|
||||
];
|
||||
@ -350,14 +357,14 @@ mod test_load {
|
||||
expect_types(
|
||||
loaded_module,
|
||||
hashmap! {
|
||||
"floatTest" => "Float",
|
||||
"divisionFn" => "Float, Float -> Result Float [ DivByZero ]*",
|
||||
"divisionTest" => "Result Float [ DivByZero ]*",
|
||||
"floatTest" => "F64",
|
||||
"divisionFn" => "F64, F64 -> Result F64 [ DivByZero ]*",
|
||||
"divisionTest" => "Result F64 [ DivByZero ]*",
|
||||
"intTest" => "Int",
|
||||
"x" => "Float",
|
||||
"x" => "F64",
|
||||
"constantNum" => "Num *",
|
||||
"divDep1ByDep2" => "Result Float [ DivByZero ]*",
|
||||
"fromDep2" => "Float",
|
||||
"divDep1ByDep2" => "Result F64 [ DivByZero ]*",
|
||||
"fromDep2" => "F64",
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -415,12 +422,12 @@ mod test_load {
|
||||
expect_types(
|
||||
loaded_module,
|
||||
hashmap! {
|
||||
"findPath" => "{ costFunction : position, position -> Float, end : position, moveFunction : position -> Set position, start : position } -> Result (List position) [ KeyNotFound ]*",
|
||||
"findPath" => "{ costFunction : position, position -> F64, end : position, moveFunction : position -> Set position, start : position } -> Result (List position) [ KeyNotFound ]*",
|
||||
"initialModel" => "position -> Model position",
|
||||
"reconstructPath" => "Map position position, position -> List position",
|
||||
"updateCost" => "position, position, Model position -> Model position",
|
||||
"cheapestOpen" => "(position -> Float), Model position -> Result position [ KeyNotFound ]*",
|
||||
"astar" => "(position, position -> Float), (position -> Set position), position, Model position -> [ Err [ KeyNotFound ]*, Ok (List position) ]*",
|
||||
"cheapestOpen" => "(position -> F64), Model position -> Result position [ KeyNotFound ]*",
|
||||
"astar" => "(position, position -> F64), (position -> Set position), position, Model position -> [ Err [ KeyNotFound ]*, Ok (List position) ]*",
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -447,7 +454,7 @@ mod test_load {
|
||||
expect_types(
|
||||
loaded_module,
|
||||
hashmap! {
|
||||
"blah2" => "Float",
|
||||
"blah2" => "F64",
|
||||
"blah3" => "Str",
|
||||
"str" => "Str",
|
||||
"alwaysThree" => "* -> Str",
|
||||
@ -469,7 +476,7 @@ mod test_load {
|
||||
expect_types(
|
||||
loaded_module,
|
||||
hashmap! {
|
||||
"blah2" => "Float",
|
||||
"blah2" => "F64",
|
||||
"blah3" => "Str",
|
||||
"str" => "Str",
|
||||
"alwaysThree" => "* -> Str",
|
||||
|
@ -22,6 +22,7 @@ mod test_uniq_load {
|
||||
use roc_collections::all::MutMap;
|
||||
use roc_constrain::module::SubsByModule;
|
||||
use roc_load::file::LoadedModule;
|
||||
use roc_module::ident::ModuleName;
|
||||
use roc_module::symbol::{Interns, ModuleId};
|
||||
use roc_types::pretty_print::{content_to_string, name_all_type_vars};
|
||||
use roc_types::subs::Subs;
|
||||
@ -66,7 +67,10 @@ mod test_uniq_load {
|
||||
.get_name(loaded_module.module_id)
|
||||
.expect("Test ModuleID not found in module_ids");
|
||||
|
||||
assert_eq!(expected_name, &InlinableString::from(module_name));
|
||||
// App module names are hardcoded and not based on anything user-specified
|
||||
if expected_name != ModuleName::APP {
|
||||
assert_eq!(expected_name, &InlinableString::from(module_name));
|
||||
}
|
||||
|
||||
loaded_module
|
||||
}
|
||||
@ -228,14 +232,14 @@ mod test_uniq_load {
|
||||
expect_types(
|
||||
loaded_module,
|
||||
hashmap! {
|
||||
"floatTest" => "Attr Shared Float",
|
||||
"divisionFn" => "Attr Shared (Attr * Float, Attr * Float -> Attr * (Result (Attr * Float) (Attr * [ DivByZero ]*)))",
|
||||
"divisionTest" => "Attr * (Result (Attr * Float) (Attr * [ DivByZero ]*))",
|
||||
"floatTest" => "Attr Shared F64",
|
||||
"divisionFn" => "Attr Shared (Attr * F64, Attr * F64 -> Attr * (Result (Attr * F64) (Attr * [ DivByZero ]*)))",
|
||||
"divisionTest" => "Attr * (Result (Attr * F64) (Attr * [ DivByZero ]*))",
|
||||
"intTest" => "Attr * Int",
|
||||
"x" => "Attr * Float",
|
||||
"x" => "Attr * F64",
|
||||
"constantNum" => "Attr * (Num (Attr * *))",
|
||||
"divDep1ByDep2" => "Attr * (Result (Attr * Float) (Attr * [ DivByZero ]*))",
|
||||
"fromDep2" => "Attr * Float",
|
||||
"divDep1ByDep2" => "Attr * (Result (Attr * F64) (Attr * [ DivByZero ]*))",
|
||||
"fromDep2" => "Attr * F64",
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -249,12 +253,12 @@ mod test_uniq_load {
|
||||
expect_types(
|
||||
loaded_module,
|
||||
hashmap! {
|
||||
"findPath" => "Attr * (Attr * { costFunction : Attr Shared (Attr Shared position, Attr Shared position -> Attr * Float), end : Attr Shared position, moveFunction : Attr Shared (Attr Shared position -> Attr * (Set (Attr * position))), start : Attr Shared position } -> Attr * (Result (Attr * (List (Attr Shared position))) (Attr * [ KeyNotFound ]*)))",
|
||||
"findPath" => "Attr * (Attr * { costFunction : Attr Shared (Attr Shared position, Attr Shared position -> Attr * F64), end : Attr Shared position, moveFunction : Attr Shared (Attr Shared position -> Attr * (Set (Attr * position))), start : Attr Shared position } -> Attr * (Result (Attr * (List (Attr Shared position))) (Attr * [ KeyNotFound ]*)))",
|
||||
"initialModel" => "Attr * (Attr Shared position -> Attr * (Model (Attr Shared position)))",
|
||||
"reconstructPath" => "Attr Shared (Attr Shared (Map (Attr * position) (Attr Shared position)), Attr Shared position -> Attr * (List (Attr Shared position)))",
|
||||
"updateCost" => "Attr * (Attr Shared position, Attr Shared position, Attr Shared (Model (Attr Shared position)) -> Attr Shared (Model (Attr Shared position)))",
|
||||
"cheapestOpen" => "Attr * (Attr * (Attr Shared position -> Attr * Float), Attr (* | a | b | c) (Model (Attr Shared position)) -> Attr * (Result (Attr Shared position) (Attr * [ KeyNotFound ]*)))",
|
||||
"astar" => "Attr Shared (Attr Shared (Attr Shared position, Attr Shared position -> Attr * Float), Attr Shared (Attr Shared position -> Attr * (Set (Attr * position))), Attr Shared position, Attr Shared (Model (Attr Shared position)) -> Attr * [ Err (Attr * [ KeyNotFound ]*), Ok (Attr * (List (Attr Shared position))) ]*)",
|
||||
"cheapestOpen" => "Attr * (Attr * (Attr Shared position -> Attr * F64), Attr (* | a | b | c) (Model (Attr Shared position)) -> Attr * (Result (Attr Shared position) (Attr * [ KeyNotFound ]*)))",
|
||||
"astar" => "Attr Shared (Attr Shared (Attr Shared position, Attr Shared position -> Attr * F64), Attr Shared (Attr Shared position -> Attr * (Set (Attr * position))), Attr Shared position, Attr Shared (Model (Attr Shared position)) -> Attr * [ Err (Attr * [ KeyNotFound ]*), Ok (Attr * (List (Attr Shared position))) ]*)",
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -314,7 +318,7 @@ mod test_uniq_load {
|
||||
expect_types(
|
||||
loaded_module,
|
||||
hashmap! {
|
||||
"blah2" => "Attr * Float",
|
||||
"blah2" => "Attr * F64",
|
||||
"blah3" => "Attr * Str",
|
||||
"str" => "Attr * Str",
|
||||
"alwaysThree" => "Attr * (* -> Attr * Str)",
|
||||
|
@ -59,6 +59,7 @@ impl TagName {
|
||||
impl ModuleName {
|
||||
// NOTE: After adding one of these, go to `impl ModuleId` and
|
||||
// add a corresponding ModuleId to there!
|
||||
pub const APP: &'static str = ""; // app modules have no module name
|
||||
pub const BOOL: &'static str = "Bool";
|
||||
pub const STR: &'static str = "Str";
|
||||
pub const NUM: &'static str = "Num";
|
||||
|
@ -22,7 +22,8 @@ pub enum LowLevel {
|
||||
ListJoin,
|
||||
ListMap,
|
||||
ListKeepIf,
|
||||
ListWalkRight,
|
||||
ListWalk,
|
||||
ListWalkBackwards,
|
||||
ListSum,
|
||||
NumAdd,
|
||||
NumAddWrap,
|
||||
|
@ -612,7 +612,7 @@ define_builtins! {
|
||||
2 NUM_INT: "Int" imported // the Int.Int type alias
|
||||
3 NUM_INTEGER: "Integer" imported // Int : Num Integer
|
||||
4 NUM_AT_INTEGER: "@Integer" // the Int.@Integer private tag
|
||||
5 NUM_FLOAT: "Float" imported // the Float.Float type alias
|
||||
5 NUM_F64: "F64" imported // the Num.F64 type alias
|
||||
6 NUM_FLOATINGPOINT: "FloatingPoint" imported // Float : Num FloatingPoint
|
||||
7 NUM_AT_FLOATINGPOINT: "@FloatingPoint" // the Float.@FloatingPoint private tag
|
||||
8 NUM_MAX_INT: "maxInt"
|
||||
@ -683,18 +683,18 @@ define_builtins! {
|
||||
5 LIST_APPEND: "append"
|
||||
6 LIST_MAP: "map"
|
||||
7 LIST_LEN: "len"
|
||||
8 LIST_FOLDL: "foldl"
|
||||
9 LIST_WALK_RIGHT: "walkRight"
|
||||
10 LIST_CONCAT: "concat"
|
||||
11 LIST_FIRST: "first"
|
||||
12 LIST_SINGLE: "single"
|
||||
13 LIST_REPEAT: "repeat"
|
||||
14 LIST_REVERSE: "reverse"
|
||||
15 LIST_PREPEND: "prepend"
|
||||
16 LIST_JOIN: "join"
|
||||
17 LIST_KEEP_IF: "keepIf"
|
||||
18 LIST_CONTAINS: "contains"
|
||||
19 LIST_SUM: "sum"
|
||||
8 LIST_WALK_BACKWARDS: "walkBackwards"
|
||||
9 LIST_CONCAT: "concat"
|
||||
10 LIST_FIRST: "first"
|
||||
11 LIST_SINGLE: "single"
|
||||
12 LIST_REPEAT: "repeat"
|
||||
13 LIST_REVERSE: "reverse"
|
||||
14 LIST_PREPEND: "prepend"
|
||||
15 LIST_JOIN: "join"
|
||||
16 LIST_KEEP_IF: "keepIf"
|
||||
17 LIST_CONTAINS: "contains"
|
||||
18 LIST_SUM: "sum"
|
||||
19 LIST_WALK: "walk"
|
||||
}
|
||||
5 RESULT: "Result" => {
|
||||
0 RESULT_RESULT: "Result" imported // the Result.Result type alias
|
||||
|
@ -535,7 +535,8 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
|
||||
ListMap => arena.alloc_slice_copy(&[owned, irrelevant]),
|
||||
ListKeepIf => arena.alloc_slice_copy(&[owned, irrelevant]),
|
||||
ListContains => arena.alloc_slice_copy(&[borrowed, irrelevant]),
|
||||
ListWalkRight => arena.alloc_slice_copy(&[borrowed, irrelevant, owned]),
|
||||
ListWalk => arena.alloc_slice_copy(&[borrowed, irrelevant, owned]),
|
||||
ListWalkBackwards => arena.alloc_slice_copy(&[borrowed, irrelevant, owned]),
|
||||
ListSum => arena.alloc_slice_copy(&[borrowed]),
|
||||
|
||||
Eq | NotEq | And | Or | NumAdd | NumAddWrap | NumAddChecked | NumSub | NumMul | NumGt
|
||||
|
@ -412,7 +412,7 @@ fn test_at_path<'a>(selected_path: &Path, branch: &Branch<'a>, all_tests: &mut V
|
||||
arguments.push((Pattern::Underscore, destruct.layout.clone()));
|
||||
}
|
||||
DestructType::Optional(_expr) => {
|
||||
arguments.push((Pattern::Underscore, destruct.layout.clone()));
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -540,23 +540,27 @@ fn to_relevant_branch_help<'a>(
|
||||
..
|
||||
} => {
|
||||
debug_assert!(test_name == &TagName::Global(RECORD_TAG_NAME.into()));
|
||||
let sub_positions = destructs.into_iter().enumerate().map(|(index, destruct)| {
|
||||
let pattern = match destruct.typ {
|
||||
DestructType::Guard(guard) => guard.clone(),
|
||||
DestructType::Required => Pattern::Underscore,
|
||||
DestructType::Optional(_expr) => Pattern::Underscore,
|
||||
};
|
||||
let sub_positions = destructs
|
||||
.into_iter()
|
||||
.filter(|destruct| !matches!(destruct.typ, DestructType::Optional(_)))
|
||||
.enumerate()
|
||||
.map(|(index, destruct)| {
|
||||
let pattern = match destruct.typ {
|
||||
DestructType::Guard(guard) => guard.clone(),
|
||||
DestructType::Required => Pattern::Underscore,
|
||||
DestructType::Optional(_expr) => unreachable!("because of the filter"),
|
||||
};
|
||||
|
||||
(
|
||||
Path::Index {
|
||||
index: index as u64,
|
||||
tag_id: *tag_id,
|
||||
path: Box::new(path.clone()),
|
||||
},
|
||||
Guard::NoGuard,
|
||||
pattern,
|
||||
)
|
||||
});
|
||||
(
|
||||
Path::Index {
|
||||
index: index as u64,
|
||||
tag_id: *tag_id,
|
||||
path: Box::new(path.clone()),
|
||||
},
|
||||
Guard::NoGuard,
|
||||
pattern,
|
||||
)
|
||||
});
|
||||
start.extend(sub_positions);
|
||||
start.extend(end);
|
||||
|
||||
|
@ -907,7 +907,13 @@ where
|
||||
if PRETTY_PRINT_IR_SYMBOLS {
|
||||
alloc.text(format!("{:?}", symbol))
|
||||
} else {
|
||||
alloc.text(format!("{}", symbol))
|
||||
let text = format!("{}", symbol);
|
||||
|
||||
if text.starts_with('.') {
|
||||
alloc.text("Test").append(text)
|
||||
} else {
|
||||
alloc.text(text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -917,7 +923,7 @@ where
|
||||
D::Doc: Clone,
|
||||
A: Clone,
|
||||
{
|
||||
alloc.text(format!("{}", symbol.0))
|
||||
symbol_to_doc(alloc, symbol.0)
|
||||
}
|
||||
|
||||
impl<'a> Expr<'a> {
|
||||
@ -1101,7 +1107,9 @@ impl<'a> Stmt<'a> {
|
||||
.chain(std::iter::once(default_doc));
|
||||
//
|
||||
alloc
|
||||
.text(format!("switch {}:", cond_symbol))
|
||||
.text("switch ")
|
||||
.append(symbol_to_doc(alloc, *cond_symbol))
|
||||
.append(":")
|
||||
.append(alloc.hardline())
|
||||
.append(
|
||||
alloc.intersperse(branches_docs, alloc.hardline().append(alloc.hardline())),
|
||||
@ -1115,7 +1123,9 @@ impl<'a> Stmt<'a> {
|
||||
fail,
|
||||
..
|
||||
} => alloc
|
||||
.text(format!("if {} then", branching_symbol))
|
||||
.text("if ")
|
||||
.append(symbol_to_doc(alloc, *branching_symbol))
|
||||
.append(" then")
|
||||
.append(alloc.hardline())
|
||||
.append(pass.to_doc(alloc).indent(4))
|
||||
.append(alloc.hardline())
|
||||
@ -2384,7 +2394,7 @@ pub fn with_hole<'a>(
|
||||
Tag {
|
||||
variant_var,
|
||||
name: tag_name,
|
||||
arguments: args,
|
||||
arguments: mut args,
|
||||
..
|
||||
} => {
|
||||
use crate::layout::UnionVariant::*;
|
||||
@ -2421,11 +2431,34 @@ pub fn with_hole<'a>(
|
||||
}
|
||||
|
||||
Unwrapped(field_layouts) => {
|
||||
let mut field_symbols_temp =
|
||||
Vec::with_capacity_in(field_layouts.len(), env.arena);
|
||||
|
||||
for (var, arg) in args.drain(..) {
|
||||
// Layout will unpack this unwrapped tack if it only has one (non-zero-sized) field
|
||||
let layout = layout_cache
|
||||
.from_var(env.arena, var, env.subs)
|
||||
.unwrap_or_else(|err| {
|
||||
panic!("TODO turn fn_var into a RuntimeError {:?}", err)
|
||||
});
|
||||
|
||||
let alignment = layout.alignment_bytes(8);
|
||||
|
||||
let symbol = possible_reuse_symbol(env, procs, &arg.value);
|
||||
field_symbols_temp.push((
|
||||
alignment,
|
||||
symbol,
|
||||
((var, arg), &*env.arena.alloc(symbol)),
|
||||
));
|
||||
}
|
||||
field_symbols_temp.sort_by(|a, b| b.0.cmp(&a.0));
|
||||
|
||||
let mut field_symbols = Vec::with_capacity_in(field_layouts.len(), env.arena);
|
||||
|
||||
for (_, arg) in args.iter() {
|
||||
field_symbols.push(possible_reuse_symbol(env, procs, &arg.value));
|
||||
for (_, symbol, _) in field_symbols_temp.iter() {
|
||||
field_symbols.push(*symbol);
|
||||
}
|
||||
|
||||
let field_symbols = field_symbols.into_bump_slice();
|
||||
|
||||
// Layout will unpack this unwrapped tack if it only has one (non-zero-sized) field
|
||||
@ -2438,7 +2471,7 @@ pub fn with_hole<'a>(
|
||||
// even though this was originally a Tag, we treat it as a Struct from now on
|
||||
let stmt = Stmt::Let(assigned, Expr::Struct(field_symbols), layout, hole);
|
||||
|
||||
let iter = args.into_iter().rev().zip(field_symbols.iter().rev());
|
||||
let iter = field_symbols_temp.into_iter().map(|(_, _, data)| data);
|
||||
assign_to_symbols(env, procs, layout_cache, iter, stmt)
|
||||
}
|
||||
Wrapped(sorted_tag_layouts) => {
|
||||
@ -2449,12 +2482,33 @@ pub fn with_hole<'a>(
|
||||
.find(|(_, (key, _))| key == &tag_name)
|
||||
.expect("tag must be in its own type");
|
||||
|
||||
let mut field_symbols_temp = Vec::with_capacity_in(args.len(), env.arena);
|
||||
|
||||
for (var, arg) in args.drain(..) {
|
||||
// Layout will unpack this unwrapped tack if it only has one (non-zero-sized) field
|
||||
let layout = layout_cache
|
||||
.from_var(env.arena, var, env.subs)
|
||||
.unwrap_or_else(|err| {
|
||||
panic!("TODO turn fn_var into a RuntimeError {:?}", err)
|
||||
});
|
||||
|
||||
let alignment = layout.alignment_bytes(8);
|
||||
|
||||
let symbol = possible_reuse_symbol(env, procs, &arg.value);
|
||||
field_symbols_temp.push((
|
||||
alignment,
|
||||
symbol,
|
||||
((var, arg), &*env.arena.alloc(symbol)),
|
||||
));
|
||||
}
|
||||
field_symbols_temp.sort_by(|a, b| b.0.cmp(&a.0));
|
||||
|
||||
let mut field_symbols: Vec<Symbol> = Vec::with_capacity_in(args.len(), arena);
|
||||
let tag_id_symbol = env.unique_symbol();
|
||||
field_symbols.push(tag_id_symbol);
|
||||
|
||||
for (_, arg) in args.iter() {
|
||||
field_symbols.push(possible_reuse_symbol(env, procs, &arg.value));
|
||||
for (_, symbol, _) in field_symbols_temp.iter() {
|
||||
field_symbols.push(*symbol);
|
||||
}
|
||||
|
||||
let mut layouts: Vec<&'a [Layout<'a>]> =
|
||||
@ -2475,7 +2529,11 @@ pub fn with_hole<'a>(
|
||||
};
|
||||
|
||||
let mut stmt = Stmt::Let(assigned, tag, layout, hole);
|
||||
let iter = args.into_iter().rev().zip(field_symbols.iter().rev());
|
||||
let iter = field_symbols_temp
|
||||
.drain(..)
|
||||
.map(|x| x.2 .0)
|
||||
.rev()
|
||||
.zip(field_symbols.iter().rev());
|
||||
|
||||
stmt = assign_to_symbols(env, procs, layout_cache, iter, stmt);
|
||||
|
||||
@ -5290,6 +5348,20 @@ pub fn from_can_pattern<'a>(
|
||||
}],
|
||||
};
|
||||
|
||||
let mut arguments = arguments.clone();
|
||||
|
||||
arguments.sort_by(|arg1, arg2| {
|
||||
let ptr_bytes = 8;
|
||||
|
||||
let layout1 = layout_cache.from_var(env.arena, arg1.0, env.subs).unwrap();
|
||||
let layout2 = layout_cache.from_var(env.arena, arg2.0, env.subs).unwrap();
|
||||
|
||||
let size1 = layout1.alignment_bytes(ptr_bytes);
|
||||
let size2 = layout2.alignment_bytes(ptr_bytes);
|
||||
|
||||
size2.cmp(&size1)
|
||||
});
|
||||
|
||||
let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena);
|
||||
for ((_, loc_pat), layout) in arguments.iter().zip(field_layouts.iter()) {
|
||||
mono_args.push((
|
||||
@ -5333,6 +5405,20 @@ pub fn from_can_pattern<'a>(
|
||||
let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena);
|
||||
// disregard the tag discriminant layout
|
||||
|
||||
let mut arguments = arguments.clone();
|
||||
|
||||
arguments.sort_by(|arg1, arg2| {
|
||||
let ptr_bytes = 8;
|
||||
|
||||
let layout1 = layout_cache.from_var(env.arena, arg1.0, env.subs).unwrap();
|
||||
let layout2 = layout_cache.from_var(env.arena, arg2.0, env.subs).unwrap();
|
||||
|
||||
let size1 = layout1.alignment_bytes(ptr_bytes);
|
||||
let size2 = layout2.alignment_bytes(ptr_bytes);
|
||||
|
||||
size2.cmp(&size1)
|
||||
});
|
||||
|
||||
// TODO make this assert pass, it currently does not because
|
||||
// 0-sized values are dropped out
|
||||
// debug_assert_eq!(arguments.len(), argument_layouts[1..].len());
|
||||
@ -5374,8 +5460,8 @@ pub fn from_can_pattern<'a>(
|
||||
|
||||
// sorted fields based on the destruct
|
||||
let mut mono_destructs = Vec::with_capacity_in(destructs.len(), env.arena);
|
||||
let mut destructs = destructs.clone();
|
||||
destructs.sort_by(|a, b| a.value.label.cmp(&b.value.label));
|
||||
let destructs_by_label = env.arena.alloc(MutMap::default());
|
||||
destructs_by_label.extend(destructs.iter().map(|x| (&x.value.label, x)));
|
||||
|
||||
let mut field_layouts = Vec::with_capacity_in(sorted_fields.len(), env.arena);
|
||||
|
||||
@ -5387,119 +5473,96 @@ pub fn from_can_pattern<'a>(
|
||||
// in the source the field is not matche in the source language.
|
||||
//
|
||||
// Optional fields somewhat complicate the matter here
|
||||
let mut it1 = sorted_fields.into_iter();
|
||||
let mut opt_sorted = it1.next();
|
||||
|
||||
let mut it2 = destructs.iter();
|
||||
let mut opt_destruct = it2.next();
|
||||
for (label, variable, res_layout) in sorted_fields.into_iter() {
|
||||
match res_layout {
|
||||
Ok(field_layout) => {
|
||||
// the field is non-optional according to the type
|
||||
|
||||
loop {
|
||||
match (opt_sorted, opt_destruct) {
|
||||
(Some((label, variable, Ok(field_layout))), Some(destruct)) => {
|
||||
if destruct.value.label == label {
|
||||
mono_destructs.push(from_can_record_destruct(
|
||||
env,
|
||||
layout_cache,
|
||||
&destruct.value,
|
||||
field_layout.clone(),
|
||||
));
|
||||
|
||||
opt_sorted = it1.next();
|
||||
opt_destruct = it2.next();
|
||||
} else {
|
||||
// insert underscore pattern
|
||||
mono_destructs.push(RecordDestruct {
|
||||
label: label.clone(),
|
||||
symbol: env.unique_symbol(),
|
||||
variable,
|
||||
layout: field_layout.clone(),
|
||||
typ: DestructType::Guard(Pattern::Underscore),
|
||||
});
|
||||
|
||||
opt_sorted = it1.next();
|
||||
match destructs_by_label.remove(&label) {
|
||||
Some(destruct) => {
|
||||
// this field is destructured by the pattern
|
||||
mono_destructs.push(from_can_record_destruct(
|
||||
env,
|
||||
layout_cache,
|
||||
&destruct.value,
|
||||
field_layout.clone(),
|
||||
));
|
||||
}
|
||||
None => {
|
||||
// this field is not destructured by the pattern
|
||||
// put in an underscore
|
||||
mono_destructs.push(RecordDestruct {
|
||||
label: label.clone(),
|
||||
symbol: env.unique_symbol(),
|
||||
variable,
|
||||
layout: field_layout.clone(),
|
||||
typ: DestructType::Guard(Pattern::Underscore),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// the layout of this field is part of the layout of the record
|
||||
field_layouts.push(field_layout);
|
||||
}
|
||||
(Some((label, variable, Err(field_layout))), Some(destruct)) => {
|
||||
if destruct.value.label == label {
|
||||
opt_destruct = it2.next();
|
||||
|
||||
mono_destructs.push(RecordDestruct {
|
||||
label: destruct.value.label.clone(),
|
||||
symbol: destruct.value.symbol,
|
||||
layout: field_layout,
|
||||
variable,
|
||||
typ: match &destruct.value.typ {
|
||||
roc_can::pattern::DestructType::Optional(_, loc_expr) => {
|
||||
// if we reach this stage, the optional field is not present
|
||||
// so use the default
|
||||
DestructType::Optional(loc_expr.value.clone())
|
||||
}
|
||||
_ => unreachable!(
|
||||
"only optional destructs can be optional fields"
|
||||
),
|
||||
},
|
||||
});
|
||||
}
|
||||
opt_sorted = it1.next();
|
||||
}
|
||||
|
||||
(Some((label, variable, Err(field_layout))), None) => {
|
||||
// the remainder of the fields (from the type) is not matched on in
|
||||
// this pattern; to fill it out, we put underscores
|
||||
mono_destructs.push(RecordDestruct {
|
||||
label: label.clone(),
|
||||
symbol: env.unique_symbol(),
|
||||
variable,
|
||||
layout: field_layout.clone(),
|
||||
typ: DestructType::Guard(Pattern::Underscore),
|
||||
});
|
||||
|
||||
opt_sorted = it1.next();
|
||||
}
|
||||
|
||||
(Some((label, variable, Ok(field_layout))), None) => {
|
||||
// the remainder of the fields (from the type) is not matched on in
|
||||
// this pattern; to fill it out, we put underscores
|
||||
mono_destructs.push(RecordDestruct {
|
||||
label: label.clone(),
|
||||
symbol: env.unique_symbol(),
|
||||
variable,
|
||||
layout: field_layout.clone(),
|
||||
typ: DestructType::Guard(Pattern::Underscore),
|
||||
});
|
||||
|
||||
field_layouts.push(field_layout);
|
||||
opt_sorted = it1.next();
|
||||
}
|
||||
(None, Some(destruct)) => {
|
||||
// destruct is not in the type, but is in the pattern
|
||||
// it must be an optional field, and we will use the default
|
||||
match &destruct.value.typ {
|
||||
roc_can::pattern::DestructType::Optional(field_var, loc_expr) => {
|
||||
let field_layout = layout_cache
|
||||
.from_var(env.arena, *field_var, env.subs)
|
||||
.unwrap_or_else(|err| {
|
||||
panic!("TODO turn fn_var into a RuntimeError {:?}", err)
|
||||
});
|
||||
|
||||
Err(field_layout) => {
|
||||
// the field is optional according to the type
|
||||
match destructs_by_label.remove(&label) {
|
||||
Some(destruct) => {
|
||||
// this field is destructured by the pattern
|
||||
mono_destructs.push(RecordDestruct {
|
||||
label: destruct.value.label.clone(),
|
||||
symbol: destruct.value.symbol,
|
||||
variable: destruct.value.var,
|
||||
layout: field_layout,
|
||||
typ: DestructType::Optional(loc_expr.value.clone()),
|
||||
})
|
||||
variable,
|
||||
typ: match &destruct.value.typ {
|
||||
roc_can::pattern::DestructType::Optional(_, loc_expr) => {
|
||||
// if we reach this stage, the optional field is not present
|
||||
// so use the default
|
||||
DestructType::Optional(loc_expr.value.clone())
|
||||
}
|
||||
_ => unreachable!(
|
||||
"only optional destructs can be optional fields"
|
||||
),
|
||||
},
|
||||
});
|
||||
}
|
||||
None => {
|
||||
// this field is not destructured by the pattern
|
||||
// put in an underscore
|
||||
mono_destructs.push(RecordDestruct {
|
||||
label: label.clone(),
|
||||
symbol: env.unique_symbol(),
|
||||
variable,
|
||||
layout: field_layout.clone(),
|
||||
typ: DestructType::Guard(Pattern::Underscore),
|
||||
});
|
||||
}
|
||||
_ => unreachable!("only optional destructs can be optional fields"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
opt_sorted = None;
|
||||
opt_destruct = it2.next();
|
||||
}
|
||||
(None, None) => {
|
||||
break;
|
||||
for (_, destruct) in destructs_by_label.drain() {
|
||||
// this destruct is not in the type, but is in the pattern
|
||||
// it must be an optional field, and we will use the default
|
||||
match &destruct.value.typ {
|
||||
roc_can::pattern::DestructType::Optional(field_var, loc_expr) => {
|
||||
let field_layout = layout_cache
|
||||
.from_var(env.arena, *field_var, env.subs)
|
||||
.unwrap_or_else(|err| {
|
||||
panic!("TODO turn fn_var into a RuntimeError {:?}", err)
|
||||
});
|
||||
|
||||
mono_destructs.push(RecordDestruct {
|
||||
label: destruct.value.label.clone(),
|
||||
symbol: destruct.value.symbol,
|
||||
variable: destruct.value.var,
|
||||
layout: field_layout,
|
||||
typ: DestructType::Optional(loc_expr.value.clone()),
|
||||
})
|
||||
}
|
||||
_ => unreachable!("only optional destructs can be optional fields"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ use roc_collections::all::{default_hasher, MutMap, MutSet};
|
||||
use roc_module::ident::{Lowercase, TagName};
|
||||
use roc_module::symbol::{Interns, Symbol};
|
||||
use roc_types::subs::{Content, FlatType, Subs, Variable};
|
||||
use roc_types::types::RecordField;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub const MAX_ENUM_SIZE: usize = (std::mem::size_of::<u8>() * 8) as usize;
|
||||
@ -326,7 +327,7 @@ impl<'a> Layout<'a> {
|
||||
debug_assert!(args.is_empty());
|
||||
Ok(Layout::Builtin(Builtin::Int64))
|
||||
}
|
||||
Alias(Symbol::NUM_FLOAT, args, _) => {
|
||||
Alias(Symbol::NUM_F64, args, _) => {
|
||||
debug_assert!(args.is_empty());
|
||||
Ok(Layout::Builtin(Builtin::Float64))
|
||||
}
|
||||
@ -726,7 +727,7 @@ fn layout_from_flat_type<'a>(
|
||||
debug_assert_eq!(args.len(), 0);
|
||||
Ok(Layout::Builtin(Builtin::Int64))
|
||||
}
|
||||
Symbol::NUM_FLOAT => {
|
||||
Symbol::NUM_F64 => {
|
||||
debug_assert_eq!(args.len(), 0);
|
||||
Ok(Layout::Builtin(Builtin::Float64))
|
||||
}
|
||||
@ -789,59 +790,30 @@ fn layout_from_flat_type<'a>(
|
||||
}
|
||||
}
|
||||
Record(fields, ext_var) => {
|
||||
// Sort the fields by label
|
||||
let mut sorted_fields = Vec::with_capacity_in(fields.len(), arena);
|
||||
sorted_fields.extend(fields.into_iter());
|
||||
|
||||
// extract any values from the ext_var
|
||||
let mut fields_map = MutMap::default();
|
||||
fields_map.extend(fields);
|
||||
match roc_types::pretty_print::chase_ext_record(subs, ext_var, &mut fields_map) {
|
||||
Ok(()) | Err((_, Content::FlexVar(_))) => {}
|
||||
Err(_) => unreachable!("this would have been a type error"),
|
||||
}
|
||||
|
||||
sorted_fields.extend(fields_map.into_iter());
|
||||
|
||||
sorted_fields.sort_by(|(label1, _), (label2, _)| label1.cmp(label2));
|
||||
let sorted_fields = sort_record_fields_help(env, fields_map);
|
||||
|
||||
// Determine the layouts of the fields, maintaining sort order
|
||||
let mut layouts = Vec::with_capacity_in(sorted_fields.len(), arena);
|
||||
|
||||
for (label, field) in sorted_fields {
|
||||
use LayoutProblem::*;
|
||||
|
||||
let field_var = {
|
||||
use roc_types::types::RecordField::*;
|
||||
match field {
|
||||
Optional(_) => {
|
||||
// when an optional field reaches this stage, the field was truly
|
||||
// optional, and not unified to be demanded or required
|
||||
// therefore, there is no such field on the record, and we ignore this
|
||||
// field from now on.
|
||||
continue;
|
||||
}
|
||||
Required(var) => var,
|
||||
Demanded(var) => var,
|
||||
}
|
||||
};
|
||||
|
||||
match Layout::from_var(env, field_var) {
|
||||
for (_, _, res_layout) in sorted_fields {
|
||||
match res_layout {
|
||||
Ok(layout) => {
|
||||
// Drop any zero-sized fields like {}.
|
||||
if !layout.is_dropped_because_empty() {
|
||||
layouts.push(layout);
|
||||
}
|
||||
}
|
||||
Err(UnresolvedTypeVar(v)) => {
|
||||
// Invalid field!
|
||||
panic!(
|
||||
r"I hit an unresolved type var {:?} when determining the layout of {:?} of record field: {:?} : {:?}",
|
||||
field_var, v, label, field
|
||||
);
|
||||
}
|
||||
Err(Erroneous) => {
|
||||
// Invalid field!
|
||||
panic!("TODO gracefully handle record with invalid field.var");
|
||||
Err(_) => {
|
||||
// optional field, ignore
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -894,6 +866,15 @@ fn layout_from_flat_type<'a>(
|
||||
tag_layout.push(Layout::from_var(env, var)?);
|
||||
}
|
||||
|
||||
tag_layout.sort_by(|layout1, layout2| {
|
||||
let ptr_bytes = 8;
|
||||
|
||||
let size1 = layout1.alignment_bytes(ptr_bytes);
|
||||
let size2 = layout2.alignment_bytes(ptr_bytes);
|
||||
|
||||
size2.cmp(&size1)
|
||||
});
|
||||
|
||||
tag_layouts.push(tag_layout.into_bump_slice());
|
||||
}
|
||||
|
||||
@ -924,39 +905,55 @@ pub fn sort_record_fields<'a>(
|
||||
};
|
||||
|
||||
match roc_types::pretty_print::chase_ext_record(subs, var, &mut fields_map) {
|
||||
Ok(()) | Err((_, Content::FlexVar(_))) => {
|
||||
// Sort the fields by label
|
||||
let mut sorted_fields = Vec::with_capacity_in(fields_map.len(), arena);
|
||||
|
||||
use roc_types::types::RecordField;
|
||||
for (label, field) in fields_map {
|
||||
let var = match field {
|
||||
RecordField::Demanded(v) => v,
|
||||
RecordField::Required(v) => v,
|
||||
RecordField::Optional(v) => {
|
||||
let layout =
|
||||
Layout::from_var(&mut env, v).expect("invalid layout from var");
|
||||
sorted_fields.push((label, v, Err(layout)));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let layout = Layout::from_var(&mut env, var).expect("invalid layout from var");
|
||||
|
||||
// Drop any zero-sized fields like {}
|
||||
if !layout.is_dropped_because_empty() {
|
||||
sorted_fields.push((label, var, Ok(layout)));
|
||||
}
|
||||
}
|
||||
|
||||
sorted_fields.sort_by(|(label1, _, _), (label2, _, _)| label1.cmp(label2));
|
||||
|
||||
sorted_fields
|
||||
}
|
||||
Ok(()) | Err((_, Content::FlexVar(_))) => sort_record_fields_help(&mut env, fields_map),
|
||||
Err(other) => panic!("invalid content in record variable: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
fn sort_record_fields_help<'a>(
|
||||
env: &mut Env<'a, '_>,
|
||||
fields_map: MutMap<Lowercase, RecordField<Variable>>,
|
||||
) -> Vec<'a, (Lowercase, Variable, Result<Layout<'a>, Layout<'a>>)> {
|
||||
// Sort the fields by label
|
||||
let mut sorted_fields = Vec::with_capacity_in(fields_map.len(), env.arena);
|
||||
|
||||
for (label, field) in fields_map {
|
||||
let var = match field {
|
||||
RecordField::Demanded(v) => v,
|
||||
RecordField::Required(v) => v,
|
||||
RecordField::Optional(v) => {
|
||||
let layout = Layout::from_var(env, v).expect("invalid layout from var");
|
||||
sorted_fields.push((label, v, Err(layout)));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let layout = Layout::from_var(env, var).expect("invalid layout from var");
|
||||
|
||||
// Drop any zero-sized fields like {}
|
||||
if !layout.is_dropped_because_empty() {
|
||||
sorted_fields.push((label, var, Ok(layout)));
|
||||
}
|
||||
}
|
||||
|
||||
sorted_fields.sort_by(
|
||||
|(label1, _, res_layout1), (label2, _, res_layout2)| match res_layout1 {
|
||||
Ok(layout1) | Err(layout1) => match res_layout2 {
|
||||
Ok(layout2) | Err(layout2) => {
|
||||
let ptr_bytes = 8;
|
||||
|
||||
let size1 = layout1.alignment_bytes(ptr_bytes);
|
||||
let size2 = layout2.alignment_bytes(ptr_bytes);
|
||||
|
||||
size2.cmp(&size1).then(label1.cmp(label2))
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
sorted_fields
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum UnionVariant<'a> {
|
||||
Never,
|
||||
@ -1059,6 +1056,15 @@ pub fn union_sorted_tags_help<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
layouts.sort_by(|layout1, layout2| {
|
||||
let ptr_bytes = 8;
|
||||
|
||||
let size1 = layout1.alignment_bytes(ptr_bytes);
|
||||
let size2 = layout2.alignment_bytes(ptr_bytes);
|
||||
|
||||
size2.cmp(&size1)
|
||||
});
|
||||
|
||||
if layouts.is_empty() {
|
||||
if contains_zero_sized {
|
||||
UnionVariant::UnitWithArguments
|
||||
@ -1102,6 +1108,15 @@ pub fn union_sorted_tags_help<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
arg_layouts.sort_by(|layout1, layout2| {
|
||||
let ptr_bytes = 8;
|
||||
|
||||
let size1 = layout1.alignment_bytes(ptr_bytes);
|
||||
let size2 = layout2.alignment_bytes(ptr_bytes);
|
||||
|
||||
size2.cmp(&size1)
|
||||
});
|
||||
|
||||
answer.push((tag_name, arg_layouts.into_bump_slice()));
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,8 @@ mod test_mono {
|
||||
use roc_mono::layout::Layout;
|
||||
|
||||
fn promote_expr_to_module(src: &str) -> String {
|
||||
let mut buffer = String::from("app Test provides [ main ] imports []\n\nmain =\n");
|
||||
let mut buffer =
|
||||
String::from("app \"test\" provides [ main ] to \"./platform\"\n\nmain =\n");
|
||||
|
||||
for line in src.lines() {
|
||||
// indent the body!
|
||||
@ -189,9 +190,9 @@ mod test_mono {
|
||||
indoc!(
|
||||
r#"
|
||||
procedure Test.0 ():
|
||||
let Test.8 = 0i64;
|
||||
let Test.9 = 3i64;
|
||||
let Test.2 = Just Test.8 Test.9;
|
||||
let Test.9 = 0i64;
|
||||
let Test.8 = 3i64;
|
||||
let Test.2 = Just Test.9 Test.8;
|
||||
let Test.5 = 0i64;
|
||||
let Test.6 = Index 0 Test.2;
|
||||
let Test.7 = lowlevel Eq Test.5 Test.6;
|
||||
@ -218,10 +219,10 @@ mod test_mono {
|
||||
indoc!(
|
||||
r#"
|
||||
procedure Test.0 ():
|
||||
let Test.10 = 1i64;
|
||||
let Test.8 = 1i64;
|
||||
let Test.9 = 1i64;
|
||||
let Test.10 = 2i64;
|
||||
let Test.4 = These Test.8 Test.9 Test.10;
|
||||
let Test.9 = 2i64;
|
||||
let Test.4 = These Test.10 Test.8 Test.9;
|
||||
switch Test.4:
|
||||
case 2:
|
||||
let Test.1 = Index 1 Test.4;
|
||||
@ -317,14 +318,14 @@ mod test_mono {
|
||||
let Test.17 = 0i64;
|
||||
let Test.13 = lowlevel NotEq #Attr.3 Test.17;
|
||||
if Test.13 then
|
||||
let Test.15 = 1i64;
|
||||
let Test.16 = lowlevel NumDivUnchecked #Attr.2 #Attr.3;
|
||||
let Test.14 = Ok Test.15 Test.16;
|
||||
let Test.16 = 1i64;
|
||||
let Test.15 = lowlevel NumDivUnchecked #Attr.2 #Attr.3;
|
||||
let Test.14 = Ok Test.16 Test.15;
|
||||
ret Test.14;
|
||||
else
|
||||
let Test.11 = 0i64;
|
||||
let Test.12 = Struct {};
|
||||
let Test.10 = Err Test.11 Test.12;
|
||||
let Test.12 = 0i64;
|
||||
let Test.11 = Struct {};
|
||||
let Test.10 = Err Test.12 Test.11;
|
||||
ret Test.10;
|
||||
|
||||
procedure Test.0 ():
|
||||
@ -388,9 +389,9 @@ mod test_mono {
|
||||
ret Test.5;
|
||||
|
||||
procedure Test.0 ():
|
||||
let Test.10 = 0i64;
|
||||
let Test.11 = 41i64;
|
||||
let Test.1 = Just Test.10 Test.11;
|
||||
let Test.11 = 0i64;
|
||||
let Test.10 = 41i64;
|
||||
let Test.1 = Just Test.11 Test.10;
|
||||
let Test.7 = 0i64;
|
||||
let Test.8 = Index 0 Test.1;
|
||||
let Test.9 = lowlevel Eq Test.7 Test.8;
|
||||
@ -515,11 +516,11 @@ mod test_mono {
|
||||
ret Test.6;
|
||||
|
||||
procedure Test.0 ():
|
||||
let Test.17 = 0i64;
|
||||
let Test.19 = 0i64;
|
||||
let Test.20 = 41i64;
|
||||
let Test.18 = Just Test.19 Test.20;
|
||||
let Test.2 = Just Test.17 Test.18;
|
||||
let Test.18 = 0i64;
|
||||
let Test.20 = 0i64;
|
||||
let Test.19 = 41i64;
|
||||
let Test.17 = Just Test.20 Test.19;
|
||||
let Test.2 = Just Test.18 Test.17;
|
||||
joinpoint Test.14:
|
||||
let Test.8 = 1i64;
|
||||
ret Test.8;
|
||||
@ -562,8 +563,8 @@ mod test_mono {
|
||||
ret Test.6;
|
||||
|
||||
procedure Test.0 ():
|
||||
let Test.14 = 2i64;
|
||||
let Test.15 = 3i64;
|
||||
let Test.14 = 2i64;
|
||||
let Test.3 = Struct {Test.14, Test.15};
|
||||
joinpoint Test.11:
|
||||
let Test.1 = Index 0 Test.3;
|
||||
@ -809,9 +810,9 @@ mod test_mono {
|
||||
indoc!(
|
||||
r#"
|
||||
procedure Test.1 (Test.4):
|
||||
let Test.18 = 1i64;
|
||||
let Test.19 = 2i64;
|
||||
let Test.2 = Ok Test.18 Test.19;
|
||||
let Test.19 = 1i64;
|
||||
let Test.18 = 2i64;
|
||||
let Test.2 = Ok Test.19 Test.18;
|
||||
joinpoint Test.8 Test.3:
|
||||
ret Test.3;
|
||||
in
|
||||
@ -1277,7 +1278,7 @@ mod test_mono {
|
||||
compiles_to_ir(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
swap = \list ->
|
||||
when Pair (List.get list 0) (List.get list 0) is
|
||||
@ -1299,14 +1300,14 @@ mod test_mono {
|
||||
let Test.38 = lowlevel ListLen #Attr.2;
|
||||
let Test.34 = lowlevel NumLt #Attr.3 Test.38;
|
||||
if Test.34 then
|
||||
let Test.36 = 1i64;
|
||||
let Test.37 = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
|
||||
let Test.35 = Ok Test.36 Test.37;
|
||||
let Test.37 = 1i64;
|
||||
let Test.36 = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
|
||||
let Test.35 = Ok Test.37 Test.36;
|
||||
ret Test.35;
|
||||
else
|
||||
let Test.32 = 0i64;
|
||||
let Test.33 = Struct {};
|
||||
let Test.31 = Err Test.32 Test.33;
|
||||
let Test.33 = 0i64;
|
||||
let Test.32 = Struct {};
|
||||
let Test.31 = Err Test.33 Test.32;
|
||||
ret Test.31;
|
||||
|
||||
procedure List.4 (#Attr.2, #Attr.3, #Attr.4):
|
||||
@ -1320,9 +1321,9 @@ mod test_mono {
|
||||
|
||||
procedure Test.1 (Test.2):
|
||||
let Test.39 = 0i64;
|
||||
let Test.28 = CallByName List.3 Test.2 Test.39;
|
||||
let Test.29 = CallByName List.3 Test.2 Test.39;
|
||||
let Test.30 = 0i64;
|
||||
let Test.29 = CallByName List.3 Test.2 Test.30;
|
||||
let Test.28 = CallByName List.3 Test.2 Test.30;
|
||||
let Test.7 = Struct {Test.28, Test.29};
|
||||
joinpoint Test.25:
|
||||
let Test.18 = Array [];
|
||||
@ -1373,7 +1374,7 @@ mod test_mono {
|
||||
compiles_to_ir(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
partitionHelp : Int, Int, List (Num a), Int, (Num a) -> [ Pair Int (List (Num a)) ]
|
||||
partitionHelp = \i, j, list, high, pivot ->
|
||||
@ -1409,7 +1410,7 @@ mod test_mono {
|
||||
compiles_to_ir(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
quicksortHelp : List (Num a), Int, Int -> List (Num a)
|
||||
quicksortHelp = \list, low, high ->
|
||||
@ -1618,7 +1619,7 @@ mod test_mono {
|
||||
compiles_to_ir(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
mkPairOf = \x -> Pair x x
|
||||
|
||||
@ -1650,7 +1651,7 @@ mod test_mono {
|
||||
compiles_to_ir(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
fst = \x, _ -> x
|
||||
|
||||
@ -1687,7 +1688,7 @@ mod test_mono {
|
||||
compiles_to_ir(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
x : List Int
|
||||
x = [1,2,3]
|
||||
@ -1763,14 +1764,14 @@ mod test_mono {
|
||||
let Test.15 = lowlevel ListLen #Attr.2;
|
||||
let Test.11 = lowlevel NumLt #Attr.3 Test.15;
|
||||
if Test.11 then
|
||||
let Test.13 = 1i64;
|
||||
let Test.14 = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
|
||||
let Test.12 = Ok Test.13 Test.14;
|
||||
let Test.14 = 1i64;
|
||||
let Test.13 = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
|
||||
let Test.12 = Ok Test.14 Test.13;
|
||||
ret Test.12;
|
||||
else
|
||||
let Test.9 = 0i64;
|
||||
let Test.10 = Struct {};
|
||||
let Test.8 = Err Test.9 Test.10;
|
||||
let Test.10 = 0i64;
|
||||
let Test.9 = Struct {};
|
||||
let Test.8 = Err Test.10 Test.9;
|
||||
ret Test.8;
|
||||
|
||||
procedure Test.1 (Test.2):
|
||||
@ -1808,14 +1809,14 @@ mod test_mono {
|
||||
indoc!(
|
||||
r#"
|
||||
procedure Test.0 ():
|
||||
let Test.4 = 0i64;
|
||||
let Test.6 = 0i64;
|
||||
let Test.8 = 0i64;
|
||||
let Test.5 = 0i64;
|
||||
let Test.7 = 0i64;
|
||||
let Test.9 = 0i64;
|
||||
let Test.10 = 1i64;
|
||||
let Test.9 = Z Test.10;
|
||||
let Test.7 = S Test.8 Test.9;
|
||||
let Test.5 = S Test.6 Test.7;
|
||||
let Test.2 = S Test.4 Test.5;
|
||||
let Test.8 = Z Test.10;
|
||||
let Test.6 = S Test.9 Test.8;
|
||||
let Test.4 = S Test.7 Test.6;
|
||||
let Test.2 = S Test.5 Test.4;
|
||||
ret Test.2;
|
||||
"#
|
||||
),
|
||||
@ -1840,14 +1841,14 @@ mod test_mono {
|
||||
indoc!(
|
||||
r#"
|
||||
procedure Test.0 ():
|
||||
let Test.8 = 0i64;
|
||||
let Test.10 = 0i64;
|
||||
let Test.12 = 0i64;
|
||||
let Test.9 = 0i64;
|
||||
let Test.11 = 0i64;
|
||||
let Test.13 = 0i64;
|
||||
let Test.14 = 1i64;
|
||||
let Test.13 = Z Test.14;
|
||||
let Test.11 = S Test.12 Test.13;
|
||||
let Test.9 = S Test.10 Test.11;
|
||||
let Test.2 = S Test.8 Test.9;
|
||||
let Test.12 = Z Test.14;
|
||||
let Test.10 = S Test.13 Test.12;
|
||||
let Test.8 = S Test.11 Test.10;
|
||||
let Test.2 = S Test.9 Test.8;
|
||||
let Test.5 = 1i64;
|
||||
let Test.6 = Index 0 Test.2;
|
||||
dec Test.2;
|
||||
@ -1882,14 +1883,14 @@ mod test_mono {
|
||||
indoc!(
|
||||
r#"
|
||||
procedure Test.0 ():
|
||||
let Test.14 = 0i64;
|
||||
let Test.16 = 0i64;
|
||||
let Test.18 = 0i64;
|
||||
let Test.15 = 0i64;
|
||||
let Test.17 = 0i64;
|
||||
let Test.19 = 0i64;
|
||||
let Test.20 = 1i64;
|
||||
let Test.19 = Z Test.20;
|
||||
let Test.17 = S Test.18 Test.19;
|
||||
let Test.15 = S Test.16 Test.17;
|
||||
let Test.2 = S Test.14 Test.15;
|
||||
let Test.18 = Z Test.20;
|
||||
let Test.16 = S Test.19 Test.18;
|
||||
let Test.14 = S Test.17 Test.16;
|
||||
let Test.2 = S Test.15 Test.14;
|
||||
let Test.11 = 0i64;
|
||||
let Test.12 = Index 0 Test.2;
|
||||
let Test.13 = lowlevel Eq Test.11 Test.12;
|
||||
@ -1943,14 +1944,14 @@ mod test_mono {
|
||||
ret Test.13;
|
||||
|
||||
procedure Test.1 (Test.6):
|
||||
let Test.18 = Index 0 Test.6;
|
||||
let Test.18 = Index 1 Test.6;
|
||||
let Test.19 = false;
|
||||
let Test.20 = lowlevel Eq Test.19 Test.18;
|
||||
if Test.20 then
|
||||
let Test.8 = Index 1 Test.6;
|
||||
let Test.8 = Index 0 Test.6;
|
||||
ret Test.8;
|
||||
else
|
||||
let Test.10 = Index 1 Test.6;
|
||||
let Test.10 = Index 0 Test.6;
|
||||
ret Test.10;
|
||||
|
||||
procedure Test.1 (Test.6):
|
||||
@ -1971,12 +1972,12 @@ mod test_mono {
|
||||
let Test.32 = false;
|
||||
let Test.26 = Struct {Test.32};
|
||||
let Test.3 = CallByName Test.1 Test.26;
|
||||
let Test.24 = true;
|
||||
let Test.25 = 11i64;
|
||||
let Test.24 = 11i64;
|
||||
let Test.25 = true;
|
||||
let Test.23 = Struct {Test.24, Test.25};
|
||||
let Test.4 = CallByName Test.1 Test.23;
|
||||
let Test.21 = false;
|
||||
let Test.22 = 7i64;
|
||||
let Test.21 = 7i64;
|
||||
let Test.22 = false;
|
||||
let Test.15 = Struct {Test.21, Test.22};
|
||||
let Test.2 = CallByName Test.1 Test.15;
|
||||
let Test.14 = CallByName Num.16 Test.2 Test.3;
|
||||
@ -2010,11 +2011,11 @@ mod test_mono {
|
||||
ret Test.6;
|
||||
|
||||
procedure Test.0 ():
|
||||
let Test.17 = 0i64;
|
||||
let Test.19 = 0i64;
|
||||
let Test.20 = 41i64;
|
||||
let Test.18 = Just Test.19 Test.20;
|
||||
let Test.2 = Just Test.17 Test.18;
|
||||
let Test.18 = 0i64;
|
||||
let Test.20 = 0i64;
|
||||
let Test.19 = 41i64;
|
||||
let Test.17 = Just Test.20 Test.19;
|
||||
let Test.2 = Just Test.18 Test.17;
|
||||
joinpoint Test.14:
|
||||
let Test.8 = 1i64;
|
||||
ret Test.8;
|
||||
@ -2103,7 +2104,7 @@ mod test_mono {
|
||||
compiles_to_ir(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
swap : Int, Int, List a -> List a
|
||||
swap = \i, j, list ->
|
||||
@ -2128,14 +2129,14 @@ mod test_mono {
|
||||
let Test.40 = lowlevel ListLen #Attr.2;
|
||||
let Test.36 = lowlevel NumLt #Attr.3 Test.40;
|
||||
if Test.36 then
|
||||
let Test.38 = 1i64;
|
||||
let Test.39 = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
|
||||
let Test.37 = Ok Test.38 Test.39;
|
||||
let Test.39 = 1i64;
|
||||
let Test.38 = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
|
||||
let Test.37 = Ok Test.39 Test.38;
|
||||
ret Test.37;
|
||||
else
|
||||
let Test.34 = 0i64;
|
||||
let Test.35 = Struct {};
|
||||
let Test.33 = Err Test.34 Test.35;
|
||||
let Test.35 = 0i64;
|
||||
let Test.34 = Struct {};
|
||||
let Test.33 = Err Test.35 Test.34;
|
||||
ret Test.33;
|
||||
|
||||
procedure List.4 (#Attr.2, #Attr.3, #Attr.4):
|
||||
@ -2148,8 +2149,8 @@ mod test_mono {
|
||||
ret #Attr.2;
|
||||
|
||||
procedure Test.1 (Test.2, Test.3, Test.4):
|
||||
let Test.31 = CallByName List.3 Test.4 Test.2;
|
||||
let Test.32 = CallByName List.3 Test.4 Test.3;
|
||||
let Test.31 = CallByName List.3 Test.4 Test.2;
|
||||
let Test.12 = Struct {Test.31, Test.32};
|
||||
joinpoint Test.28:
|
||||
let Test.21 = Array [];
|
||||
@ -2260,7 +2261,7 @@ mod test_mono {
|
||||
compiles_to_ir(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
foo = \{} ->
|
||||
x = 42
|
||||
@ -2302,7 +2303,7 @@ mod test_mono {
|
||||
compiles_to_ir(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
foo = \{} ->
|
||||
x = 41
|
||||
|
@ -1,7 +1,6 @@
|
||||
use crate::header::{ModuleName, PackageName};
|
||||
use crate::header::{AppHeader, ImportsEntry, InterfaceHeader, PlatformHeader, TypedIdent};
|
||||
use crate::ident::Ident;
|
||||
use bumpalo::collections::String;
|
||||
use bumpalo::collections::Vec;
|
||||
use bumpalo::Bump;
|
||||
use roc_module::operator::{BinOp, CalledVia, UnaryOp};
|
||||
use roc_region::all::{Loc, Region};
|
||||
@ -13,20 +12,6 @@ pub enum Module<'a> {
|
||||
Platform { header: PlatformHeader<'a> },
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct InterfaceHeader<'a> {
|
||||
pub name: Loc<ModuleName<'a>>,
|
||||
pub exposes: Vec<'a, Loc<ExposesEntry<'a>>>,
|
||||
pub imports: Vec<'a, Loc<ImportsEntry<'a>>>,
|
||||
|
||||
// Potential comments and newlines - these will typically all be empty.
|
||||
pub after_interface_keyword: &'a [CommentOrNewline<'a>],
|
||||
pub before_exposes: &'a [CommentOrNewline<'a>],
|
||||
pub after_exposes: &'a [CommentOrNewline<'a>],
|
||||
pub before_imports: &'a [CommentOrNewline<'a>],
|
||||
pub after_imports: &'a [CommentOrNewline<'a>],
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct WhenBranch<'a> {
|
||||
pub patterns: &'a [Loc<Pattern<'a>>],
|
||||
@ -34,94 +19,6 @@ pub struct WhenBranch<'a> {
|
||||
pub guard: Option<Loc<Expr<'a>>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct AppHeader<'a> {
|
||||
pub name: Loc<ModuleName<'a>>,
|
||||
pub provides: Vec<'a, Loc<ExposesEntry<'a>>>,
|
||||
pub imports: Vec<'a, Loc<ImportsEntry<'a>>>,
|
||||
|
||||
// Potential comments and newlines - these will typically all be empty.
|
||||
pub after_app_keyword: &'a [CommentOrNewline<'a>],
|
||||
pub before_provides: &'a [CommentOrNewline<'a>],
|
||||
pub after_provides: &'a [CommentOrNewline<'a>],
|
||||
pub before_imports: &'a [CommentOrNewline<'a>],
|
||||
pub after_imports: &'a [CommentOrNewline<'a>],
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct PlatformHeader<'a> {
|
||||
pub name: Loc<PackageName<'a>>,
|
||||
pub provides: Vec<'a, Loc<ExposesEntry<'a>>>,
|
||||
pub requires: Vec<'a, Loc<TypedIdent<'a>>>,
|
||||
pub imports: Vec<'a, Loc<ImportsEntry<'a>>>,
|
||||
pub effects: Effects<'a>,
|
||||
|
||||
// Potential comments and newlines - these will typically all be empty.
|
||||
pub after_platform_keyword: &'a [CommentOrNewline<'a>],
|
||||
pub before_provides: &'a [CommentOrNewline<'a>],
|
||||
pub after_provides: &'a [CommentOrNewline<'a>],
|
||||
pub before_requires: &'a [CommentOrNewline<'a>],
|
||||
pub after_requires: &'a [CommentOrNewline<'a>],
|
||||
pub before_imports: &'a [CommentOrNewline<'a>],
|
||||
pub after_imports: &'a [CommentOrNewline<'a>],
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Effects<'a> {
|
||||
pub spaces_before_effects_keyword: &'a [CommentOrNewline<'a>],
|
||||
pub spaces_after_effects_keyword: &'a [CommentOrNewline<'a>],
|
||||
pub spaces_after_type_name: &'a [CommentOrNewline<'a>],
|
||||
pub type_name: &'a str,
|
||||
pub entries: Vec<'a, Loc<TypedIdent<'a>>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum TypedIdent<'a> {
|
||||
/// e.g.
|
||||
///
|
||||
/// printLine : Str -> Effect {}
|
||||
Entry {
|
||||
ident: Loc<&'a str>,
|
||||
spaces_before_colon: &'a [CommentOrNewline<'a>],
|
||||
ann: Loc<TypeAnnotation<'a>>,
|
||||
},
|
||||
|
||||
// Spaces
|
||||
SpaceBefore(&'a TypedIdent<'a>, &'a [CommentOrNewline<'a>]),
|
||||
SpaceAfter(&'a TypedIdent<'a>, &'a [CommentOrNewline<'a>]),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum ExposesEntry<'a> {
|
||||
/// e.g. `Task`
|
||||
Ident(&'a str),
|
||||
|
||||
// Spaces
|
||||
SpaceBefore(&'a ExposesEntry<'a>, &'a [CommentOrNewline<'a>]),
|
||||
SpaceAfter(&'a ExposesEntry<'a>, &'a [CommentOrNewline<'a>]),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum ImportsEntry<'a> {
|
||||
/// e.g. `Task` or `Task.{ Task, after }`
|
||||
Module(ModuleName<'a>, Vec<'a, Loc<ExposesEntry<'a>>>),
|
||||
|
||||
// Spaces
|
||||
SpaceBefore(&'a ImportsEntry<'a>, &'a [CommentOrNewline<'a>]),
|
||||
SpaceAfter(&'a ImportsEntry<'a>, &'a [CommentOrNewline<'a>]),
|
||||
}
|
||||
|
||||
impl<'a> ExposesEntry<'a> {
|
||||
pub fn as_str(&'a self) -> &'a str {
|
||||
use ExposesEntry::*;
|
||||
|
||||
match self {
|
||||
Ident(string) => string,
|
||||
SpaceBefore(sub_entry, _) | SpaceAfter(sub_entry, _) => sub_entry.as_str(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct WhenPattern<'a> {
|
||||
pub pattern: Loc<Pattern<'a>>,
|
||||
@ -633,15 +530,6 @@ impl<'a> Spaceable<'a> for TypeAnnotation<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Spaceable<'a> for ExposesEntry<'a> {
|
||||
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
|
||||
ExposesEntry::SpaceBefore(self, spaces)
|
||||
}
|
||||
fn after(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
|
||||
ExposesEntry::SpaceAfter(self, spaces)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Spaceable<'a> for ImportsEntry<'a> {
|
||||
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
|
||||
ImportsEntry::SpaceBefore(self, spaces)
|
||||
|
@ -544,7 +544,7 @@ fn annotation<'a>(
|
||||
ascii_char(b':'),
|
||||
// Spaces after the ':' (at a normal indentation level) and then the type.
|
||||
// The type itself must be indented more than the pattern and ':'
|
||||
space0_before(type_annotation::located(indented_more), indented_more)
|
||||
space0_before(type_annotation::located(indented_more), min_indent)
|
||||
)
|
||||
)
|
||||
}
|
||||
@ -835,7 +835,7 @@ fn parse_def_signature<'a>(
|
||||
// It should be indented more than the original, and it will
|
||||
// end when outdented again.
|
||||
and_then_with_indent_level(
|
||||
type_annotation::located(indented_more),
|
||||
space0_before(type_annotation::located(indented_more), min_indent),
|
||||
// The first annotation may be immediately (spaces_then_comment_or_newline())
|
||||
// followed by a body at the exact same indent_level
|
||||
// leading to an AnnotatedBody in this case
|
||||
@ -1730,17 +1730,8 @@ fn ident_etc<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
|
||||
};
|
||||
let region = loc_ident.region;
|
||||
let loc_pattern = Located { region, value };
|
||||
let (spaces_after_colon, state) = space0(min_indent).parse(arena, state)?;
|
||||
let (parsed_expr, state) =
|
||||
parse_def_signature(min_indent, colon_indent, arena, state, loc_pattern)?;
|
||||
|
||||
let answer = if spaces_after_colon.is_empty() {
|
||||
parsed_expr
|
||||
} else {
|
||||
Expr::SpaceBefore(arena.alloc(parsed_expr), spaces_after_colon)
|
||||
};
|
||||
|
||||
Ok((answer, state))
|
||||
parse_def_signature(min_indent, colon_indent, arena, state, loc_pattern)
|
||||
}
|
||||
(None, None) => {
|
||||
// We got nothin'
|
||||
@ -1977,17 +1968,8 @@ fn record_literal<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
|
||||
Pattern::SpaceAfter(arena.alloc(pattern), spaces_before_colon)
|
||||
};
|
||||
let loc_pattern = Located { region, value };
|
||||
let (spaces_after_equals, state) = space0(min_indent).parse(arena, state)?;
|
||||
let (parsed_expr, state) =
|
||||
parse_def_signature(min_indent, colon_indent, arena, state, loc_pattern)?;
|
||||
|
||||
let answer = if spaces_after_equals.is_empty() {
|
||||
parsed_expr
|
||||
} else {
|
||||
Expr::SpaceBefore(arena.alloc(parsed_expr), spaces_after_equals)
|
||||
};
|
||||
|
||||
Ok((answer, state))
|
||||
parse_def_signature(min_indent, colon_indent, arena, state, loc_pattern)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -1,15 +1,43 @@
|
||||
use crate::ast::CommentOrNewline;
|
||||
use crate::ast::{CommentOrNewline, Spaceable, StrLiteral, TypeAnnotation};
|
||||
use crate::blankspace::space0;
|
||||
use crate::ident::lowercase_ident;
|
||||
use crate::module::package_name;
|
||||
use crate::parser::{ascii_char, optional, Either, Parser};
|
||||
use crate::string_literal;
|
||||
use bumpalo::collections::Vec;
|
||||
use inlinable_string::InlinableString;
|
||||
use roc_region::all::Loc;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
|
||||
pub struct PackageName<'a> {
|
||||
pub account: &'a str,
|
||||
pub pkg: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
|
||||
pub enum Version<'a> {
|
||||
Exact(&'a str),
|
||||
Range {
|
||||
min: &'a str,
|
||||
min_comparison: VersionComparison,
|
||||
max: &'a str,
|
||||
max_comparison: VersionComparison,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
|
||||
pub enum VersionComparison {
|
||||
AllowsEqual,
|
||||
DisallowsEqual,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub enum PackageOrPath<'a> {
|
||||
Package(PackageName<'a>, Version<'a>),
|
||||
Path(StrLiteral<'a>),
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
|
||||
pub struct ModuleName<'a>(&'a str);
|
||||
|
||||
impl<'a> Into<&'a str> for ModuleName<'a> {
|
||||
@ -34,46 +62,228 @@ impl<'a> ModuleName<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO is this all duplicated from parse::ast?
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct InterfaceHeader<'a> {
|
||||
pub name: Loc<ModuleName<'a>>,
|
||||
pub exposes: Vec<'a, Loc<Exposes<'a>>>,
|
||||
pub imports: Vec<'a, (ModuleName<'a>, Vec<'a, Loc<Imports<'a>>>)>,
|
||||
pub exposes: Vec<'a, Loc<ExposesEntry<'a, &'a str>>>,
|
||||
pub imports: Vec<'a, Loc<ImportsEntry<'a>>>,
|
||||
|
||||
// Potential comments and newlines - these will typically all be empty.
|
||||
pub after_interface: &'a [CommentOrNewline<'a>],
|
||||
pub after_interface_keyword: &'a [CommentOrNewline<'a>],
|
||||
pub before_exposes: &'a [CommentOrNewline<'a>],
|
||||
pub after_exposes: &'a [CommentOrNewline<'a>],
|
||||
pub before_imports: &'a [CommentOrNewline<'a>],
|
||||
pub after_imports: &'a [CommentOrNewline<'a>],
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum To<'a> {
|
||||
ExistingPackage(&'a str),
|
||||
NewPackage(PackageOrPath<'a>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct AppHeader<'a> {
|
||||
pub imports: Vec<'a, (ModuleName<'a>, Loc<Imports<'a>>)>,
|
||||
pub name: Loc<StrLiteral<'a>>,
|
||||
pub packages: Vec<'a, Loc<PackageEntry<'a>>>,
|
||||
pub imports: Vec<'a, Loc<ImportsEntry<'a>>>,
|
||||
pub provides: Vec<'a, Loc<ExposesEntry<'a, &'a str>>>,
|
||||
pub to: Loc<To<'a>>,
|
||||
|
||||
// Potential comments and newlines - these will typically all be empty.
|
||||
pub after_app_keyword: &'a [CommentOrNewline<'a>],
|
||||
pub before_packages: &'a [CommentOrNewline<'a>],
|
||||
pub after_packages: &'a [CommentOrNewline<'a>],
|
||||
pub before_imports: &'a [CommentOrNewline<'a>],
|
||||
pub after_imports: &'a [CommentOrNewline<'a>],
|
||||
pub before_provides: &'a [CommentOrNewline<'a>],
|
||||
pub after_provides: &'a [CommentOrNewline<'a>],
|
||||
pub before_to: &'a [CommentOrNewline<'a>],
|
||||
pub after_to: &'a [CommentOrNewline<'a>],
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct PackageHeader<'a> {
|
||||
pub name: Loc<PackageName<'a>>,
|
||||
pub exposes: Vec<'a, Loc<ExposesEntry<'a, &'a str>>>,
|
||||
pub packages: Vec<'a, (Loc<&'a str>, Loc<PackageOrPath<'a>>)>,
|
||||
pub imports: Vec<'a, Loc<ImportsEntry<'a>>>,
|
||||
|
||||
// Potential comments and newlines - these will typically all be empty.
|
||||
pub after_package_keyword: &'a [CommentOrNewline<'a>],
|
||||
pub before_exposes: &'a [CommentOrNewline<'a>],
|
||||
pub after_exposes: &'a [CommentOrNewline<'a>],
|
||||
pub before_packages: &'a [CommentOrNewline<'a>],
|
||||
pub after_packages: &'a [CommentOrNewline<'a>],
|
||||
pub before_imports: &'a [CommentOrNewline<'a>],
|
||||
pub after_imports: &'a [CommentOrNewline<'a>],
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Exposes<'a> {
|
||||
/// e.g. `Task`
|
||||
Ident(&'a str),
|
||||
pub struct PlatformHeader<'a> {
|
||||
pub name: Loc<PackageName<'a>>,
|
||||
pub requires: Vec<'a, Loc<TypedIdent<'a>>>,
|
||||
pub exposes: Vec<'a, Loc<ExposesEntry<'a, ModuleName<'a>>>>,
|
||||
pub packages: Vec<'a, Loc<PackageEntry<'a>>>,
|
||||
pub imports: Vec<'a, Loc<ImportsEntry<'a>>>,
|
||||
pub provides: Vec<'a, Loc<ExposesEntry<'a, &'a str>>>,
|
||||
pub effects: Effects<'a>,
|
||||
|
||||
// Spaces
|
||||
SpaceBefore(&'a Exposes<'a>, &'a [CommentOrNewline<'a>]),
|
||||
SpaceAfter(&'a Exposes<'a>, &'a [CommentOrNewline<'a>]),
|
||||
// Potential comments and newlines - these will typically all be empty.
|
||||
pub after_platform_keyword: &'a [CommentOrNewline<'a>],
|
||||
pub before_requires: &'a [CommentOrNewline<'a>],
|
||||
pub after_requires: &'a [CommentOrNewline<'a>],
|
||||
pub before_exposes: &'a [CommentOrNewline<'a>],
|
||||
pub after_exposes: &'a [CommentOrNewline<'a>],
|
||||
pub before_packages: &'a [CommentOrNewline<'a>],
|
||||
pub after_packages: &'a [CommentOrNewline<'a>],
|
||||
pub before_imports: &'a [CommentOrNewline<'a>],
|
||||
pub after_imports: &'a [CommentOrNewline<'a>],
|
||||
pub before_provides: &'a [CommentOrNewline<'a>],
|
||||
pub after_provides: &'a [CommentOrNewline<'a>],
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Imports<'a> {
|
||||
/// e.g. `Task` or `Task.{ Task, after }`
|
||||
Ident(&'a str, Vec<'a, &'a str>),
|
||||
pub struct Effects<'a> {
|
||||
pub spaces_before_effects_keyword: &'a [CommentOrNewline<'a>],
|
||||
pub spaces_after_effects_keyword: &'a [CommentOrNewline<'a>],
|
||||
pub spaces_after_type_name: &'a [CommentOrNewline<'a>],
|
||||
pub type_name: &'a str,
|
||||
pub entries: Vec<'a, Loc<TypedIdent<'a>>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum ExposesEntry<'a, T> {
|
||||
/// e.g. `Task`
|
||||
Exposed(T),
|
||||
|
||||
// Spaces
|
||||
SpaceBefore(&'a Imports<'a>, &'a [CommentOrNewline<'a>]),
|
||||
SpaceAfter(&'a Imports<'a>, &'a [CommentOrNewline<'a>]),
|
||||
SpaceBefore(&'a ExposesEntry<'a, T>, &'a [CommentOrNewline<'a>]),
|
||||
SpaceAfter(&'a ExposesEntry<'a, T>, &'a [CommentOrNewline<'a>]),
|
||||
}
|
||||
|
||||
impl<'a, T> Spaceable<'a> for ExposesEntry<'a, T> {
|
||||
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
|
||||
ExposesEntry::SpaceBefore(self, spaces)
|
||||
}
|
||||
fn after(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
|
||||
ExposesEntry::SpaceAfter(self, spaces)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum ImportsEntry<'a> {
|
||||
/// e.g. `Task` or `Task.{ Task, after }`
|
||||
Module(ModuleName<'a>, Vec<'a, Loc<ExposesEntry<'a, &'a str>>>),
|
||||
|
||||
/// e.g. `base.Task` or `base.Task.{ after }` or `base.{ Task.{ Task, after } }`
|
||||
Package(
|
||||
&'a str,
|
||||
ModuleName<'a>,
|
||||
Vec<'a, Loc<ExposesEntry<'a, &'a str>>>,
|
||||
),
|
||||
|
||||
// Spaces
|
||||
SpaceBefore(&'a ImportsEntry<'a>, &'a [CommentOrNewline<'a>]),
|
||||
SpaceAfter(&'a ImportsEntry<'a>, &'a [CommentOrNewline<'a>]),
|
||||
}
|
||||
|
||||
impl<'a> ExposesEntry<'a, &'a str> {
|
||||
pub fn as_str(&'a self) -> &'a str {
|
||||
use ExposesEntry::*;
|
||||
|
||||
match self {
|
||||
Exposed(string) => string,
|
||||
SpaceBefore(sub_entry, _) | SpaceAfter(sub_entry, _) => sub_entry.as_str(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum TypedIdent<'a> {
|
||||
/// e.g.
|
||||
///
|
||||
/// printLine : Str -> Effect {}
|
||||
Entry {
|
||||
ident: Loc<&'a str>,
|
||||
spaces_before_colon: &'a [CommentOrNewline<'a>],
|
||||
ann: Loc<TypeAnnotation<'a>>,
|
||||
},
|
||||
|
||||
// Spaces
|
||||
SpaceBefore(&'a TypedIdent<'a>, &'a [CommentOrNewline<'a>]),
|
||||
SpaceAfter(&'a TypedIdent<'a>, &'a [CommentOrNewline<'a>]),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum PackageEntry<'a> {
|
||||
Entry {
|
||||
shorthand: &'a str,
|
||||
spaces_after_shorthand: &'a [CommentOrNewline<'a>],
|
||||
package_or_path: Loc<PackageOrPath<'a>>,
|
||||
},
|
||||
|
||||
// Spaces
|
||||
SpaceBefore(&'a PackageEntry<'a>, &'a [CommentOrNewline<'a>]),
|
||||
SpaceAfter(&'a PackageEntry<'a>, &'a [CommentOrNewline<'a>]),
|
||||
}
|
||||
|
||||
impl<'a> Spaceable<'a> for PackageEntry<'a> {
|
||||
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
|
||||
PackageEntry::SpaceBefore(self, spaces)
|
||||
}
|
||||
fn after(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
|
||||
PackageEntry::SpaceAfter(self, spaces)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn package_entry<'a>() -> impl Parser<'a, PackageEntry<'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)
|
||||
))
|
||||
.parse(arena, state)?;
|
||||
let (package_or_path, state) = loc!(package_or_path()).parse(arena, state)?;
|
||||
let entry = match opt_shorthand {
|
||||
Some((shorthand, spaces_after_shorthand)) => PackageEntry::Entry {
|
||||
shorthand,
|
||||
spaces_after_shorthand,
|
||||
package_or_path,
|
||||
},
|
||||
None => PackageEntry::Entry {
|
||||
shorthand: "",
|
||||
spaces_after_shorthand: &[],
|
||||
package_or_path,
|
||||
},
|
||||
};
|
||||
|
||||
Ok((entry, state))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn package_or_path<'a>() -> impl Parser<'a, PackageOrPath<'a>> {
|
||||
map!(
|
||||
either!(
|
||||
string_literal::parse(),
|
||||
and!(
|
||||
package_name(),
|
||||
skip_first!(one_or_more!(ascii_char(b' ')), package_version())
|
||||
)
|
||||
),
|
||||
|answer| {
|
||||
match answer {
|
||||
Either::First(str_literal) => PackageOrPath::Path(str_literal),
|
||||
Either::Second((name, version)) => PackageOrPath::Package(name, version),
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fn package_version<'a>() -> impl Parser<'a, Version<'a>> {
|
||||
move |_, _| todo!("TODO parse package version")
|
||||
}
|
||||
|
@ -1,15 +1,17 @@
|
||||
use crate::ast::{
|
||||
AppHeader, Attempting, CommentOrNewline, Def, Effects, ExposesEntry, ImportsEntry,
|
||||
InterfaceHeader, Module, PlatformHeader, TypedIdent,
|
||||
};
|
||||
use crate::ast::{Attempting, CommentOrNewline, Def, Module};
|
||||
use crate::blankspace::{space0, space0_around, space0_before, space1};
|
||||
use crate::expr::def;
|
||||
use crate::header::{ModuleName, PackageName};
|
||||
use crate::header::{
|
||||
package_entry, package_or_path, AppHeader, Effects, ExposesEntry, ImportsEntry,
|
||||
InterfaceHeader, ModuleName, PackageEntry, PackageName, PackageOrPath, PlatformHeader, To,
|
||||
TypedIdent,
|
||||
};
|
||||
use crate::ident::{lowercase_ident, unqualified_ident, uppercase_ident};
|
||||
use crate::parser::{
|
||||
self, ascii_char, ascii_string, loc, optional, peek_utf8_char, peek_utf8_char_at, unexpected,
|
||||
unexpected_eof, ParseResult, Parser, State,
|
||||
unexpected_eof, Either, ParseResult, Parser, State,
|
||||
};
|
||||
use crate::string_literal;
|
||||
use crate::type_annotation;
|
||||
use bumpalo::collections::{String, Vec};
|
||||
use bumpalo::Bump;
|
||||
@ -44,7 +46,7 @@ pub fn interface_header<'a>() -> impl Parser<'a, InterfaceHeader<'a>> {
|
||||
ascii_string("interface"),
|
||||
and!(space1(1), loc!(module_name()))
|
||||
),
|
||||
and!(exposes(), imports())
|
||||
and!(exposes_values(), imports())
|
||||
),
|
||||
|(
|
||||
(after_interface_keyword, name),
|
||||
@ -173,66 +175,114 @@ pub fn module_name<'a>() -> impl Parser<'a, ModuleName<'a>> {
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn app_header<'a>() -> impl Parser<'a, AppHeader<'a>> {
|
||||
parser::map(
|
||||
pub fn app_header<'a>() -> impl Parser<'a, AppHeader<'a>> {
|
||||
map_with_arena!(
|
||||
and!(
|
||||
skip_first!(ascii_string("app"), and!(space1(1), loc!(module_name()))),
|
||||
and!(provides(), imports())
|
||||
),
|
||||
|(
|
||||
(after_app_keyword, name),
|
||||
(
|
||||
((before_provides, after_provides), provides),
|
||||
((before_imports, after_imports), imports),
|
||||
skip_first!(
|
||||
ascii_string("app"),
|
||||
and!(space1(1), loc!(string_literal::parse()))
|
||||
),
|
||||
)| {
|
||||
and!(
|
||||
optional(packages()),
|
||||
and!(optional(imports()), provides_to())
|
||||
)
|
||||
),
|
||||
|arena, ((after_app_keyword, name), (opt_pkgs, (opt_imports, provides)))| {
|
||||
let (before_packages, after_packages, package_entries) = match opt_pkgs {
|
||||
Some(pkgs) => {
|
||||
let pkgs: Packages<'a> = pkgs; // rustc must be told the type here
|
||||
|
||||
(
|
||||
pkgs.before_packages_keyword,
|
||||
pkgs.after_packages_keyword,
|
||||
pkgs.entries,
|
||||
)
|
||||
}
|
||||
None => (&[] as _, &[] as _, Vec::new_in(arena)),
|
||||
};
|
||||
|
||||
// rustc must be told the type here
|
||||
#[allow(clippy::type_complexity)]
|
||||
let opt_imports: Option<(
|
||||
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
|
||||
Vec<'a, Located<ImportsEntry<'a>>>,
|
||||
)> = opt_imports;
|
||||
|
||||
let ((before_imports, after_imports), imports) =
|
||||
opt_imports.unwrap_or_else(|| ((&[] as _, &[] as _), Vec::new_in(arena)));
|
||||
let provides: ProvidesTo<'a> = provides; // rustc must be told the type here
|
||||
|
||||
AppHeader {
|
||||
name,
|
||||
provides,
|
||||
packages: package_entries,
|
||||
imports,
|
||||
provides: provides.entries,
|
||||
to: provides.to,
|
||||
after_app_keyword,
|
||||
before_provides,
|
||||
after_provides,
|
||||
before_packages,
|
||||
after_packages,
|
||||
before_imports,
|
||||
after_imports,
|
||||
before_provides: provides.before_provides_keyword,
|
||||
after_provides: provides.after_provides_keyword,
|
||||
before_to: provides.before_to_keyword,
|
||||
after_to: provides.after_to_keyword,
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn platform_header<'a>() -> impl Parser<'a, PlatformHeader<'a>> {
|
||||
pub fn platform_header<'a>() -> impl Parser<'a, PlatformHeader<'a>> {
|
||||
parser::map(
|
||||
and!(
|
||||
skip_first!(
|
||||
ascii_string("platform"),
|
||||
and!(space1(1), loc!(package_name()))
|
||||
),
|
||||
and!(provides(), and!(requires(), and!(imports(), effects())))
|
||||
and!(
|
||||
and!(
|
||||
and!(requires(), and!(exposes_modules(), packages())),
|
||||
and!(imports(), provides_without_to())
|
||||
),
|
||||
effects()
|
||||
)
|
||||
),
|
||||
|(
|
||||
(after_platform_keyword, name),
|
||||
(
|
||||
((before_provides, after_provides), provides),
|
||||
(
|
||||
((before_requires, after_requires), requires),
|
||||
(((before_imports, after_imports), imports), effects),
|
||||
(
|
||||
((before_requires, after_requires), requires),
|
||||
(((before_exposes, after_exposes), exposes), packages),
|
||||
),
|
||||
(
|
||||
((before_imports, after_imports), imports),
|
||||
((before_provides, after_provides), provides),
|
||||
),
|
||||
),
|
||||
effects,
|
||||
),
|
||||
)| {
|
||||
PlatformHeader {
|
||||
name,
|
||||
provides,
|
||||
requires,
|
||||
exposes,
|
||||
packages: packages.entries,
|
||||
imports,
|
||||
provides,
|
||||
effects,
|
||||
after_platform_keyword,
|
||||
before_provides,
|
||||
after_provides,
|
||||
before_requires,
|
||||
after_requires,
|
||||
before_exposes,
|
||||
after_exposes,
|
||||
before_packages: packages.before_packages_keyword,
|
||||
after_packages: packages.after_packages_keyword,
|
||||
before_imports,
|
||||
after_imports,
|
||||
before_provides,
|
||||
after_provides,
|
||||
}
|
||||
},
|
||||
)
|
||||
@ -243,19 +293,80 @@ pub fn module_defs<'a>() -> impl Parser<'a, Vec<'a, Located<Def<'a>>>> {
|
||||
zero_or_more!(space0_around(loc(def(0)), 0))
|
||||
}
|
||||
|
||||
struct ProvidesTo<'a> {
|
||||
entries: Vec<'a, Located<ExposesEntry<'a, &'a str>>>,
|
||||
to: Located<To<'a>>,
|
||||
|
||||
before_provides_keyword: &'a [CommentOrNewline<'a>],
|
||||
after_provides_keyword: &'a [CommentOrNewline<'a>],
|
||||
before_to_keyword: &'a [CommentOrNewline<'a>],
|
||||
after_to_keyword: &'a [CommentOrNewline<'a>],
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn provides<'a>() -> impl Parser<
|
||||
fn provides_to<'a>() -> impl Parser<'a, ProvidesTo<'a>> {
|
||||
map!(
|
||||
and!(
|
||||
and!(skip_second!(space1(1), ascii_string("provides")), space1(1)),
|
||||
and!(
|
||||
collection!(
|
||||
ascii_char(b'['),
|
||||
loc!(map!(unqualified_ident(), ExposesEntry::Exposed)),
|
||||
ascii_char(b','),
|
||||
ascii_char(b']'),
|
||||
1
|
||||
),
|
||||
and!(
|
||||
space1(1),
|
||||
skip_first!(
|
||||
ascii_string("to"),
|
||||
and!(
|
||||
space1(1),
|
||||
loc!(either!(lowercase_ident(), package_or_path()))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
|(
|
||||
(before_provides_keyword, after_provides_keyword),
|
||||
(entries, (before_to_keyword, (after_to_keyword, loc_to))),
|
||||
)| {
|
||||
let loc_to: Located<Either<&'a str, PackageOrPath<'a>>> = loc_to;
|
||||
let to_val = match loc_to.value {
|
||||
Either::First(pkg) => To::ExistingPackage(pkg),
|
||||
Either::Second(pkg) => To::NewPackage(pkg),
|
||||
};
|
||||
let to = Located {
|
||||
value: to_val,
|
||||
region: loc_to.region,
|
||||
};
|
||||
|
||||
ProvidesTo {
|
||||
entries,
|
||||
to,
|
||||
before_provides_keyword,
|
||||
after_provides_keyword,
|
||||
before_to_keyword,
|
||||
after_to_keyword,
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn provides_without_to<'a>() -> impl Parser<
|
||||
'a,
|
||||
(
|
||||
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
|
||||
Vec<'a, Located<ExposesEntry<'a>>>,
|
||||
Vec<'a, Located<ExposesEntry<'a, &'a str>>>,
|
||||
),
|
||||
> {
|
||||
and!(
|
||||
and!(skip_second!(space1(1), ascii_string("provides")), space1(1)),
|
||||
collection!(
|
||||
ascii_char(b'['),
|
||||
loc!(exposes_entry()),
|
||||
loc!(map!(unqualified_ident(), ExposesEntry::Exposed)),
|
||||
ascii_char(b','),
|
||||
ascii_char(b']'),
|
||||
1
|
||||
@ -284,18 +395,18 @@ fn requires<'a>() -> impl Parser<
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn exposes<'a>() -> impl Parser<
|
||||
fn exposes_values<'a>() -> impl Parser<
|
||||
'a,
|
||||
(
|
||||
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
|
||||
Vec<'a, Located<ExposesEntry<'a>>>,
|
||||
Vec<'a, Located<ExposesEntry<'a, &'a str>>>,
|
||||
),
|
||||
> {
|
||||
and!(
|
||||
and!(skip_second!(space1(1), ascii_string("exposes")), space1(1)),
|
||||
collection!(
|
||||
ascii_char(b'['),
|
||||
loc!(exposes_entry()),
|
||||
loc!(map!(unqualified_ident(), ExposesEntry::Exposed)),
|
||||
ascii_char(b','),
|
||||
ascii_char(b']'),
|
||||
1
|
||||
@ -303,6 +414,56 @@ fn exposes<'a>() -> impl Parser<
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn exposes_modules<'a>() -> impl Parser<
|
||||
'a,
|
||||
(
|
||||
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
|
||||
Vec<'a, Located<ExposesEntry<'a, ModuleName<'a>>>>,
|
||||
),
|
||||
> {
|
||||
and!(
|
||||
and!(skip_second!(space1(1), ascii_string("exposes")), space1(1)),
|
||||
collection!(
|
||||
ascii_char(b'['),
|
||||
loc!(map!(module_name(), ExposesEntry::Exposed)),
|
||||
ascii_char(b','),
|
||||
ascii_char(b']'),
|
||||
1
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
struct Packages<'a> {
|
||||
entries: Vec<'a, Located<PackageEntry<'a>>>,
|
||||
|
||||
before_packages_keyword: &'a [CommentOrNewline<'a>],
|
||||
after_packages_keyword: &'a [CommentOrNewline<'a>],
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn packages<'a>() -> impl Parser<'a, Packages<'a>> {
|
||||
map!(
|
||||
and!(
|
||||
and!(skip_second!(space1(1), ascii_string("packages")), space1(1)),
|
||||
collection!(
|
||||
ascii_char(b'{'),
|
||||
loc!(package_entry()),
|
||||
ascii_char(b','),
|
||||
ascii_char(b'}'),
|
||||
1
|
||||
)
|
||||
),
|
||||
|((before_packages_keyword, after_packages_keyword), entries)| {
|
||||
Packages {
|
||||
entries,
|
||||
before_packages_keyword,
|
||||
after_packages_keyword,
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn imports<'a>() -> impl Parser<
|
||||
'a,
|
||||
@ -383,22 +544,22 @@ fn typed_ident<'a>() -> impl Parser<'a, TypedIdent<'a>> {
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn exposes_entry<'a>() -> impl Parser<'a, ExposesEntry<'a>> {
|
||||
map!(unqualified_ident(), ExposesEntry::Ident)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn imports_entry<'a>() -> impl Parser<'a, ImportsEntry<'a>> {
|
||||
map_with_arena!(
|
||||
and!(
|
||||
// e.g. `Task`
|
||||
module_name(),
|
||||
and!(
|
||||
// e.g. `base.`
|
||||
optional(skip_second!(lowercase_ident(), ascii_char(b'.'))),
|
||||
// e.g. `Task`
|
||||
module_name()
|
||||
),
|
||||
// e.g. `.{ Task, after}`
|
||||
optional(skip_first!(
|
||||
ascii_char(b'.'),
|
||||
collection!(
|
||||
ascii_char(b'{'),
|
||||
loc!(exposes_entry()),
|
||||
loc!(map!(unqualified_ident(), ExposesEntry::Exposed)),
|
||||
ascii_char(b','),
|
||||
ascii_char(b'}'),
|
||||
1
|
||||
@ -406,13 +567,17 @@ fn imports_entry<'a>() -> impl Parser<'a, ImportsEntry<'a>> {
|
||||
))
|
||||
),
|
||||
|arena,
|
||||
(module_name, opt_values): (
|
||||
ModuleName<'a>,
|
||||
Option<Vec<'a, Located<ExposesEntry<'a>>>>
|
||||
((opt_shortname, module_name), opt_values): (
|
||||
(Option<&'a str>, ModuleName<'a>),
|
||||
Option<Vec<'a, Located<ExposesEntry<'a, &'a str>>>>
|
||||
)| {
|
||||
let exposed_values = opt_values.unwrap_or_else(|| Vec::new_in(arena));
|
||||
|
||||
ImportsEntry::Module(module_name, exposed_values)
|
||||
match opt_shortname {
|
||||
Some(shortname) => ImportsEntry::Package(shortname, module_name, exposed_values),
|
||||
|
||||
None => ImportsEntry::Module(module_name, exposed_values),
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -1037,7 +1037,11 @@ macro_rules! one_or_more {
|
||||
}
|
||||
}
|
||||
}
|
||||
Err((_, new_state)) => Err(unexpected_eof(0, new_state.attempting, new_state)),
|
||||
Err((_, new_state)) => Err($crate::parser::unexpected_eof(
|
||||
0,
|
||||
new_state.attempting,
|
||||
new_state,
|
||||
)),
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -1083,9 +1087,9 @@ macro_rules! either {
|
||||
let original_attempting = state.attempting;
|
||||
|
||||
match $p1.parse(arena, state) {
|
||||
Ok((output, state)) => Ok((Either::First(output), state)),
|
||||
Ok((output, state)) => Ok(($crate::parser::Either::First(output), state)),
|
||||
Err((_, state)) => match $p2.parse(arena, state) {
|
||||
Ok((output, state)) => Ok((Either::Second(output), state)),
|
||||
Ok((output, state)) => Ok(($crate::parser::Either::Second(output), state)),
|
||||
Err((fail, state)) => Err((
|
||||
Fail {
|
||||
attempting: original_attempting,
|
||||
|
@ -12,10 +12,10 @@ pub fn parse_expr_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Expr<
|
||||
parse_loc_with(arena, input).map(|loc_expr| loc_expr.value)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn parse_header_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Module<'a>, Fail> {
|
||||
let state = State::new(input.trim().as_bytes(), Attempting::Module);
|
||||
let answer = header().parse(arena, state);
|
||||
|
||||
answer
|
||||
.map(|(loc_expr, _)| loc_expr)
|
||||
.map_err(|(fail, _)| fail)
|
||||
|
@ -21,13 +21,16 @@ mod test_parse {
|
||||
use roc_parse::ast::CommentOrNewline::*;
|
||||
use roc_parse::ast::Expr::{self, *};
|
||||
use roc_parse::ast::Pattern::{self, *};
|
||||
use roc_parse::ast::StrLiteral::*;
|
||||
use roc_parse::ast::StrLiteral::{self, *};
|
||||
use roc_parse::ast::StrSegment::*;
|
||||
use roc_parse::ast::{
|
||||
self, Attempting, Def, EscapedChar, InterfaceHeader, Spaceable, TypeAnnotation, WhenBranch,
|
||||
self, Attempting, Def, EscapedChar, Spaceable, TypeAnnotation, WhenBranch,
|
||||
};
|
||||
use roc_parse::header::ModuleName;
|
||||
use roc_parse::module::{interface_header, module_defs};
|
||||
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::parser::{Fail, FailReason, Parser, State};
|
||||
use roc_parse::test_helpers::parse_expr_with;
|
||||
use roc_region::all::{Located, Region};
|
||||
@ -1733,6 +1736,83 @@ mod test_parse {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiline_type_signature() {
|
||||
assert_parses_to(
|
||||
"f :\n {}\n\n42",
|
||||
Defs(
|
||||
&[&Located::new(
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
6,
|
||||
Def::Annotation(
|
||||
Located::new(0, 0, 0, 1, Pattern::Identifier("f")),
|
||||
Located::new(
|
||||
1,
|
||||
1,
|
||||
4,
|
||||
6,
|
||||
TypeAnnotation::SpaceBefore(
|
||||
&TypeAnnotation::Record {
|
||||
fields: &[],
|
||||
ext: None,
|
||||
final_comments: &[],
|
||||
},
|
||||
&[Newline],
|
||||
),
|
||||
),
|
||||
),
|
||||
)],
|
||||
&Located::new(
|
||||
3,
|
||||
3,
|
||||
0,
|
||||
2,
|
||||
Expr::SpaceBefore(&Expr::Num("42"), &[Newline, Newline]),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiline_type_signature_with_comment() {
|
||||
assert_parses_to(
|
||||
"f :# comment\n {}\n\n42",
|
||||
Defs(
|
||||
&[&Located::new(
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
6,
|
||||
Def::Annotation(
|
||||
Located::new(0, 0, 0, 1, Pattern::Identifier("f")),
|
||||
Located::new(
|
||||
1,
|
||||
1,
|
||||
4,
|
||||
6,
|
||||
TypeAnnotation::SpaceBefore(
|
||||
&TypeAnnotation::Record {
|
||||
fields: &[],
|
||||
ext: None,
|
||||
final_comments: &[],
|
||||
},
|
||||
&[LineComment(" comment")],
|
||||
),
|
||||
),
|
||||
),
|
||||
)],
|
||||
&Located::new(
|
||||
3,
|
||||
3,
|
||||
0,
|
||||
2,
|
||||
Expr::SpaceBefore(&Expr::Num("42"), &[Newline, Newline]),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
// #[test]
|
||||
// fn type_signature_function_def() {
|
||||
// use TypeAnnotation;
|
||||
@ -2200,7 +2280,243 @@ mod test_parse {
|
||||
// MODULE
|
||||
|
||||
#[test]
|
||||
fn empty_module() {
|
||||
fn empty_app_header() {
|
||||
let arena = Bump::new();
|
||||
let packages = Vec::new_in(&arena);
|
||||
let imports = Vec::new_in(&arena);
|
||||
let provides = Vec::new_in(&arena);
|
||||
let module_name = StrLiteral::PlainLine("test-app");
|
||||
let expected = AppHeader {
|
||||
name: Located::new(0, 0, 4, 14, module_name),
|
||||
packages,
|
||||
imports,
|
||||
provides,
|
||||
to: Located::new(0, 0, 53, 57, To::ExistingPackage("blah")),
|
||||
after_app_keyword: &[],
|
||||
before_packages: &[],
|
||||
after_packages: &[],
|
||||
before_imports: &[],
|
||||
after_imports: &[],
|
||||
before_provides: &[],
|
||||
after_provides: &[],
|
||||
before_to: &[],
|
||||
after_to: &[],
|
||||
};
|
||||
|
||||
let src = indoc!(
|
||||
r#"
|
||||
app "test-app" packages {} imports [] provides [] to blah
|
||||
"#
|
||||
);
|
||||
let actual = app_header()
|
||||
.parse(&arena, State::new(src.as_bytes(), Attempting::Module))
|
||||
.map(|tuple| tuple.0);
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn minimal_app_header() {
|
||||
use PackageOrPath::Path;
|
||||
|
||||
let arena = Bump::new();
|
||||
let packages = Vec::new_in(&arena);
|
||||
let imports = Vec::new_in(&arena);
|
||||
let provides = Vec::new_in(&arena);
|
||||
let module_name = StrLiteral::PlainLine("test-app");
|
||||
let expected = AppHeader {
|
||||
name: Located::new(0, 0, 4, 14, module_name),
|
||||
packages,
|
||||
imports,
|
||||
provides,
|
||||
to: Located::new(0, 0, 30, 38, To::NewPackage(Path(PlainLine("./blah")))),
|
||||
after_app_keyword: &[],
|
||||
before_packages: &[],
|
||||
after_packages: &[],
|
||||
before_imports: &[],
|
||||
after_imports: &[],
|
||||
before_provides: &[],
|
||||
after_provides: &[],
|
||||
before_to: &[],
|
||||
after_to: &[],
|
||||
};
|
||||
|
||||
let src = indoc!(
|
||||
r#"
|
||||
app "test-app" provides [] to "./blah"
|
||||
"#
|
||||
);
|
||||
let actual = app_header()
|
||||
.parse(&arena, State::new(src.as_bytes(), Attempting::Module))
|
||||
.map(|tuple| tuple.0);
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_app_header() {
|
||||
use ExposesEntry::Exposed;
|
||||
use PackageOrPath::Path;
|
||||
|
||||
let newlines = &[Newline];
|
||||
let pkg_entry = PackageEntry::Entry {
|
||||
shorthand: "base",
|
||||
spaces_after_shorthand: &[],
|
||||
package_or_path: Located::new(1, 1, 21, 33, Path(PlainLine("./platform"))),
|
||||
};
|
||||
let loc_pkg_entry = Located::new(1, 1, 15, 33, pkg_entry);
|
||||
let arena = Bump::new();
|
||||
let packages = bumpalo::vec![in &arena; loc_pkg_entry];
|
||||
let import = ImportsEntry::Package("foo", ModuleName::new("Bar.Baz"), Vec::new_in(&arena));
|
||||
let loc_import = Located::new(2, 2, 14, 25, import);
|
||||
let imports = bumpalo::vec![in &arena; loc_import];
|
||||
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 {
|
||||
name: Located::new(0, 0, 4, 15, module_name),
|
||||
packages,
|
||||
imports,
|
||||
provides,
|
||||
to: Located::new(3, 3, 30, 34, To::ExistingPackage("base")),
|
||||
after_app_keyword: &[],
|
||||
before_packages: newlines,
|
||||
after_packages: &[],
|
||||
before_imports: newlines,
|
||||
after_imports: &[],
|
||||
before_provides: newlines,
|
||||
after_provides: &[],
|
||||
before_to: &[],
|
||||
after_to: &[],
|
||||
};
|
||||
|
||||
let src = indoc!(
|
||||
r#"
|
||||
app "quicksort"
|
||||
packages { base: "./platform" }
|
||||
imports [ foo.Bar.Baz ]
|
||||
provides [ quicksort ] to base
|
||||
"#
|
||||
);
|
||||
let actual = app_header()
|
||||
.parse(&arena, State::new(src.as_bytes(), Attempting::Module))
|
||||
.map(|tuple| tuple.0);
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_platform_header() {
|
||||
let pkg_name = PackageName {
|
||||
account: "rtfeldman",
|
||||
pkg: "blah",
|
||||
};
|
||||
let arena = Bump::new();
|
||||
let effects = Effects {
|
||||
type_name: "Blah",
|
||||
entries: Vec::new_in(&arena),
|
||||
spaces_before_effects_keyword: &[],
|
||||
spaces_after_effects_keyword: &[],
|
||||
spaces_after_type_name: &[],
|
||||
};
|
||||
let expected = PlatformHeader {
|
||||
name: Located::new(0, 0, 9, 23, pkg_name),
|
||||
requires: Vec::new_in(&arena),
|
||||
exposes: Vec::new_in(&arena),
|
||||
packages: Vec::new_in(&arena),
|
||||
imports: Vec::new_in(&arena),
|
||||
provides: Vec::new_in(&arena),
|
||||
effects,
|
||||
after_platform_keyword: &[],
|
||||
before_requires: &[],
|
||||
after_requires: &[],
|
||||
before_exposes: &[],
|
||||
after_exposes: &[],
|
||||
before_packages: &[],
|
||||
after_packages: &[],
|
||||
before_imports: &[],
|
||||
after_imports: &[],
|
||||
before_provides: &[],
|
||||
after_provides: &[],
|
||||
};
|
||||
|
||||
let src = "platform rtfeldman/blah requires {} exposes [] packages {} imports [] provides [] effects Blah {}";
|
||||
let actual = platform_header()
|
||||
.parse(&arena, State::new(src.as_bytes(), Attempting::Module))
|
||||
.map(|tuple| tuple.0);
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nonempty_platform_header() {
|
||||
use ExposesEntry::Exposed;
|
||||
use PackageOrPath::Path;
|
||||
|
||||
let newlines = &[Newline];
|
||||
let pkg_name = PackageName {
|
||||
account: "foo",
|
||||
pkg: "barbaz",
|
||||
};
|
||||
let pkg_entry = PackageEntry::Entry {
|
||||
shorthand: "foo",
|
||||
spaces_after_shorthand: &[],
|
||||
package_or_path: Located::new(3, 3, 20, 27, Path(PlainLine("./foo"))),
|
||||
};
|
||||
let loc_pkg_entry = Located::new(3, 3, 15, 27, pkg_entry);
|
||||
let arena = Bump::new();
|
||||
let packages = bumpalo::vec![in &arena; loc_pkg_entry];
|
||||
let imports = Vec::new_in(&arena);
|
||||
let provide_entry = Located::new(5, 5, 15, 26, Exposed("mainForHost"));
|
||||
let provides = bumpalo::vec![in &arena; provide_entry];
|
||||
let effects = Effects {
|
||||
type_name: "Effect",
|
||||
entries: Vec::new_in(&arena),
|
||||
spaces_before_effects_keyword: newlines,
|
||||
spaces_after_effects_keyword: &[],
|
||||
spaces_after_type_name: &[],
|
||||
};
|
||||
let expected = PlatformHeader {
|
||||
name: Located::new(0, 0, 9, 19, pkg_name),
|
||||
requires: Vec::new_in(&arena),
|
||||
exposes: Vec::new_in(&arena),
|
||||
packages,
|
||||
imports,
|
||||
provides,
|
||||
effects,
|
||||
after_platform_keyword: &[],
|
||||
before_requires: newlines,
|
||||
after_requires: &[],
|
||||
before_exposes: newlines,
|
||||
after_exposes: &[],
|
||||
before_packages: newlines,
|
||||
after_packages: &[],
|
||||
before_imports: newlines,
|
||||
after_imports: &[],
|
||||
before_provides: newlines,
|
||||
after_provides: &[],
|
||||
};
|
||||
|
||||
let src = indoc!(
|
||||
r#"
|
||||
platform foo/barbaz
|
||||
requires {}
|
||||
exposes []
|
||||
packages { foo: "./foo" }
|
||||
imports []
|
||||
provides [ mainForHost ]
|
||||
effects Effect {}
|
||||
"#
|
||||
);
|
||||
let actual = platform_header()
|
||||
.parse(&arena, State::new(src.as_bytes(), Attempting::Module))
|
||||
.map(|tuple| tuple.0);
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_interface_header() {
|
||||
let arena = Bump::new();
|
||||
let exposes = Vec::new_in(&arena);
|
||||
let imports = Vec::new_in(&arena);
|
||||
|
@ -1624,8 +1624,8 @@ fn to_diff<'b>(
|
||||
_ => false,
|
||||
};
|
||||
let is_float = |t: &ErrorType| match t {
|
||||
ErrorType::Type(Symbol::NUM_FLOAT, _) => true,
|
||||
ErrorType::Alias(Symbol::NUM_FLOAT, _, _) => true,
|
||||
ErrorType::Type(Symbol::NUM_F64, _) => true,
|
||||
ErrorType::Alias(Symbol::NUM_F64, _, _) => true,
|
||||
|
||||
_ => false,
|
||||
};
|
||||
|
@ -1054,7 +1054,7 @@ mod test_reporting {
|
||||
|
||||
This `Blue` global tag application has the type:
|
||||
|
||||
[ Blue Float ]a
|
||||
[ Blue F64 ]a
|
||||
|
||||
But `f` needs the 1st argument to be:
|
||||
|
||||
@ -1092,7 +1092,7 @@ mod test_reporting {
|
||||
|
||||
The 1st branch is a float of type:
|
||||
|
||||
Float
|
||||
F64
|
||||
|
||||
But the type annotation on `x` says it should be:
|
||||
|
||||
@ -1131,7 +1131,7 @@ mod test_reporting {
|
||||
|
||||
This `when`expression produces:
|
||||
|
||||
Float
|
||||
F64
|
||||
|
||||
But the type annotation on `x` says it should be:
|
||||
|
||||
@ -1167,7 +1167,7 @@ mod test_reporting {
|
||||
|
||||
The body is a float of type:
|
||||
|
||||
Float
|
||||
F64
|
||||
|
||||
But the type annotation on `x` says it should be:
|
||||
|
||||
@ -1373,8 +1373,8 @@ mod test_reporting {
|
||||
|
||||
Bool
|
||||
Int
|
||||
F64
|
||||
Num
|
||||
Map
|
||||
"#
|
||||
),
|
||||
)
|
||||
@ -1501,7 +1501,7 @@ mod test_reporting {
|
||||
|
||||
The body is a record of type:
|
||||
|
||||
{ x : Float }
|
||||
{ x : F64 }
|
||||
|
||||
But the type annotation says it should be:
|
||||
|
||||
@ -1644,7 +1644,7 @@ mod test_reporting {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
x : { a : Int, b : Float, c : Bool }
|
||||
x : { a : Int, b : F64, c : Bool }
|
||||
x = { b: 4.0 }
|
||||
|
||||
x
|
||||
@ -1656,17 +1656,17 @@ mod test_reporting {
|
||||
|
||||
Something is off with the body of the `x` definition:
|
||||
|
||||
1│ x : { a : Int, b : Float, c : Bool }
|
||||
1│ x : { a : Int, b : F64, c : Bool }
|
||||
2│ x = { b: 4.0 }
|
||||
^^^^^^^^^^
|
||||
|
||||
The body is a record of type:
|
||||
|
||||
{ b : Float }
|
||||
{ b : F64 }
|
||||
|
||||
But the type annotation on `x` says it should be:
|
||||
|
||||
{ a : Int, b : Float, c : Bool }
|
||||
{ a : Int, b : F64, c : Bool }
|
||||
|
||||
Tip: Looks like the c and a fields are missing.
|
||||
"#
|
||||
@ -1829,8 +1829,8 @@ mod test_reporting {
|
||||
|
||||
f
|
||||
Int
|
||||
F64
|
||||
Num
|
||||
Map
|
||||
"#
|
||||
),
|
||||
)
|
||||
@ -2119,7 +2119,7 @@ mod test_reporting {
|
||||
|
||||
This argument is a float of type:
|
||||
|
||||
Float
|
||||
F64
|
||||
|
||||
But `add` needs the 2nd argument to be:
|
||||
|
||||
@ -2601,7 +2601,7 @@ mod test_reporting {
|
||||
|
||||
This argument is a record of type:
|
||||
|
||||
{ y : Float }
|
||||
{ y : F64 }
|
||||
|
||||
But `f` needs the 1st argument to be:
|
||||
|
||||
@ -2835,7 +2835,7 @@ mod test_reporting {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
a : { foo : Int, bar : Float, foo : Str }
|
||||
a : { foo : Int, bar : F64, foo : Str }
|
||||
a = { bar: 3.0, foo: "foo" }
|
||||
|
||||
a
|
||||
@ -2847,13 +2847,13 @@ mod test_reporting {
|
||||
|
||||
This record type defines the `.foo` field twice!
|
||||
|
||||
1│ a : { foo : Int, bar : Float, foo : Str }
|
||||
^^^^^^^^^ ^^^^^^^^^
|
||||
1│ a : { foo : Int, bar : F64, foo : Str }
|
||||
^^^^^^^^^ ^^^^^^^^^
|
||||
|
||||
In the rest of the program, I will only use the latter definition:
|
||||
|
||||
1│ a : { foo : Int, bar : Float, foo : Str }
|
||||
^^^^^^^^^
|
||||
1│ a : { foo : Int, bar : F64, foo : Str }
|
||||
^^^^^^^^^
|
||||
|
||||
For clarity, remove the previous `.foo` definitions from this record
|
||||
type.
|
||||
@ -2867,7 +2867,7 @@ mod test_reporting {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
a : [ Foo Int, Bar Float, Foo Str ]
|
||||
a : [ Foo Int, Bar F64, Foo Str ]
|
||||
a = Foo "foo"
|
||||
|
||||
a
|
||||
@ -2879,13 +2879,13 @@ mod test_reporting {
|
||||
|
||||
This tag union type defines the `Foo` tag twice!
|
||||
|
||||
1│ a : [ Foo Int, Bar Float, Foo Str ]
|
||||
^^^^^^^ ^^^^^^^
|
||||
1│ a : [ Foo Int, Bar F64, Foo Str ]
|
||||
^^^^^^^ ^^^^^^^
|
||||
|
||||
In the rest of the program, I will only use the latter definition:
|
||||
|
||||
1│ a : [ Foo Int, Bar Float, Foo Str ]
|
||||
^^^^^^^
|
||||
1│ a : [ Foo Int, Bar F64, Foo Str ]
|
||||
^^^^^^^
|
||||
|
||||
For clarity, remove the previous `Foo` definitions from this tag union
|
||||
type.
|
||||
@ -2978,7 +2978,7 @@ mod test_reporting {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
a : Num Int Float
|
||||
a : Num Int F64
|
||||
a = 3
|
||||
|
||||
a
|
||||
@ -2990,8 +2990,8 @@ mod test_reporting {
|
||||
|
||||
The `Num` alias expects 1 type argument, but it got 2 instead:
|
||||
|
||||
1│ a : Num Int Float
|
||||
^^^^^^^^^^^^^
|
||||
1│ a : Num Int F64
|
||||
^^^^^^^^^^^
|
||||
|
||||
Are there missing parentheses?
|
||||
"#
|
||||
@ -3004,7 +3004,7 @@ mod test_reporting {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
f : Bool -> Num Int Float
|
||||
f : Bool -> Num Int F64
|
||||
f = \_ -> 3
|
||||
|
||||
f
|
||||
@ -3016,8 +3016,8 @@ mod test_reporting {
|
||||
|
||||
The `Num` alias expects 1 type argument, but it got 2 instead:
|
||||
|
||||
1│ f : Bool -> Num Int Float
|
||||
^^^^^^^^^^^^^
|
||||
1│ f : Bool -> Num Int F64
|
||||
^^^^^^^^^^^
|
||||
|
||||
Are there missing parentheses?
|
||||
"#
|
||||
|
@ -128,7 +128,8 @@ mod solve_expr {
|
||||
}
|
||||
|
||||
fn promote_expr_to_module(src: &str) -> String {
|
||||
let mut buffer = String::from("app Test provides [ main ] imports []\n\nmain =\n");
|
||||
let mut buffer =
|
||||
String::from("app \"test\" provides [ main ] to \"./platform\"\n\nmain =\n");
|
||||
|
||||
for line in src.lines() {
|
||||
// indent the body!
|
||||
@ -168,7 +169,7 @@ mod solve_expr {
|
||||
|
||||
#[test]
|
||||
fn float_literal() {
|
||||
infer_eq("0.5", "Float");
|
||||
infer_eq("0.5", "F64");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -761,7 +762,7 @@ mod solve_expr {
|
||||
(\a -> a) 3.14
|
||||
"#
|
||||
),
|
||||
"Float",
|
||||
"F64",
|
||||
);
|
||||
}
|
||||
|
||||
@ -893,7 +894,7 @@ mod solve_expr {
|
||||
// \l r -> l / r
|
||||
// "#
|
||||
// ),
|
||||
// "Float, Float -> Float",
|
||||
// "F64, F64 -> F64",
|
||||
// );
|
||||
// }
|
||||
|
||||
@ -905,7 +906,7 @@ mod solve_expr {
|
||||
// 1 / 2
|
||||
// "#
|
||||
// ),
|
||||
// "Float",
|
||||
// "F64",
|
||||
// );
|
||||
// }
|
||||
|
||||
@ -1025,7 +1026,7 @@ mod solve_expr {
|
||||
|
||||
#[test]
|
||||
fn two_field_record() {
|
||||
infer_eq("{ x: 5, y : 3.14 }", "{ x : Num *, y : Float }");
|
||||
infer_eq("{ x: 5, y : 3.14 }", "{ x : Num *, y : F64 }");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1413,12 +1414,12 @@ mod solve_expr {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
float : Float
|
||||
float : F64
|
||||
|
||||
float
|
||||
"#
|
||||
),
|
||||
"Float",
|
||||
"F64",
|
||||
);
|
||||
}
|
||||
|
||||
@ -1432,7 +1433,7 @@ mod solve_expr {
|
||||
float
|
||||
"#
|
||||
),
|
||||
"Float",
|
||||
"F64",
|
||||
);
|
||||
}
|
||||
|
||||
@ -1441,13 +1442,13 @@ mod solve_expr {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
float : Num.Float
|
||||
float : Num.F64
|
||||
float = 5.5
|
||||
|
||||
float
|
||||
"#
|
||||
),
|
||||
"Float",
|
||||
"F64",
|
||||
);
|
||||
}
|
||||
|
||||
@ -1456,13 +1457,13 @@ mod solve_expr {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
float : Float
|
||||
float : F64
|
||||
float = 5.5
|
||||
|
||||
float
|
||||
"#
|
||||
),
|
||||
"Float",
|
||||
"F64",
|
||||
);
|
||||
}
|
||||
|
||||
@ -1477,7 +1478,7 @@ mod solve_expr {
|
||||
float
|
||||
"#
|
||||
),
|
||||
"Float",
|
||||
"F64",
|
||||
);
|
||||
}
|
||||
|
||||
@ -1577,7 +1578,7 @@ mod solve_expr {
|
||||
float
|
||||
"#
|
||||
),
|
||||
"Float",
|
||||
"F64",
|
||||
);
|
||||
}
|
||||
|
||||
@ -1612,7 +1613,7 @@ mod solve_expr {
|
||||
{ numIdentity, x : numIdentity 42, y }
|
||||
"#
|
||||
),
|
||||
"{ numIdentity : Num a -> Num a, x : Num a, y : Float }",
|
||||
"{ numIdentity : Num a -> Num a, x : Num a, y : F64 }",
|
||||
);
|
||||
}
|
||||
|
||||
@ -1790,7 +1791,7 @@ mod solve_expr {
|
||||
threePointZero
|
||||
"#
|
||||
),
|
||||
"Float",
|
||||
"F64",
|
||||
);
|
||||
}
|
||||
|
||||
@ -2054,7 +2055,7 @@ mod solve_expr {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
Peano : [ S Peano, Z ]
|
||||
|
||||
@ -2138,7 +2139,7 @@ mod solve_expr {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
ConsList a : [ Cons a (ConsList a), Nil ]
|
||||
|
||||
@ -2194,7 +2195,7 @@ mod solve_expr {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
map =
|
||||
\peano ->
|
||||
@ -2522,7 +2523,7 @@ mod solve_expr {
|
||||
Num.toFloat
|
||||
"#
|
||||
),
|
||||
"Num * -> Float",
|
||||
"Num * -> F64",
|
||||
);
|
||||
}
|
||||
|
||||
@ -2534,7 +2535,7 @@ mod solve_expr {
|
||||
Num.pow
|
||||
"#
|
||||
),
|
||||
"Float, Float -> Float",
|
||||
"F64, F64 -> F64",
|
||||
);
|
||||
}
|
||||
|
||||
@ -2546,7 +2547,7 @@ mod solve_expr {
|
||||
Num.ceiling
|
||||
"#
|
||||
),
|
||||
"Float -> Int",
|
||||
"F64 -> Int",
|
||||
);
|
||||
}
|
||||
|
||||
@ -2558,7 +2559,7 @@ mod solve_expr {
|
||||
Num.floor
|
||||
"#
|
||||
),
|
||||
"Float -> Int",
|
||||
"F64 -> Int",
|
||||
);
|
||||
}
|
||||
|
||||
@ -2582,7 +2583,7 @@ mod solve_expr {
|
||||
Num.atan
|
||||
"#
|
||||
),
|
||||
"Float -> Float",
|
||||
"F64 -> F64",
|
||||
);
|
||||
}
|
||||
|
||||
@ -2632,7 +2633,7 @@ mod solve_expr {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
boom = \_ -> boom {}
|
||||
|
||||
@ -2849,7 +2850,7 @@ mod solve_expr {
|
||||
negatePoint { x: 1, y: 2.1, z: 0x3 }
|
||||
"#
|
||||
),
|
||||
"{ x : Num a, y : Float, z : Int }",
|
||||
"{ x : Num a, y : F64, z : Int }",
|
||||
);
|
||||
}
|
||||
|
||||
@ -2866,7 +2867,7 @@ mod solve_expr {
|
||||
{ a, b }
|
||||
"#
|
||||
),
|
||||
"{ a : { x : Num a, y : Float, z : c }, b : { blah : Str, x : Num a, y : Float, z : c } }",
|
||||
"{ a : { x : Num a, y : F64, z : c }, b : { blah : Str, x : Num a, y : F64, z : c } }",
|
||||
);
|
||||
}
|
||||
|
||||
@ -2927,11 +2928,11 @@ mod solve_expr {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_walk_right() {
|
||||
fn list_walk_backwards() {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
List.walkRight
|
||||
List.walkBackwards
|
||||
"#
|
||||
),
|
||||
"List a, (a, b -> b), b -> b",
|
||||
@ -2939,7 +2940,7 @@ mod solve_expr {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_walk_right_example() {
|
||||
fn list_walk_backwards_example() {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
@ -2947,7 +2948,7 @@ mod solve_expr {
|
||||
empty =
|
||||
[]
|
||||
|
||||
List.walkRight empty (\a, b -> a + b) 0
|
||||
List.walkBackwards empty (\a, b -> a + b) 0
|
||||
"#
|
||||
),
|
||||
"Int",
|
||||
@ -2978,7 +2979,7 @@ mod solve_expr {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
|
||||
main : List x
|
||||
@ -2998,7 +2999,7 @@ mod solve_expr {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
|
||||
main =
|
||||
@ -3019,7 +3020,7 @@ mod solve_expr {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
Bar : [ Bar ]
|
||||
Foo : [ Foo Bar Int, Empty ]
|
||||
@ -3045,7 +3046,7 @@ mod solve_expr {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
Foo : [ @Foo [ @Bar ] Int, @Empty ]
|
||||
|
||||
@ -3070,7 +3071,7 @@ mod solve_expr {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
State a : { count : Int, x : a }
|
||||
|
||||
@ -3095,7 +3096,7 @@ mod solve_expr {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
# The color of a node. Leaves are considered Black.
|
||||
NodeColor : [ Red, Black ]
|
||||
@ -3128,7 +3129,7 @@ mod solve_expr {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
Dict k : [ Node k (Dict k), Empty ]
|
||||
|
||||
@ -3153,7 +3154,7 @@ mod solve_expr {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
NodeColor : [ Red, Black ]
|
||||
|
||||
@ -3383,7 +3384,7 @@ mod solve_expr {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
Dict k : [ Node k (Dict k) (Dict k), Empty ]
|
||||
|
||||
@ -3423,7 +3424,7 @@ mod solve_expr {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
NodeColor : [ Red, Black ]
|
||||
|
||||
@ -3499,7 +3500,7 @@ mod solve_expr {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ partitionHelp ] imports []
|
||||
app "test" provides [ partitionHelp ] to "./platform"
|
||||
|
||||
swap : Int, Int, List a -> List a
|
||||
swap = \i, j, list ->
|
||||
@ -3537,7 +3538,7 @@ mod solve_expr {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
Dict k : [ Node k (Dict k) (Dict k), Empty ]
|
||||
|
||||
@ -3559,7 +3560,7 @@ mod solve_expr {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
Dict k : [ Node k (Dict k) (Dict k), Empty ]
|
||||
|
||||
@ -3583,7 +3584,7 @@ mod solve_expr {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
NodeColor : [ Red, Black ]
|
||||
|
||||
|
@ -75,7 +75,7 @@ mod solve_uniq_expr {
|
||||
|
||||
#[test]
|
||||
fn float_literal() {
|
||||
infer_eq("0.5", "Attr * Float");
|
||||
infer_eq("0.5", "Attr * F64");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -640,7 +640,7 @@ mod solve_uniq_expr {
|
||||
(\a -> a) 3.14
|
||||
"#
|
||||
),
|
||||
"Attr * Float",
|
||||
"Attr * F64",
|
||||
);
|
||||
}
|
||||
|
||||
@ -773,7 +773,7 @@ mod solve_uniq_expr {
|
||||
// \l r -> l / r
|
||||
// "#
|
||||
// ),
|
||||
// "Float, Float -> Float",
|
||||
// "F64, F64 -> F64",
|
||||
// );
|
||||
// }
|
||||
|
||||
@ -785,7 +785,7 @@ mod solve_uniq_expr {
|
||||
// 1 / 2
|
||||
// "#
|
||||
// ),
|
||||
// "Float",
|
||||
// "F64",
|
||||
// );
|
||||
// }
|
||||
|
||||
@ -1211,7 +1211,7 @@ mod solve_uniq_expr {
|
||||
{ numIdentity, p, q }
|
||||
"#
|
||||
),
|
||||
"Attr * { numIdentity : Attr Shared (Attr b (Num (Attr a p)) -> Attr b (Num (Attr a p))), p : Attr * (Num (Attr * p)), q : Attr * Float }"
|
||||
"Attr * { numIdentity : Attr Shared (Attr b (Num (Attr a p)) -> Attr b (Num (Attr a p))), p : Attr * (Num (Attr * p)), q : Attr * F64 }"
|
||||
);
|
||||
}
|
||||
|
||||
@ -1831,7 +1831,7 @@ mod solve_uniq_expr {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
Foo : { x : Str, y : Float }
|
||||
Foo : { x : Str, y : F64 }
|
||||
|
||||
{ x, y } : Foo
|
||||
{ x, y } = { x : "foo", y : 3.14 }
|
||||
@ -1848,7 +1848,7 @@ mod solve_uniq_expr {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
Foo : { x : Str, y : Float }
|
||||
Foo : { x : Str, y : F64 }
|
||||
|
||||
Bar : Foo
|
||||
|
||||
@ -2113,7 +2113,7 @@ mod solve_uniq_expr {
|
||||
Num.maxFloat / Num.maxFloat
|
||||
"#
|
||||
),
|
||||
"Attr * (Result (Attr * Float) (Attr * [ DivByZero ]*))",
|
||||
"Attr * (Result (Attr * F64) (Attr * [ DivByZero ]*))",
|
||||
);
|
||||
}
|
||||
|
||||
@ -2125,7 +2125,7 @@ mod solve_uniq_expr {
|
||||
3.0 / 4.0
|
||||
"#
|
||||
),
|
||||
"Attr * (Result (Attr * Float) (Attr * [ DivByZero ]*))",
|
||||
"Attr * (Result (Attr * F64) (Attr * [ DivByZero ]*))",
|
||||
);
|
||||
}
|
||||
|
||||
@ -2137,7 +2137,7 @@ mod solve_uniq_expr {
|
||||
3.0 / Num.maxFloat
|
||||
"#
|
||||
),
|
||||
"Attr * (Result (Attr * Float) (Attr * [ DivByZero ]*))",
|
||||
"Attr * (Result (Attr * F64) (Attr * [ DivByZero ]*))",
|
||||
);
|
||||
}
|
||||
|
||||
@ -2236,11 +2236,11 @@ mod solve_uniq_expr {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_walk_right_sum() {
|
||||
fn list_walk_backwards_sum() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
sum = \list -> List.walkRight list Num.add 0
|
||||
sum = \list -> List.walkBackwards list Num.add 0
|
||||
|
||||
sum
|
||||
"#
|
||||
@ -2321,11 +2321,11 @@ mod solve_uniq_expr {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_walk_right_reverse() {
|
||||
fn list_walk_backwards_reverse() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
reverse = \list -> List.walkRight list (\e, l -> List.append l e) []
|
||||
reverse = \list -> List.walkBackwards list (\e, l -> List.append l e) []
|
||||
|
||||
reverse
|
||||
"#
|
||||
@ -2772,11 +2772,11 @@ mod solve_uniq_expr {
|
||||
r#"
|
||||
Model position : { evaluated : Set position
|
||||
, openSet : Set position
|
||||
, costs : Map.Map position Float
|
||||
, costs : Map.Map position F64
|
||||
, cameFrom : Map.Map position position
|
||||
}
|
||||
|
||||
cheapestOpen : (position -> Float), Model position -> Result position [ KeyNotFound ]*
|
||||
cheapestOpen : (position -> F64), Model position -> Result position [ KeyNotFound ]*
|
||||
cheapestOpen = \costFunction, model ->
|
||||
|
||||
folder = \position, resSmallestSoFar ->
|
||||
@ -2802,7 +2802,7 @@ mod solve_uniq_expr {
|
||||
cheapestOpen
|
||||
"#
|
||||
),
|
||||
"Attr * (Attr * (Attr Shared position -> Attr * Float), Attr (* | * | a | b) (Model (Attr Shared position)) -> Attr * (Result (Attr Shared position) (Attr * [ KeyNotFound ]*)))"
|
||||
"Attr * (Attr * (Attr Shared position -> Attr * F64), Attr (* | * | a | b) (Model (Attr Shared position)) -> Attr * (Result (Attr Shared position) (Attr * [ KeyNotFound ]*)))"
|
||||
)
|
||||
});
|
||||
}
|
||||
@ -2815,7 +2815,7 @@ mod solve_uniq_expr {
|
||||
r#"
|
||||
Model position : { evaluated : Set position
|
||||
, openSet : Set position
|
||||
, costs : Map.Map position Float
|
||||
, costs : Map.Map position F64
|
||||
, cameFrom : Map.Map position position
|
||||
}
|
||||
|
||||
@ -2867,7 +2867,7 @@ mod solve_uniq_expr {
|
||||
r#"
|
||||
Model position : { evaluated : Set position
|
||||
, openSet : Set position
|
||||
, costs : Map.Map position Float
|
||||
, costs : Map.Map position F64
|
||||
, cameFrom : Map.Map position position
|
||||
}
|
||||
|
||||
@ -2881,7 +2881,7 @@ mod solve_uniq_expr {
|
||||
}
|
||||
|
||||
|
||||
cheapestOpen : (position -> Float), Model position -> Result position [ KeyNotFound ]*
|
||||
cheapestOpen : (position -> F64), Model position -> Result position [ KeyNotFound ]*
|
||||
cheapestOpen = \costFunction, model ->
|
||||
|
||||
folder = \position, resSmallestSoFar ->
|
||||
@ -2941,12 +2941,12 @@ mod solve_uniq_expr {
|
||||
model
|
||||
|
||||
|
||||
findPath : { costFunction: (position, position -> Float), moveFunction: (position -> Set position), start : position, end : position } -> Result (List position) [ KeyNotFound ]*
|
||||
findPath : { costFunction: (position, position -> F64), moveFunction: (position -> Set position), start : position, end : position } -> Result (List position) [ KeyNotFound ]*
|
||||
findPath = \{ costFunction, moveFunction, start, end } ->
|
||||
astar costFunction moveFunction end (initialModel start)
|
||||
|
||||
|
||||
astar : (position, position -> Float), (position -> Set position), position, Model position -> [ Err [ KeyNotFound ]*, Ok (List position) ]*
|
||||
astar : (position, position -> F64), (position -> Set position), position, Model position -> [ Err [ KeyNotFound ]*, Ok (List position) ]*
|
||||
astar = \costFn, moveFn, goal, model ->
|
||||
when cheapestOpen (\position -> costFn goal position) model is
|
||||
Err _ ->
|
||||
@ -2972,7 +2972,7 @@ mod solve_uniq_expr {
|
||||
findPath
|
||||
"#
|
||||
),
|
||||
"Attr * (Attr * { costFunction : Attr Shared (Attr Shared position, Attr Shared position -> Attr * Float), end : Attr Shared position, moveFunction : Attr Shared (Attr Shared position -> Attr * (Set (Attr * position))), start : Attr Shared position } -> Attr * (Result (Attr * (List (Attr Shared position))) (Attr * [ KeyNotFound ]*)))"
|
||||
"Attr * (Attr * { costFunction : Attr Shared (Attr Shared position, Attr Shared position -> Attr * F64), end : Attr Shared position, moveFunction : Attr Shared (Attr Shared position -> Attr * (Set (Attr * position))), start : Attr Shared position } -> Attr * (Result (Attr * (List (Attr Shared position))) (Attr * [ KeyNotFound ]*)))"
|
||||
)
|
||||
});
|
||||
}
|
||||
@ -3071,7 +3071,7 @@ mod solve_uniq_expr {
|
||||
negatePoint { x: 1, y: 2.1, z: 0x3 }
|
||||
"#
|
||||
),
|
||||
"Attr * { x : Attr * (Num (Attr * a)), y : Attr * Float, z : Attr * Int }",
|
||||
"Attr * { x : Attr * (Num (Attr * a)), y : Attr * F64, z : Attr * Int }",
|
||||
);
|
||||
}
|
||||
|
||||
@ -3088,7 +3088,7 @@ mod solve_uniq_expr {
|
||||
{ a, b }
|
||||
"#
|
||||
),
|
||||
"Attr * { a : Attr * { x : Attr * (Num (Attr * a)), y : Attr * Float, z : Attr * c }, b : Attr * { blah : Attr * Str, x : Attr * (Num (Attr * a)), y : Attr * Float, z : Attr * c } }"
|
||||
"Attr * { a : Attr * { x : Attr * (Num (Attr * a)), y : Attr * F64, z : Attr * c }, b : Attr * { blah : Attr * Str, x : Attr * (Num (Attr * a)), y : Attr * F64, z : Attr * c } }"
|
||||
);
|
||||
}
|
||||
|
||||
@ -3133,11 +3133,11 @@ mod solve_uniq_expr {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_walk_right() {
|
||||
fn list_walk_backwards() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
List.walkRight
|
||||
List.walkBackwards
|
||||
"#
|
||||
),
|
||||
"Attr * (Attr (* | b) (List (Attr b a)), Attr Shared (Attr b a, c -> c), c -> c)",
|
||||
@ -3145,7 +3145,7 @@ mod solve_uniq_expr {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_walk_right_example() {
|
||||
fn list_walk_backwards_example() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
@ -3153,7 +3153,7 @@ mod solve_uniq_expr {
|
||||
empty =
|
||||
[]
|
||||
|
||||
List.walkRight empty (\a, b -> a + b) 0
|
||||
List.walkBackwards empty (\a, b -> a + b) 0
|
||||
"#
|
||||
),
|
||||
"Attr a Int",
|
||||
|
@ -70,7 +70,7 @@ pub fn aliases() -> MutMap<Symbol, BuiltinAlias> {
|
||||
|
||||
// Float : Num FloatingPoint
|
||||
add_alias(
|
||||
Symbol::NUM_FLOAT,
|
||||
Symbol::NUM_F64,
|
||||
BuiltinAlias {
|
||||
region: Region::zero(),
|
||||
vars: Vec::new(),
|
||||
@ -143,11 +143,7 @@ fn floatingpoint_alias_content() -> SolvedType {
|
||||
|
||||
#[inline(always)]
|
||||
pub fn float_type() -> SolvedType {
|
||||
SolvedType::Alias(
|
||||
Symbol::NUM_FLOAT,
|
||||
Vec::new(),
|
||||
Box::new(float_alias_content()),
|
||||
)
|
||||
SolvedType::Alias(Symbol::NUM_F64, Vec::new(), Box::new(float_alias_content()))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -331,7 +331,7 @@ fn write_content(env: &Env, content: Content, subs: &Subs, buf: &mut String, par
|
||||
match &content {
|
||||
Alias(nested, _, _) => match *nested {
|
||||
Symbol::NUM_INTEGER => buf.push_str("Int"),
|
||||
Symbol::NUM_FLOATINGPOINT => buf.push_str("Float"),
|
||||
Symbol::NUM_FLOATINGPOINT => buf.push_str("F64"),
|
||||
|
||||
_ => write_parens!(write_parens, buf, {
|
||||
buf.push_str("Num ");
|
||||
@ -344,7 +344,7 @@ fn write_content(env: &Env, content: Content, subs: &Subs, buf: &mut String, par
|
||||
match &attr_content {
|
||||
Alias(nested, _, _) => match *nested {
|
||||
Symbol::NUM_INTEGER => buf.push_str("Int"),
|
||||
Symbol::NUM_FLOATINGPOINT => buf.push_str("Float"),
|
||||
Symbol::NUM_FLOATINGPOINT => buf.push_str("F64"),
|
||||
_ => write_parens!(write_parens, buf, {
|
||||
buf.push_str("Num ");
|
||||
write_content(env, content, subs, buf, parens);
|
||||
@ -757,7 +757,7 @@ fn write_apply(
|
||||
buf.push_str("Int");
|
||||
}
|
||||
Symbol::NUM_FLOATINGPOINT if nested_args.is_empty() => {
|
||||
buf.push_str("Float");
|
||||
buf.push_str("F64");
|
||||
}
|
||||
Symbol::ATTR_ATTR => match nested_args
|
||||
.get(1)
|
||||
@ -771,7 +771,7 @@ fn write_apply(
|
||||
buf.push_str("Int");
|
||||
}
|
||||
Symbol::NUM_FLOATINGPOINT if double_nested_args.is_empty() => {
|
||||
buf.push_str("Float");
|
||||
buf.push_str("F64");
|
||||
}
|
||||
_ => default_case(subs, arg_content),
|
||||
},
|
||||
|
@ -1160,7 +1160,7 @@ fn write_error_type_help(
|
||||
buf.push_str("Int");
|
||||
}
|
||||
Type(Symbol::NUM_FLOATINGPOINT, _) => {
|
||||
buf.push_str("Float");
|
||||
buf.push_str("F64");
|
||||
}
|
||||
other => {
|
||||
let write_parens = parens == Parens::InTypeParam;
|
||||
@ -1278,7 +1278,7 @@ fn write_debug_error_type_help(error_type: ErrorType, buf: &mut String, parens:
|
||||
buf.push_str("Int");
|
||||
}
|
||||
Type(Symbol::NUM_FLOATINGPOINT, _) => {
|
||||
buf.push_str("Float");
|
||||
buf.push_str("F64");
|
||||
}
|
||||
other => {
|
||||
let write_parens = parens == Parens::InTypeParam;
|
||||
|
@ -30,7 +30,7 @@ pub fn int_literal(num_var: Variable, expected: Expected<Type>, region: Region)
|
||||
pub fn float_literal(num_var: Variable, expected: Expected<Type>, region: Region) -> Constraint {
|
||||
let num_type = Variable(num_var);
|
||||
let reason = Reason::FloatLiteral;
|
||||
let float_type = builtin_type(Symbol::NUM_FLOAT, vec![]);
|
||||
let float_type = builtin_type(Symbol::NUM_F64, vec![]);
|
||||
let expected_literal = ForReason(reason, float_type, region);
|
||||
|
||||
exists(
|
||||
|
@ -70,6 +70,8 @@ Thoughts and ideas possibly taken from above inspirations or separate.
|
||||
* Makes sense for unit tests, keeps the test close to the source
|
||||
* Doesn't necessarily make sense for integration or e2e testing
|
||||
* Maybe easier to manually trigger a test related to exactly what code you're writing
|
||||
* Ability to generate unit tests for a selected function in context menu
|
||||
* A table should appear to enter input and expected output pairs quickly
|
||||
* "Error mode" where the editor jumps you to the next error
|
||||
* Similar in theory to diff tools that jump you to the next merge conflict
|
||||
* dependency recommendation
|
||||
|
6
examples/.gitignore
vendored
6
examples/.gitignore
vendored
@ -1,5 +1,3 @@
|
||||
app
|
||||
host.o
|
||||
c_host.o
|
||||
roc_app.o
|
||||
app.dSYM
|
||||
*.o
|
||||
*.dSYM
|
||||
|
7
examples/balance/Main.roc
Normal file
7
examples/balance/Main.roc
Normal file
@ -0,0 +1,7 @@
|
||||
app Main provides [ rocMain ] imports [ Effect ]
|
||||
|
||||
rocMain : Effect.Effect {} as Fx
|
||||
rocMain =
|
||||
when List.len (Str.split "hello" "JJJJ there") is
|
||||
_ -> Effect.putLine "Yay"
|
||||
|
23
examples/balance/platform/Cargo.lock
generated
Normal file
23
examples/balance/platform/Cargo.lock
generated
Normal file
@ -0,0 +1,23 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "host"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"roc_std 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "roc_std"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"libc 0.2.79 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[metadata]
|
||||
"checksum libc 0.2.79 (registry+https://github.com/rust-lang/crates.io-index)" = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743"
|
13
examples/balance/platform/Cargo.toml
Normal file
13
examples/balance/platform/Cargo.toml
Normal file
@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "host"
|
||||
version = "0.1.0"
|
||||
authors = ["Richard Feldman <oss@rtfeldman.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
crate-type = ["staticlib"]
|
||||
|
||||
[dependencies]
|
||||
roc_std = { path = "../../../roc_std" }
|
||||
|
||||
[workspace]
|
13
examples/balance/platform/Pkg-Config.roc
Normal file
13
examples/balance/platform/Pkg-Config.roc
Normal file
@ -0,0 +1,13 @@
|
||||
platform folkertdev/foo
|
||||
provides [ mainForHost ]
|
||||
requires { main : Effect {} }
|
||||
imports []
|
||||
effects Effect
|
||||
{
|
||||
putChar : Int -> Effect {},
|
||||
putLine : Str -> Effect {},
|
||||
getLine : Effect Str
|
||||
}
|
||||
|
||||
mainForHost : Effect {} as Fx
|
||||
mainForHost = main
|
7
examples/balance/platform/host.c
Normal file
7
examples/balance/platform/host.c
Normal file
@ -0,0 +1,7 @@
|
||||
#include <stdio.h>
|
||||
|
||||
extern int rust_main();
|
||||
|
||||
int main() {
|
||||
return rust_main();
|
||||
}
|
118
examples/balance/platform/src/lib.rs
Normal file
118
examples/balance/platform/src/lib.rs
Normal file
@ -0,0 +1,118 @@
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use roc_std::alloca;
|
||||
use roc_std::RocCallResult;
|
||||
use roc_std::RocStr;
|
||||
use std::alloc::Layout;
|
||||
use std::time::SystemTime;
|
||||
|
||||
extern "C" {
|
||||
#[link_name = "Main_rocMain_1_exposed"]
|
||||
fn roc_main(output: *mut u8) -> ();
|
||||
|
||||
#[link_name = "Main_rocMain_1_size"]
|
||||
fn roc_main_size() -> i64;
|
||||
|
||||
#[link_name = "Main_rocMain_1_Fx_caller"]
|
||||
fn call_Fx(function_pointer: *const u8, closure_data: *const u8, output: *mut u8) -> ();
|
||||
|
||||
#[link_name = "Main_rocMain_1_Fx_size"]
|
||||
fn size_Fx() -> i64;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub fn roc_fx_putChar(foo: i64) -> () {
|
||||
let character = foo as u8 as char;
|
||||
print!("{}", character);
|
||||
|
||||
()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub fn roc_fx_putLine(line: RocStr) -> () {
|
||||
let bytes = line.as_slice();
|
||||
let string = unsafe { std::str::from_utf8_unchecked(bytes) };
|
||||
println!("{}", string);
|
||||
|
||||
()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub fn roc_fx_getLine() -> RocStr {
|
||||
use std::io::{self, BufRead};
|
||||
|
||||
let stdin = io::stdin();
|
||||
let line1 = stdin.lock().lines().next().unwrap().unwrap();
|
||||
|
||||
RocStr::from_slice_with_capacity(line1.as_bytes(), line1.len())
|
||||
}
|
||||
|
||||
unsafe fn call_the_closure(function_pointer: *const u8, closure_data_ptr: *const u8) -> i64 {
|
||||
let size = size_Fx() as usize;
|
||||
|
||||
alloca::with_stack_bytes(size, |buffer| {
|
||||
let buffer: *mut std::ffi::c_void = buffer;
|
||||
let buffer: *mut u8 = buffer as *mut u8;
|
||||
|
||||
call_Fx(
|
||||
function_pointer,
|
||||
closure_data_ptr as *const u8,
|
||||
buffer as *mut u8,
|
||||
);
|
||||
|
||||
let output = &*(buffer as *mut RocCallResult<i64>);
|
||||
|
||||
// match output.into() {
|
||||
// Ok(v) => v,
|
||||
// Err(e) => panic!("failed with {}", e),
|
||||
// }
|
||||
32
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub fn rust_main() -> isize {
|
||||
println!("Running Roc closure");
|
||||
let start_time = SystemTime::now();
|
||||
|
||||
let size = unsafe { roc_main_size() } as usize;
|
||||
let layout = Layout::array::<u8>(size).unwrap();
|
||||
let answer = unsafe {
|
||||
let buffer = std::alloc::alloc(layout);
|
||||
|
||||
roc_main(buffer);
|
||||
|
||||
let output = &*(buffer as *mut RocCallResult<()>);
|
||||
|
||||
match output.into() {
|
||||
Ok(()) => {
|
||||
let function_pointer = {
|
||||
// this is a pointer to the location where the function pointer is stored
|
||||
// we pass just the function pointer
|
||||
let temp = buffer.offset(8) as *const i64;
|
||||
|
||||
(*temp) as *const u8
|
||||
};
|
||||
|
||||
let closure_data_ptr = buffer.offset(16);
|
||||
|
||||
call_the_closure(function_pointer as *const u8, closure_data_ptr as *const u8)
|
||||
}
|
||||
Err(msg) => {
|
||||
std::alloc::dealloc(buffer, layout);
|
||||
|
||||
panic!("Roc failed with message: {}", msg);
|
||||
}
|
||||
}
|
||||
};
|
||||
let end_time = SystemTime::now();
|
||||
let duration = end_time.duration_since(start_time).unwrap();
|
||||
|
||||
println!(
|
||||
"Roc execution took {:.4} ms",
|
||||
duration.as_secs_f64() * 1000.0,
|
||||
);
|
||||
|
||||
// Exit code
|
||||
0
|
||||
}
|
@ -1,9 +1,8 @@
|
||||
app Closure provides [ makeClosure ] imports []
|
||||
app "closure" provides [ makeClosure ] to "./platform/"
|
||||
|
||||
makeClosure : ({} -> Int) as MyClosure
|
||||
makeClosure =
|
||||
makeClosure =
|
||||
x = 42
|
||||
y = 42
|
||||
|
||||
\{} -> x + y
|
||||
|
||||
|
@ -1,5 +1,12 @@
|
||||
platform roc/quicksort
|
||||
provides []
|
||||
requires {}
|
||||
platform examples/closure
|
||||
requires { main : Effect {} }
|
||||
exposes []
|
||||
packages {}
|
||||
imports []
|
||||
effects Effect {}
|
||||
provides [ mainForHost ]
|
||||
effects Effect
|
||||
{
|
||||
putChar : Int -> Effect {},
|
||||
putLine : Str -> Effect {},
|
||||
getLine : Effect Str
|
||||
}
|
||||
|
@ -1,31 +1,18 @@
|
||||
app Main provides [ main ] imports [ Effect, RBTree ]
|
||||
|
||||
toAndFro : Int
|
||||
toAndFro =
|
||||
empty : RBTree.Dict Int {}
|
||||
empty = RBTree.empty
|
||||
|
||||
empty
|
||||
|> (\d -> RBTree.insert 1 {} d)
|
||||
|> RBTree.toList
|
||||
|> List.len
|
||||
|
||||
|
||||
app "effect-example" imports [ Effect ] provides [ main ] to "./platform"
|
||||
|
||||
|
||||
main : Effect.Effect {} as Fx
|
||||
main =
|
||||
# if RBTree.isEmpty empty then
|
||||
if toAndFro == 2 then
|
||||
Effect.putLine "Yay"
|
||||
|> Effect.after (\{} -> Effect.getLine)
|
||||
|> Effect.after (\line -> Effect.putLine line)
|
||||
else
|
||||
Effect.putLine "Nay"
|
||||
when if 1 == 1 then True 3 else False 3.14 is
|
||||
True 3 -> Effect.putLine "Yay"
|
||||
_ -> Effect.putLine "Yay"
|
||||
|
||||
|
||||
# Effect.always "Write a thing"
|
||||
# |> Effect.map (\line -> Str.concat line "!")
|
||||
# |> Effect.after (\line -> Effect.putLine line)
|
||||
# |> Effect.after (\{} -> Effect.getLine)
|
||||
# |> Effect.after (\line -> Effect.putLine line)
|
||||
# main : Effect.Effect {} as Fx
|
||||
# main =
|
||||
# if RBTree.isEmpty (RBTree.insert 1 2 Empty) then
|
||||
# Effect.putLine "Yay"
|
||||
# |> Effect.after (\{} -> Effect.getLine)
|
||||
# |> Effect.after (\line -> Effect.putLine line)
|
||||
# else
|
||||
# Effect.putLine "Nay"
|
||||
#
|
||||
|
@ -1,4 +1,4 @@
|
||||
interface RBTree exposes [ Dict, empty, size, singleton, isEmpty, insert, remove, update, fromList, toList ] imports []
|
||||
interface RBTree exposes [ Dict, empty, size, singleton, isEmpty, insert, remove, update, fromList, toList, balance ] imports []
|
||||
|
||||
# The color of a node. Leaves are considered Black.
|
||||
NodeColor : [ Red, Black ]
|
||||
|
@ -1,7 +1,9 @@
|
||||
platform folkertdev/foo
|
||||
provides [ mainForHost ]
|
||||
requires { main : Effect {} }
|
||||
exposes []
|
||||
packages {}
|
||||
imports []
|
||||
provides [ mainForHost ]
|
||||
effects Effect
|
||||
{
|
||||
putChar : Int -> Effect {},
|
||||
|
@ -7,16 +7,16 @@ use std::alloc::Layout;
|
||||
use std::time::SystemTime;
|
||||
|
||||
extern "C" {
|
||||
#[link_name = "Main_main_1_exposed"]
|
||||
#[link_name = "roc__main_1_exposed"]
|
||||
fn roc_main(output: *mut u8) -> ();
|
||||
|
||||
#[link_name = "Main_main_1_size"]
|
||||
#[link_name = "roc__main_1_size"]
|
||||
fn roc_main_size() -> i64;
|
||||
|
||||
#[link_name = "Main_main_1_Fx_caller"]
|
||||
#[link_name = "roc__main_1_Fx_caller"]
|
||||
fn call_Fx(function_pointer: *const u8, closure_data: *const u8, output: *mut u8) -> ();
|
||||
|
||||
#[link_name = "Main_main_1_Fx_size"]
|
||||
#[link_name = "roc__main_1_Fx_size"]
|
||||
fn size_Fx() -> i64;
|
||||
}
|
||||
|
||||
@ -60,10 +60,10 @@ unsafe fn call_the_closure(function_pointer: *const u8, closure_data_ptr: *const
|
||||
buffer as *mut u8,
|
||||
);
|
||||
|
||||
let output = &*(buffer as *mut RocCallResult<i64>);
|
||||
let output = &*(buffer as *mut RocCallResult<()>);
|
||||
|
||||
match output.into() {
|
||||
Ok(v) => v,
|
||||
Ok(_) => 0,
|
||||
Err(e) => panic!("failed with {}", e),
|
||||
}
|
||||
})
|
||||
@ -95,7 +95,12 @@ pub fn rust_main() -> isize {
|
||||
|
||||
let closure_data_ptr = buffer.offset(16);
|
||||
|
||||
call_the_closure(function_pointer as *const u8, closure_data_ptr as *const u8)
|
||||
let result =
|
||||
call_the_closure(function_pointer as *const u8, closure_data_ptr as *const u8);
|
||||
|
||||
std::alloc::dealloc(buffer, layout);
|
||||
|
||||
result
|
||||
}
|
||||
Err(msg) => {
|
||||
std::alloc::dealloc(buffer, layout);
|
||||
|
1
examples/hello-world/.gitignore
vendored
Normal file
1
examples/hello-world/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
hello-world
|
@ -1,4 +1,4 @@
|
||||
app Hello provides [ main ] imports []
|
||||
app "hello-world" provides [ main ] to "./platform"
|
||||
|
||||
greeting =
|
||||
hi = "Hello"
|
||||
|
@ -1,5 +1,7 @@
|
||||
platform roc/quicksort
|
||||
provides []
|
||||
requires {}
|
||||
platform examples/hello-world
|
||||
requires { main : Str }
|
||||
exposes []
|
||||
packages {}
|
||||
imports []
|
||||
provides [ main ]
|
||||
effects Effect {}
|
||||
|
@ -3,7 +3,7 @@ use roc_std::RocStr;
|
||||
use std::str;
|
||||
|
||||
extern "C" {
|
||||
#[link_name = "Hello_main_1_exposed"]
|
||||
#[link_name = "roc__main_1_exposed"]
|
||||
fn say_hello(output: &mut RocCallResult<RocStr>) -> ();
|
||||
}
|
||||
|
||||
|
1
examples/multi-module/.gitignore
vendored
Normal file
1
examples/multi-module/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
quicksort
|
@ -1,8 +1,8 @@
|
||||
app Quicksort provides [ quicksort ] imports [ Utils.{swap} ]
|
||||
app "quicksort" imports [ Utils.{ swap } ] provides [ quicksort ] to "./platform"
|
||||
|
||||
|
||||
quicksort : List Int -> List Int
|
||||
quicksort = \originalList ->
|
||||
quicksort = \originalList ->
|
||||
quicksortHelp : List (Num a), Int, Int -> List (Num a)
|
||||
quicksortHelp = \list, low, high ->
|
||||
if low < high then
|
||||
@ -43,5 +43,5 @@ quicksort = \originalList ->
|
||||
|
||||
|
||||
|
||||
n = List.len originalList
|
||||
n = List.len originalList
|
||||
quicksortHelp originalList 0 (n - 1)
|
||||
|
@ -1,5 +1,7 @@
|
||||
platform roc/quicksort
|
||||
provides []
|
||||
requires {}
|
||||
platform examples/multi-module
|
||||
requires { quicksort : List (Num a) -> List (Num a) }
|
||||
exposes []
|
||||
packages {}
|
||||
imports []
|
||||
provides [ main ]
|
||||
effects Effect {}
|
||||
|
@ -3,7 +3,7 @@ use roc_std::RocList;
|
||||
use std::time::SystemTime;
|
||||
|
||||
extern "C" {
|
||||
#[link_name = "Quicksort_quicksort_1_exposed"]
|
||||
#[link_name = "roc__quicksort_1_exposed"]
|
||||
fn quicksort(list: RocList<i64>, output: &mut RocCallResult<RocList<i64>>) -> ();
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user