mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-01 15:51:12 +00:00
Merge branch 'trunk' into remove-dict-hash-test-only
This commit is contained in:
commit
ad50f4a8f3
148 changed files with 6856 additions and 5566 deletions
5
AUTHORS
5
AUTHORS
|
@ -43,3 +43,8 @@ Locria Cyber <locriacyber@noreply.users.github.com>
|
|||
Matthias Beyer <mail@beyermatthias.de>
|
||||
Tim Whiting <tim@whitings.org>
|
||||
Logan Lowder <logan.lowder@logikcull.com>
|
||||
Joshua Warner <joshuawarner32@gmail.com>
|
||||
Luiz Carlos L. G. de Oliveira <luizcarlos1405@gmail.com>
|
||||
Oleksii Skidan <al.skidan@gmail.com>
|
||||
Martin Janiczek <martin@janiczek.cz>
|
||||
Eric Newbury <enewbury@users.noreply.github.com>
|
||||
|
|
|
@ -82,8 +82,6 @@ There are also alternative installation options at http://releases.llvm.org/down
|
|||
|
||||
## Using Nix
|
||||
|
||||
:exclamation: **Our Nix setup is not yet working on MacOS, you'll have to install manually for now** :exclamation:
|
||||
|
||||
### Install
|
||||
|
||||
Using [nix](https://nixos.org/download.html) is a quick way to get an environment bootstrapped with a single command.
|
||||
|
@ -120,10 +118,11 @@ You should be in a repl now. Have fun!
|
|||
|
||||
### Extra tips
|
||||
|
||||
If you plan on using `nix-shell` regularly, check out [direnv](https://direnv.net/) and [lorri](https://github.com/nix-community/lorri). Whenever you `cd` into `roc/`, they will automatically load the Nix dependecies into your current shell, so you never have to run nix-shell directly!
|
||||
If you plan on using `nix-shell` regularly, check out [direnv](https://direnv.net/) and [lorri](https://github.com/nix-community/lorri). Whenever you `cd` into `roc/`, they will automatically load the Nix dependencies into your current shell, so you never have to run nix-shell directly!
|
||||
|
||||
### Editor
|
||||
|
||||
The editor is a WIP and not ready yet to replace your favorite editor, although if you want to try it out on nix, read on.
|
||||
`cargo run edit` should work from NixOS, if you use a nix-shell from inside another OS, follow the instructions below.
|
||||
|
||||
#### Nvidia GPU
|
||||
|
|
1975
Cargo.lock
generated
1975
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -23,6 +23,7 @@ members = [
|
|||
"compiler/gen_wasm",
|
||||
"compiler/build",
|
||||
"compiler/arena_pool",
|
||||
"compiler/test_dev",
|
||||
"compiler/test_gen",
|
||||
"compiler/test_wasm",
|
||||
"vendor/ena",
|
||||
|
|
|
@ -79,11 +79,11 @@ test-rust:
|
|||
# not pre-compiling the host can cause race conditions
|
||||
RUN echo "4" | cargo run --release examples/benchmarks/NQueens.roc
|
||||
RUN --mount=type=cache,target=$SCCACHE_DIR \
|
||||
cargo test --release && sccache --show-stats
|
||||
cargo test --release --features with_sound && sccache --show-stats
|
||||
# run i386 (32-bit linux) cli tests
|
||||
RUN echo "4" | cargo run --release -- --backend=x86_32 examples/benchmarks/NQueens.roc
|
||||
RUN echo "4" | cargo run --release --features="target-x86" -- --backend=x86_32 examples/benchmarks/NQueens.roc
|
||||
RUN --mount=type=cache,target=$SCCACHE_DIR \
|
||||
cargo test --release --test cli_run i386 --features="i386-cli-run" && sccache --show-stats
|
||||
cargo test --release --features with_sound --test cli_run i386 --features="i386-cli-run" && sccache --show-stats
|
||||
|
||||
verify-no-git-changes:
|
||||
FROM +test-rust
|
||||
|
@ -109,7 +109,7 @@ build-nightly-release:
|
|||
# version.txt is used by the CLI: roc --version
|
||||
RUN printf "nightly pre-release, built from commit " > version.txt
|
||||
RUN git log --pretty=format:'%h' -n 1 >> version.txt
|
||||
RUN cargo build --release
|
||||
RUN cargo build --features with_sound --release
|
||||
RUN cd ./target/release && tar -czvf roc_linux_x86_64.tar.gz ./roc
|
||||
SAVE ARTIFACT ./target/release/roc_linux_x86_64.tar.gz AS LOCAL roc_linux_x86_64.tar.gz
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ If you're curious about where the language's name and logo came from,
|
|||
## State of Roc
|
||||
|
||||
Roc is not ready for production yet. You are likely to encounter bugs. Publishing packages or documentation is not yet supported.
|
||||
Many programs can however be compiled correctly. Check out [examples](examples) and [examples/benchmarks](examples/benchmarks). There are minimal platforms for Rust, Zig, C and an HTTP server. We are hard at work to make programming in Roc a delightful experience!
|
||||
Many programs can however be compiled correctly. Check out [examples](examples) and [examples/benchmarks](examples/benchmarks). There are minimal platforms for Rust, Zig, C, Swift and an HTTP server. We are hard at work to make programming in Roc a delightful experience!
|
||||
|
||||
## Getting started
|
||||
|
||||
|
@ -64,7 +64,7 @@ By using systems-level programming languages like C and C++, platform authors sa
|
|||
Roc is designed to make the "systems-level platform, higher-level application" experience as nice as possible.
|
||||
|
||||
* **Application** authors code exclusively in Roc. It's a language designed for nice ergonomics. The syntax resembles Ruby or CoffeeScript, and it has a fast compiler with full type inference.
|
||||
* **Platform** authors code almost exclusively in a systems-level language like C, C++, Rust, or [Zig](https://ziglang.org/), except for the thin Roc API they expose to application authors. Roc application code compiles to machine code, and production builds of Roc apps benefit from the same [LLVM](https://llvm.org/) optimizations that C++, Rust, and Zig do. Roc application authors do not need to know this lower-level code exists; all they have to interact with is the platform's API, which is exposed as an ordinary Roc API.
|
||||
* **Platform** authors code almost exclusively in a systems-level language like C, C++, Rust, Swift or [Zig](https://ziglang.org/), except for the thin Roc API they expose to application authors. Roc application code compiles to machine code, and production builds of Roc apps benefit from the same [LLVM](https://llvm.org/) optimizations that C++, Rust, Swift and Zig do. Roc application authors do not need to know this lower-level code exists; all they have to interact with is the platform's API, which is exposed as an ordinary Roc API.
|
||||
|
||||
Every Roc application is built on top of exactly one Roc platform. There is no such thing as a Roc application that runs without a platform, and there is no default platform. You must choose one!
|
||||
|
||||
|
|
|
@ -18,12 +18,11 @@ roc_types = { path = "../compiler/types" }
|
|||
roc_unify = { path = "../compiler/unify"}
|
||||
roc_load = { path = "../compiler/load" }
|
||||
arraystring = "0.3.0"
|
||||
bumpalo = { version = "3.6.1", features = ["collections"] }
|
||||
libc = "0.2"
|
||||
page_size = "0.4"
|
||||
snafu = { version = "0.6", features = ["backtraces"] }
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
libc = "0.2.106"
|
||||
page_size = "0.4.2"
|
||||
snafu = { version = "0.6.10", features = ["backtraces"] }
|
||||
ven_graph = { path = "../vendor/pathfinding" }
|
||||
indoc = "1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.6"
|
||||
indoc = "1.0.3"
|
||||
|
|
|
@ -9,7 +9,7 @@ use crate::lang::core::ast::ASTNodeId;
|
|||
#[snafu(visibility(pub))]
|
||||
pub enum ASTError {
|
||||
#[snafu(display(
|
||||
"ASTNodeIdWithoutExprId: The expr_id_opt in ASTNode({:?}) was `None` but I was expexting `Some(ExprId)` .",
|
||||
"ASTNodeIdWithoutExprId: The expr_id_opt in ASTNode({:?}) was `None` but I was expecting `Some(ExprId)` .",
|
||||
ast_node_id
|
||||
))]
|
||||
ASTNodeIdWithoutExprId {
|
||||
|
|
|
@ -464,7 +464,7 @@ fn canonicalize_pending_def<'a>(
|
|||
env.tailcallable_symbol = Some(*defined_symbol);
|
||||
};
|
||||
|
||||
// regiser the name of this closure, to make sure the closure won't capture it's own name
|
||||
// register the name of this closure, to make sure the closure won't capture it's own name
|
||||
if let (Pattern2::Identifier(ref defined_symbol), &ast::Expr::Closure(_, _)) =
|
||||
(&env.pool[loc_can_pattern], &loc_expr.value)
|
||||
{
|
||||
|
@ -632,7 +632,7 @@ fn canonicalize_pending_def<'a>(
|
|||
env.tailcallable_symbol = Some(*defined_symbol);
|
||||
};
|
||||
|
||||
// regiser the name of this closure, to make sure the closure won't capture it's own name
|
||||
// register the name of this closure, to make sure the closure won't capture it's own name
|
||||
if let (Pattern2::Identifier(ref defined_symbol), &ast::Expr::Closure(_, _)) =
|
||||
(&env.pool[loc_can_pattern], &loc_expr.value)
|
||||
{
|
||||
|
|
|
@ -108,7 +108,7 @@ pub enum Expr2 {
|
|||
},
|
||||
Closure {
|
||||
args: PoolVec<(Variable, NodeId<Pattern2>)>, // 8B
|
||||
uniq_symbol: Symbol, // 8B This is a globally uniqe symbol for the closure
|
||||
uniq_symbol: Symbol, // 8B This is a globally unique symbol for the closure
|
||||
body_id: ExprId, // 4B
|
||||
function_type: Variable, // 4B
|
||||
recursive: Recursive, // 1B
|
||||
|
|
|
@ -454,7 +454,7 @@ pub fn expr_to_expr2<'a>(
|
|||
|
||||
// We shouldn't ultimately count arguments as referenced locals. Otherwise,
|
||||
// we end up with weird conclusions like the expression (\x -> x + 1)
|
||||
// references the (nonexistant) local variable x!
|
||||
// references the (nonexistent) local variable x!
|
||||
output.references.lookups.remove(&sub_symbol);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ use roc_types::subs::Variable;
|
|||
|
||||
#[derive(Clone, Debug, PartialEq, Default)]
|
||||
pub struct IntroducedVariables {
|
||||
// Rigids must be unique within a type annoation.
|
||||
// Rigids must be unique within a type annotation.
|
||||
// E.g. in `identity : a -> a`, there should only be one
|
||||
// variable (a rigid one, with name "a").
|
||||
// Hence `rigids : Map<Lowercase, Variable>`
|
||||
|
|
|
@ -162,7 +162,7 @@ impl Scope {
|
|||
|
||||
let alias = Alias {
|
||||
actual,
|
||||
/// We know that builtin aliases have no hiddden variables (e.g. in closures)
|
||||
/// We know that builtin aliases have no hidden variables (e.g. in closures)
|
||||
hidden_variables: PoolVec::empty(pool),
|
||||
targs: variables,
|
||||
};
|
||||
|
|
|
@ -10,8 +10,9 @@
|
|||
///
|
||||
/// Pages also use the node value 0 (all 0 bits) to mark nodes as unoccupied.
|
||||
/// This is important for performance.
|
||||
use libc::{c_void, MAP_ANONYMOUS, MAP_PRIVATE, PROT_READ, PROT_WRITE};
|
||||
use libc::{MAP_ANONYMOUS, MAP_PRIVATE, PROT_READ, PROT_WRITE};
|
||||
use std::any::type_name;
|
||||
use std::ffi::c_void;
|
||||
use std::marker::PhantomData;
|
||||
use std::mem::size_of;
|
||||
use std::ptr::null;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use super::pool::{NodeId, Pool, NODE_BYTES};
|
||||
use super::shallow_clone::ShallowClone;
|
||||
use libc::c_void;
|
||||
use std::ffi::c_void;
|
||||
use std::marker::PhantomData;
|
||||
use std::mem::size_of;
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use super::pool::{NodeId, Pool, NODE_BYTES};
|
||||
use super::shallow_clone::ShallowClone;
|
||||
use libc::c_void;
|
||||
use std::any::type_name;
|
||||
use std::cmp::Ordering;
|
||||
use std::ffi::c_void;
|
||||
use std::marker::PhantomData;
|
||||
use std::mem::size_of;
|
||||
|
||||
|
|
|
@ -15,25 +15,31 @@ test = false
|
|||
bench = false
|
||||
|
||||
[features]
|
||||
default = ["target-x86", "llvm", "editor"]
|
||||
wasm32-cli-run = []
|
||||
i386-cli-run = []
|
||||
default = ["target-aarch64", "target-x86_64", "target-wasm32", "llvm", "editor"]
|
||||
|
||||
wasm32-cli-run = ["target-wasm32", "run-wasm32"]
|
||||
i386-cli-run = ["target-x86"]
|
||||
|
||||
# This is a separate feature because when we generate docs on Netlify,
|
||||
# it doesn't have LLVM installed. (Also, it doesn't need to do code gen.)
|
||||
llvm = ["inkwell", "roc_gen_llvm", "roc_build/llvm"]
|
||||
editor = ["roc_editor"]
|
||||
|
||||
target-x86 = []
|
||||
run-wasm32 = ["wasmer", "wasmer-wasi"]
|
||||
|
||||
# arm and wasm give linker errors on some platforms
|
||||
target-arm = []
|
||||
target-webassembly = []
|
||||
# Compiling for a different platform than the host can cause linker errors.
|
||||
target-arm = ["roc_build/target-arm"]
|
||||
target-aarch64 = ["roc_build/target-aarch64"]
|
||||
target-x86 = ["roc_build/target-x86"]
|
||||
target-x86_64 = ["roc_build/target-x86_64"]
|
||||
target-wasm32 = ["roc_build/target-wasm32"]
|
||||
|
||||
target-all = [
|
||||
"target-x86",
|
||||
"target-aarch64",
|
||||
"target-arm",
|
||||
"target-webassembly"
|
||||
"target-x86",
|
||||
"target-x86_64",
|
||||
"target-wasm32"
|
||||
]
|
||||
|
||||
|
||||
|
@ -58,36 +64,33 @@ roc_fmt = { path = "../compiler/fmt" }
|
|||
roc_reporting = { path = "../compiler/reporting" }
|
||||
roc_editor = { path = "../editor", optional = true }
|
||||
roc_linker = { path = "../linker" }
|
||||
clap = "= 3.0.0-beta.1"
|
||||
const_format = "0.2"
|
||||
clap = { version = "= 3.0.0-beta.5", default-features = false, features = ["std", "color", "suggestions"] }
|
||||
const_format = "0.2.22"
|
||||
rustyline = { git = "https://github.com/rtfeldman/rustyline", tag = "prompt-fix" }
|
||||
rustyline-derive = { git = "https://github.com/rtfeldman/rustyline", tag = "prompt-fix" }
|
||||
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"] }
|
||||
libc = "0.2"
|
||||
libloading = "0.6"
|
||||
im = "15.0.0"
|
||||
im-rc = "15.0.0"
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
libloading = "0.7.1"
|
||||
mimalloc = { version = "0.1.26", default-features = false }
|
||||
|
||||
inkwell = { path = "../vendor/inkwell", optional = true }
|
||||
target-lexicon = "0.12.2"
|
||||
tempfile = "3.1.0"
|
||||
tempfile = "3.2.0"
|
||||
|
||||
wasmer = "2.0.0"
|
||||
wasmer-wasi = "2.0.0"
|
||||
wasmer = { version = "2.0.0", optional = true, default-features = false, features = ["default-cranelift", "default-universal"] }
|
||||
wasmer-wasi = { version = "2.0.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.5.1"
|
||||
maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
quickcheck_macros = "0.8"
|
||||
serial_test = "0.5"
|
||||
tempfile = "3.1.0"
|
||||
wasmer = { version = "2.0.0", default-features = false, features = ["default-cranelift", "default-universal"] }
|
||||
wasmer-wasi = "2.0.0"
|
||||
pretty_assertions = "1.0.0"
|
||||
indoc = "1.0.3"
|
||||
serial_test = "0.5.1"
|
||||
tempfile = "3.2.0"
|
||||
criterion = { git = "https://github.com/Anton-4/criterion.rs"}
|
||||
cli_utils = { path = "cli_utils" }
|
||||
|
||||
[[bench]]
|
||||
name = "time_bench"
|
||||
harness = false
|
||||
|
||||
|
|
|
@ -14,11 +14,10 @@ roc_cli = { path = "../../cli" }
|
|||
roc_collections = { path = "../../compiler/collections" }
|
||||
roc_load = { path = "../../compiler/load" }
|
||||
roc_module = { path = "../../compiler/module" }
|
||||
bumpalo = { version = "3.6.1", features = ["collections"] }
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
criterion = { git = "https://github.com/Anton-4/criterion.rs"}
|
||||
inlinable_string = "0.1"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde-xml-rs = "0.4"
|
||||
strip-ansi-escapes = "0.1"
|
||||
tempfile = "3.1.0"
|
||||
serde = { version = "1.0.130", features = ["derive"] }
|
||||
serde-xml-rs = "0.5.1"
|
||||
strip-ansi-escapes = "0.1.1"
|
||||
tempfile = "3.2.0"
|
||||
rlimit = "0.6.2"
|
||||
|
|
|
@ -3,7 +3,6 @@ use roc_build::{
|
|||
link::{link, rebuild_host, LinkType},
|
||||
program,
|
||||
};
|
||||
#[cfg(feature = "llvm")]
|
||||
use roc_builtins::bitcode;
|
||||
use roc_can::builtins::builtin_defs_map;
|
||||
use roc_collections::all::MutMap;
|
||||
|
@ -12,7 +11,6 @@ use roc_mono::ir::OptLevel;
|
|||
use std::path::PathBuf;
|
||||
use std::time::{Duration, SystemTime};
|
||||
use target_lexicon::Triple;
|
||||
#[cfg(feature = "llvm")]
|
||||
use tempfile::Builder;
|
||||
|
||||
fn report_timing(buf: &mut String, label: &str, duration: Duration) {
|
||||
|
@ -45,7 +43,6 @@ pub struct BuiltFile {
|
|||
pub total_time: Duration,
|
||||
}
|
||||
|
||||
#[cfg(feature = "llvm")]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn build_file<'a>(
|
||||
arena: &'a Bump,
|
||||
|
@ -179,8 +176,7 @@ pub fn build_file<'a>(
|
|||
program::report_problems_monomorphized(&mut loaded);
|
||||
let loaded = loaded;
|
||||
|
||||
let code_gen_timing = match opt_level {
|
||||
OptLevel::Normal | OptLevel::Optimize => program::gen_from_mono_module_llvm(
|
||||
let code_gen_timing = program::gen_from_mono_module(
|
||||
arena,
|
||||
loaded,
|
||||
&roc_file_path,
|
||||
|
@ -188,11 +184,7 @@ pub fn build_file<'a>(
|
|||
app_o_file,
|
||||
opt_level,
|
||||
emit_debug_info,
|
||||
),
|
||||
OptLevel::Development => {
|
||||
program::gen_from_mono_module_dev(arena, loaded, target, app_o_file)
|
||||
}
|
||||
};
|
||||
);
|
||||
|
||||
buf.push('\n');
|
||||
buf.push_str(" ");
|
||||
|
|
|
@ -44,24 +44,24 @@ pub fn build_app<'a>() -> App<'a> {
|
|||
.subcommand(App::new(CMD_BUILD)
|
||||
.about("Build a binary from the given .roc file, but don't run it")
|
||||
.arg(
|
||||
Arg::with_name(ROC_FILE)
|
||||
Arg::new(ROC_FILE)
|
||||
.about("The .roc file to build")
|
||||
.required(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(FLAG_OPTIMIZE)
|
||||
Arg::new(FLAG_OPTIMIZE)
|
||||
.long(FLAG_OPTIMIZE)
|
||||
.about("Optimize your compiled Roc program to run faster. (Optimization takes time to complete.)")
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(FLAG_DEV)
|
||||
Arg::new(FLAG_DEV)
|
||||
.long(FLAG_DEV)
|
||||
.about("Make compilation as fast as possible. (Runtime performance may suffer)")
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(FLAG_BACKEND)
|
||||
Arg::new(FLAG_BACKEND)
|
||||
.long(FLAG_BACKEND)
|
||||
.about("Choose a different backend")
|
||||
// .requires(BACKEND)
|
||||
|
@ -70,31 +70,31 @@ pub fn build_app<'a>() -> App<'a> {
|
|||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(FLAG_LIB)
|
||||
Arg::new(FLAG_LIB)
|
||||
.long(FLAG_LIB)
|
||||
.about("Build a C library instead of an executable.")
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(FLAG_DEBUG)
|
||||
Arg::new(FLAG_DEBUG)
|
||||
.long(FLAG_DEBUG)
|
||||
.about("Store LLVM debug information in the generated program")
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(FLAG_TIME)
|
||||
Arg::new(FLAG_TIME)
|
||||
.long(FLAG_TIME)
|
||||
.about("Prints detailed compilation time information.")
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(FLAG_LINK)
|
||||
Arg::new(FLAG_LINK)
|
||||
.long(FLAG_LINK)
|
||||
.about("Uses the roc linker instead of the system linker.")
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(FLAG_PRECOMPILED)
|
||||
Arg::new(FLAG_PRECOMPILED)
|
||||
.long(FLAG_PRECOMPILED)
|
||||
.about("Assumes the host has been precompiled and skips recompiling the host.")
|
||||
.required(false),
|
||||
|
@ -106,23 +106,23 @@ pub fn build_app<'a>() -> App<'a> {
|
|||
.subcommand(App::new(CMD_CHECK)
|
||||
.about("Build a binary from the given .roc file, but don't run it")
|
||||
.arg(
|
||||
Arg::with_name(FLAG_TIME)
|
||||
Arg::new(FLAG_TIME)
|
||||
.long(FLAG_TIME)
|
||||
.about("Prints detailed compilation time information.")
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(ROC_FILE)
|
||||
Arg::new(ROC_FILE)
|
||||
.about("The .roc file of an app to run")
|
||||
.required(true),
|
||||
)
|
||||
)
|
||||
.subcommand(
|
||||
App::new(CMD_DOCS)
|
||||
.about("Generate documentation for Roc modules")
|
||||
.arg(Arg::with_name(DIRECTORY_OR_FILES)
|
||||
.about("Generate documentation for Roc modules (Work In Progress)")
|
||||
.arg(Arg::new(DIRECTORY_OR_FILES)
|
||||
.index(1)
|
||||
.multiple(true)
|
||||
.multiple_values(true)
|
||||
.required(false)
|
||||
.about("The directory or files to build documentation for")
|
||||
|
||||
|
@ -130,45 +130,45 @@ pub fn build_app<'a>() -> App<'a> {
|
|||
)
|
||||
.setting(AppSettings::TrailingVarArg)
|
||||
.arg(
|
||||
Arg::with_name(FLAG_OPTIMIZE)
|
||||
Arg::new(FLAG_OPTIMIZE)
|
||||
.long(FLAG_OPTIMIZE)
|
||||
.about("Optimize the compiled program to run faster. (Optimization takes time to complete.)")
|
||||
.requires(ROC_FILE)
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(FLAG_DEV)
|
||||
Arg::new(FLAG_DEV)
|
||||
.long(FLAG_DEV)
|
||||
.about("Make compilation as fast as possible. (Runtime performance may suffer)")
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(FLAG_DEBUG)
|
||||
Arg::new(FLAG_DEBUG)
|
||||
.long(FLAG_DEBUG)
|
||||
.about("Store LLVM debug information in the generated program")
|
||||
.requires(ROC_FILE)
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(FLAG_TIME)
|
||||
Arg::new(FLAG_TIME)
|
||||
.long(FLAG_TIME)
|
||||
.about("Prints detailed compilation time information.")
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(FLAG_LINK)
|
||||
Arg::new(FLAG_LINK)
|
||||
.long(FLAG_LINK)
|
||||
.about("Uses the roc linker instead of the system linker.")
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(FLAG_PRECOMPILED)
|
||||
Arg::new(FLAG_PRECOMPILED)
|
||||
.long(FLAG_PRECOMPILED)
|
||||
.about("Assumes the host has been precompiled and skips recompiling the host.")
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(FLAG_BACKEND)
|
||||
Arg::new(FLAG_BACKEND)
|
||||
.long(FLAG_BACKEND)
|
||||
.about("Choose a different backend")
|
||||
// .requires(BACKEND)
|
||||
|
@ -177,23 +177,23 @@ pub fn build_app<'a>() -> App<'a> {
|
|||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(ROC_FILE)
|
||||
Arg::new(ROC_FILE)
|
||||
.about("The .roc file of an app to build and run")
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(ARGS_FOR_APP)
|
||||
Arg::new(ARGS_FOR_APP)
|
||||
.about("Arguments to pass into the app being run")
|
||||
.requires(ROC_FILE)
|
||||
.multiple(true),
|
||||
.multiple_values(true),
|
||||
);
|
||||
|
||||
if cfg!(feature = "editor") {
|
||||
app.subcommand(
|
||||
App::new(CMD_EDIT).about("Launch the Roc editor").arg(
|
||||
Arg::with_name(DIRECTORY_OR_FILES)
|
||||
Arg::new(DIRECTORY_OR_FILES)
|
||||
.index(1)
|
||||
.multiple(true)
|
||||
.multiple_values(true)
|
||||
.required(false)
|
||||
.about("(optional) The directory or files to open on launch."),
|
||||
),
|
||||
|
@ -217,7 +217,6 @@ pub enum BuildConfig {
|
|||
BuildAndRun { roc_file_arg_index: usize },
|
||||
}
|
||||
|
||||
#[cfg(feature = "llvm")]
|
||||
pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
|
||||
use build::build_file;
|
||||
use std::str::FromStr;
|
||||
|
@ -412,6 +411,7 @@ fn roc_run(cmd: &mut Command) -> io::Result<i32> {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "run-wasm32")]
|
||||
fn run_with_wasmer(wasm_path: &std::path::Path, args: &[String]) {
|
||||
use wasmer::{Instance, Module, Store};
|
||||
|
||||
|
@ -442,6 +442,11 @@ fn run_with_wasmer(wasm_path: &std::path::Path, args: &[String]) {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "run-wasm32"))]
|
||||
fn run_with_wasmer(_wasm_path: &std::path::Path, _args: &[String]) {
|
||||
println!("Running wasm files not support");
|
||||
}
|
||||
|
||||
enum Backend {
|
||||
Host,
|
||||
X86_32,
|
||||
|
|
|
@ -13,14 +13,8 @@ static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc;
|
|||
|
||||
use std::ffi::{OsStr, OsString};
|
||||
|
||||
#[cfg(feature = "llvm")]
|
||||
use roc_cli::build;
|
||||
|
||||
#[cfg(not(feature = "llvm"))]
|
||||
fn build(_matches: &clap::ArgMatches, _config: BuildConfig) -> io::Result<i32> {
|
||||
panic!("Building without LLVM is not currently supported.");
|
||||
}
|
||||
|
||||
fn main() -> io::Result<()> {
|
||||
let matches = build_app().get_matches();
|
||||
|
||||
|
|
|
@ -191,6 +191,12 @@ mod cli_run {
|
|||
eprintln!("WARNING: skipping testing example {} because the test is broken right now!", example.filename);
|
||||
return;
|
||||
}
|
||||
"hello-swift" => {
|
||||
if cfg!(not(target_os = "macos")) {
|
||||
eprintln!("WARNING: skipping testing example {} because it only works on MacOS.", example.filename);
|
||||
return;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
|
@ -283,6 +289,14 @@ mod cli_run {
|
|||
expected_ending:"Hello, World!\n",
|
||||
use_valgrind: true,
|
||||
},
|
||||
hello_swift:"hello-swift" => Example {
|
||||
filename: "Hello.roc",
|
||||
executable_filename: "hello-swift",
|
||||
stdin: &[],
|
||||
input_file: None,
|
||||
expected_ending:"Hello Swift, meet Roc\n",
|
||||
use_valgrind: true,
|
||||
},
|
||||
hello_web:"hello-web" => Example {
|
||||
filename: "Hello.roc",
|
||||
executable_filename: "hello-web",
|
||||
|
|
|
@ -10,10 +10,8 @@ description = "Our own markup language for Roc code. Used by the editor and (soo
|
|||
roc_ast = { path = "../ast" }
|
||||
roc_module = { path = "../compiler/module" }
|
||||
roc_utils = { path = "../utils" }
|
||||
serde = { version = "1.0.123", features = ["derive"] }
|
||||
palette = "0.5"
|
||||
snafu = { version = "0.6", features = ["backtraces"] }
|
||||
bumpalo = { version = "3.2", features = ["collections"] }
|
||||
serde = { version = "1.0.130", features = ["derive"] }
|
||||
palette = "0.6.0"
|
||||
snafu = { version = "0.6.10", features = ["backtraces"] }
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
itertools = "0.10.1"
|
||||
|
||||
[dev-dependencies]
|
|
@ -1,4 +1,4 @@
|
|||
use palette::{Hsv, LinSrgb};
|
||||
use palette::{FromColor, Hsv, Srgb};
|
||||
|
||||
pub type RgbaTup = (f32, f32, f32, f32);
|
||||
pub const WHITE: RgbaTup = (1.0, 1.0, 1.0, 1.0);
|
||||
|
@ -12,7 +12,7 @@ pub fn from_hsb(hue: usize, saturation: usize, brightness: usize) -> RgbaTup {
|
|||
}
|
||||
|
||||
pub fn from_hsba(hue: usize, saturation: usize, brightness: usize, alpha: f32) -> RgbaTup {
|
||||
let rgb = LinSrgb::from(Hsv::new(
|
||||
let rgb = Srgb::from_color(Hsv::new(
|
||||
hue as f32,
|
||||
(saturation as f32) / 100.0,
|
||||
(brightness as f32) / 100.0,
|
||||
|
|
|
@ -6,6 +6,3 @@ license = "UPL-1.0"
|
|||
repository = "https://github.com/rtfeldman/roc"
|
||||
edition = "2018"
|
||||
description = "A CLI for Roc"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.5.1"
|
||||
|
|
|
@ -20,32 +20,29 @@ roc_solve = { path = "../solve" }
|
|||
roc_mono = { path = "../mono" }
|
||||
roc_load = { path = "../load" }
|
||||
roc_gen_llvm = { path = "../gen_llvm", optional = true }
|
||||
roc_gen_wasm = { path = "../gen_wasm" }
|
||||
roc_gen_dev = { path = "../gen_dev" }
|
||||
roc_gen_wasm = { path = "../gen_wasm", optional = true }
|
||||
roc_gen_dev = { path = "../gen_dev", default-features = false }
|
||||
roc_reporting = { path = "../reporting" }
|
||||
roc_std = { path = "../../roc_std" }
|
||||
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.6.1", features = ["collections"] }
|
||||
inlinable_string = "0.1.0"
|
||||
libloading = "0.6"
|
||||
tempfile = "3.1.0"
|
||||
serde_json = "1.0"
|
||||
im = "15.0.0"
|
||||
im-rc = "15.0.0"
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
libloading = "0.7.1"
|
||||
tempfile = "3.2.0"
|
||||
inkwell = { path = "../../vendor/inkwell", optional = true }
|
||||
target-lexicon = "0.12.2"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.5.1"
|
||||
maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
quickcheck_macros = "0.8"
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
serde_json = "1.0.69"
|
||||
|
||||
[features]
|
||||
default = ["llvm", "target-webassembly", "target-aarch64"]
|
||||
default = ["llvm", "target-aarch64", "target-x86_64", "target-wasm32"]
|
||||
target-arm = []
|
||||
target-aarch64 = []
|
||||
target-webassembly = []
|
||||
target-aarch64 = ["roc_gen_dev/target-aarch64"]
|
||||
target-x86 = []
|
||||
target-x86_64 = ["roc_gen_dev/target-x86_64"]
|
||||
target-wasm32 = ["roc_gen_wasm"]
|
||||
|
||||
# This is a separate feature because when we generate docs on Netlify,
|
||||
# it doesn't have LLVM installed. (Also, it doesn't need to do code gen.)
|
||||
llvm = ["inkwell", "roc_gen_llvm"]
|
||||
|
|
|
@ -300,6 +300,40 @@ pub fn build_c_host_native(
|
|||
command.output().unwrap()
|
||||
}
|
||||
|
||||
pub fn build_swift_host_native(
|
||||
env_path: &str,
|
||||
env_home: &str,
|
||||
dest: &str,
|
||||
sources: &[&str],
|
||||
opt_level: OptLevel,
|
||||
shared_lib_path: Option<&Path>,
|
||||
objc_header_path: Option<&str>,
|
||||
) -> Output {
|
||||
if shared_lib_path.is_some() {
|
||||
unimplemented!("Linking a shared library to Swift not yet implemented");
|
||||
}
|
||||
|
||||
let mut command = Command::new("swiftc");
|
||||
command
|
||||
.env_clear()
|
||||
.env("PATH", &env_path)
|
||||
.env("HOME", &env_home)
|
||||
.args(sources)
|
||||
.arg("-emit-object")
|
||||
.arg("-parse-as-library")
|
||||
.args(&["-o", dest]);
|
||||
|
||||
if let Some(objc_header) = objc_header_path {
|
||||
command.args(&["-import-objc-header", objc_header]);
|
||||
}
|
||||
|
||||
if matches!(opt_level, OptLevel::Optimize) {
|
||||
command.arg("-O");
|
||||
}
|
||||
|
||||
command.output().unwrap()
|
||||
}
|
||||
|
||||
pub fn rebuild_host(
|
||||
opt_level: OptLevel,
|
||||
target: &Triple,
|
||||
|
@ -312,6 +346,9 @@ pub fn rebuild_host(
|
|||
let rust_host_src = host_input_path.with_file_name("host.rs");
|
||||
let rust_host_dest = host_input_path.with_file_name("rust_host.o");
|
||||
let cargo_host_src = host_input_path.with_file_name("Cargo.toml");
|
||||
let swift_host_src = host_input_path.with_file_name("host.swift");
|
||||
let swift_host_header_src = host_input_path.with_file_name("host.h");
|
||||
|
||||
let host_dest_native = host_input_path.with_file_name(if shared_lib_path.is_some() {
|
||||
"dynhost"
|
||||
} else {
|
||||
|
@ -540,6 +577,20 @@ pub fn rebuild_host(
|
|||
shared_lib_path,
|
||||
);
|
||||
validate_output("host.c", "clang", output);
|
||||
} else if swift_host_src.exists() {
|
||||
// Compile host.swift, if it exists
|
||||
let output = build_swift_host_native(
|
||||
&env_path,
|
||||
&env_home,
|
||||
host_dest_native.to_str().unwrap(),
|
||||
&[swift_host_src.to_str().unwrap()],
|
||||
opt_level,
|
||||
shared_lib_path,
|
||||
swift_host_header_src
|
||||
.exists()
|
||||
.then(|| swift_host_header_src.to_str().unwrap()),
|
||||
);
|
||||
validate_output("host.swift", "swiftc", output);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -690,7 +741,7 @@ fn link_linux(
|
|||
.args(&[
|
||||
"--gc-sections",
|
||||
"--eh-frame-hdr",
|
||||
"--arch",
|
||||
"-A",
|
||||
arch_str(target),
|
||||
"-pie",
|
||||
libcrt_path.join("crti.o").to_str().unwrap(),
|
||||
|
@ -738,22 +789,14 @@ fn link_macos(
|
|||
}
|
||||
};
|
||||
|
||||
// This path only exists on macOS Big Sur, and it causes ld errors
|
||||
// on Catalina if it's specified with -L, so we replace it with a
|
||||
// redundant -lSystem if the directory isn't there.
|
||||
let big_sur_path = "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib";
|
||||
let big_sur_fix = if Path::new(big_sur_path).exists() {
|
||||
format!("-L{}", big_sur_path)
|
||||
} else {
|
||||
String::from("-lSystem")
|
||||
};
|
||||
|
||||
let arch = match target.architecture {
|
||||
Architecture::Aarch64(_) => "arm64".to_string(),
|
||||
_ => target.architecture.to_string(),
|
||||
};
|
||||
|
||||
let mut ld_child = Command::new("ld")
|
||||
let mut ld_command = Command::new("ld");
|
||||
|
||||
ld_command
|
||||
// NOTE: order of arguments to `ld` matters here!
|
||||
// The `-l` flags should go after the `.o` arguments
|
||||
// Don't allow LD_ env vars to affect this
|
||||
|
@ -767,12 +810,20 @@ fn link_macos(
|
|||
link_type_arg,
|
||||
"-arch",
|
||||
&arch,
|
||||
"-macos_version_min",
|
||||
&get_macos_version(),
|
||||
])
|
||||
.args(input_paths)
|
||||
.args(&[
|
||||
.args(input_paths);
|
||||
|
||||
let sdk_path = "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib";
|
||||
if Path::new(sdk_path).exists() {
|
||||
ld_command.arg(format!("-L{}", sdk_path));
|
||||
ld_command.arg(format!("-L{}/swift", sdk_path));
|
||||
};
|
||||
|
||||
ld_command.args(&[
|
||||
// Libraries - see https://github.com/rtfeldman/roc/pull/554#discussion_r496392274
|
||||
// for discussion and further references
|
||||
&big_sur_fix,
|
||||
"-lSystem",
|
||||
"-lresolv",
|
||||
"-lpthread",
|
||||
|
@ -784,22 +835,40 @@ fn link_macos(
|
|||
// Output
|
||||
"-o",
|
||||
output_path.to_str().unwrap(), // app
|
||||
])
|
||||
.spawn()?;
|
||||
]);
|
||||
|
||||
let mut ld_child = ld_command.spawn()?;
|
||||
|
||||
match target.architecture {
|
||||
Architecture::Aarch64(_) => {
|
||||
ld_child.wait()?;
|
||||
let child = Command::new("codesign")
|
||||
let codesign_child = Command::new("codesign")
|
||||
.args(&["-s", "-", output_path.to_str().unwrap()])
|
||||
.spawn()?;
|
||||
|
||||
Ok((child, output_path))
|
||||
Ok((codesign_child, output_path))
|
||||
}
|
||||
_ => Ok((ld_child, output_path)),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_macos_version() -> String {
|
||||
let cmd_stdout = Command::new("sw_vers")
|
||||
.arg("-productVersion")
|
||||
.output()
|
||||
.expect("Failed to execute command 'sw_vers -productVersion'")
|
||||
.stdout;
|
||||
|
||||
let full_version_string = String::from_utf8(cmd_stdout)
|
||||
.expect("Failed to convert output of command 'sw_vers -productVersion' into a utf8 string");
|
||||
|
||||
full_version_string
|
||||
.split('.')
|
||||
.take(2)
|
||||
.collect::<Vec<&str>>()
|
||||
.join(".")
|
||||
}
|
||||
|
||||
fn link_wasm32(
|
||||
_target: &Triple,
|
||||
output_path: PathBuf,
|
||||
|
@ -876,7 +945,7 @@ pub fn module_to_dylib(
|
|||
// Load the dylib
|
||||
let path = dylib_path.as_path().to_str().unwrap();
|
||||
|
||||
Library::new(path)
|
||||
unsafe { Library::new(path) }
|
||||
}
|
||||
|
||||
fn validate_output(file_name: &str, cmd_name: &str, output: Output) {
|
||||
|
|
|
@ -4,12 +4,13 @@ use roc_gen_llvm::llvm::build::module_from_builtins;
|
|||
pub use roc_gen_llvm::llvm::build::FunctionIterator;
|
||||
use roc_load::file::{LoadedModule, MonomorphizedModule};
|
||||
use roc_module::symbol::{Interns, ModuleId};
|
||||
#[cfg(feature = "llvm")]
|
||||
use roc_mono::ir::OptLevel;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
|
||||
use roc_collections::all::{MutMap, MutSet};
|
||||
use roc_collections::all::MutMap;
|
||||
#[cfg(feature = "target-wasm32")]
|
||||
use roc_collections::all::MutSet;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct CodeGenTiming {
|
||||
|
@ -19,6 +20,7 @@ pub struct CodeGenTiming {
|
|||
|
||||
// TODO: If modules besides this one start needing to know which version of
|
||||
// llvm we're using, consider moving me somewhere else.
|
||||
#[cfg(feature = "llvm")]
|
||||
const LLVM_VERSION: &str = "12";
|
||||
|
||||
// TODO instead of finding exhaustiveness problems in monomorphization, find
|
||||
|
@ -171,6 +173,50 @@ fn report_problems_help(
|
|||
problems_reported
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "llvm"))]
|
||||
pub fn gen_from_mono_module(
|
||||
arena: &bumpalo::Bump,
|
||||
loaded: MonomorphizedModule,
|
||||
_roc_file_path: &Path,
|
||||
target: &target_lexicon::Triple,
|
||||
app_o_file: &Path,
|
||||
opt_level: OptLevel,
|
||||
_emit_debug_info: bool,
|
||||
) -> CodeGenTiming {
|
||||
match opt_level {
|
||||
OptLevel::Optimize => {
|
||||
todo!("Return this error message in a better way: optimized builds not supported without llvm backend");
|
||||
}
|
||||
OptLevel::Normal | OptLevel::Development => {
|
||||
gen_from_mono_module_dev(arena, loaded, target, app_o_file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "llvm")]
|
||||
pub fn gen_from_mono_module(
|
||||
arena: &bumpalo::Bump,
|
||||
loaded: MonomorphizedModule,
|
||||
roc_file_path: &Path,
|
||||
target: &target_lexicon::Triple,
|
||||
app_o_file: &Path,
|
||||
opt_level: OptLevel,
|
||||
emit_debug_info: bool,
|
||||
) -> CodeGenTiming {
|
||||
match opt_level {
|
||||
OptLevel::Normal | OptLevel::Optimize => gen_from_mono_module_llvm(
|
||||
arena,
|
||||
loaded,
|
||||
roc_file_path,
|
||||
target,
|
||||
app_o_file,
|
||||
opt_level,
|
||||
emit_debug_info,
|
||||
),
|
||||
OptLevel::Development => gen_from_mono_module_dev(arena, loaded, target, app_o_file),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO how should imported modules factor into this? What if those use builtins too?
|
||||
// TODO this should probably use more helper functions
|
||||
// TODO make this polymorphic in the llvm functions so it can be reused for another backend.
|
||||
|
@ -404,7 +450,7 @@ pub fn gen_from_mono_module_llvm(
|
|||
emit_o_file,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "target-wasm32")]
|
||||
pub fn gen_from_mono_module_dev(
|
||||
arena: &bumpalo::Bump,
|
||||
loaded: MonomorphizedModule,
|
||||
|
@ -415,13 +461,31 @@ pub fn gen_from_mono_module_dev(
|
|||
|
||||
match target.architecture {
|
||||
Architecture::Wasm32 => gen_from_mono_module_dev_wasm32(arena, loaded, app_o_file),
|
||||
Architecture::X86_64 => {
|
||||
Architecture::X86_64 | Architecture::Aarch64(_) => {
|
||||
gen_from_mono_module_dev_assembly(arena, loaded, target, app_o_file)
|
||||
}
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "target-wasm32"))]
|
||||
pub fn gen_from_mono_module_dev(
|
||||
arena: &bumpalo::Bump,
|
||||
loaded: MonomorphizedModule,
|
||||
target: &target_lexicon::Triple,
|
||||
app_o_file: &Path,
|
||||
) -> CodeGenTiming {
|
||||
use target_lexicon::Architecture;
|
||||
|
||||
match target.architecture {
|
||||
Architecture::X86_64 | Architecture::Aarch64(_) => {
|
||||
gen_from_mono_module_dev_assembly(arena, loaded, target, app_o_file)
|
||||
}
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "target-wasm32")]
|
||||
fn gen_from_mono_module_dev_wasm32(
|
||||
arena: &bumpalo::Bump,
|
||||
loaded: MonomorphizedModule,
|
||||
|
|
|
@ -48,7 +48,9 @@ pub fn target_triple_str(target: &Triple) -> &'static str {
|
|||
#[cfg(feature = "llvm")]
|
||||
pub fn init_arch(target: &Triple) {
|
||||
match target.architecture {
|
||||
Architecture::X86_64 | Architecture::X86_32(_) => {
|
||||
Architecture::X86_64 | Architecture::X86_32(_)
|
||||
if cfg!(any(feature = "target-x86", feature = "target-x86_64")) =>
|
||||
{
|
||||
Target::initialize_x86(&InitializationConfig::default());
|
||||
}
|
||||
Architecture::Aarch64(_) if cfg!(feature = "target-aarch64") => {
|
||||
|
@ -57,7 +59,7 @@ pub fn init_arch(target: &Triple) {
|
|||
Architecture::Arm(_) if cfg!(feature = "target-arm") => {
|
||||
Target::initialize_arm(&InitializationConfig::default());
|
||||
}
|
||||
Architecture::Wasm32 if cfg!(feature = "target-webassembly") => {
|
||||
Architecture::Wasm32 if cfg!(feature = "target-wasm32") => {
|
||||
Target::initialize_webassembly(&InitializationConfig::default());
|
||||
}
|
||||
_ => panic!(
|
||||
|
@ -75,8 +77,8 @@ pub fn arch_str(target: &Triple) -> &'static str {
|
|||
//
|
||||
// https://stackoverflow.com/questions/15036909/clang-how-to-list-supported-target-architectures
|
||||
match target.architecture {
|
||||
Architecture::X86_64 => "x86-64",
|
||||
Architecture::X86_32(_) => "x86",
|
||||
Architecture::X86_64 if cfg!(feature = "target-x86_64") => "x86-64",
|
||||
Architecture::X86_32(_) if cfg!(feature = "target-x86") => "x86",
|
||||
Architecture::Aarch64(_) if cfg!(feature = "target-aarch64") => "aarch64",
|
||||
Architecture::Arm(_) if cfg!(feature = "target-arm") => "arm",
|
||||
Architecture::Wasm32 if cfg!(feature = "target-webassembly") => "wasm32",
|
||||
|
|
|
@ -10,9 +10,3 @@ roc_collections = { path = "../collections" }
|
|||
roc_region = { path = "../region" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_types = { path = "../types" }
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.5.1"
|
||||
maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
quickcheck_macros = "0.8"
|
||||
|
|
|
@ -42,7 +42,7 @@ fn list_repeat(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|||
)
|
||||
}
|
||||
```
|
||||
In these builtin definitions you will need to allocate for and list the arguments. For `List.repeat`, the arguments are the `elem_var` and the `len_var`. So in both the `body` and `defn` we list these arguments in a vector, with the `Symobl::ARG_1` and` Symvol::ARG_2` designating which argument is which.
|
||||
In these builtin definitions you will need to allocate for and list the arguments. For `List.repeat`, the arguments are the `elem_var` and the `len_var`. So in both the `body` and `defn` we list these arguments in a vector, with the `Symbol::ARG_1` and` Symvol::ARG_2` designating which argument is which.
|
||||
|
||||
Since `List.repeat` is implemented entirely as low level functions, its `body` is a `RunLowLevel`, and the `op` is `LowLevel::ListRepeat`. Lets talk about `LowLevel` in the next section.
|
||||
|
||||
|
@ -60,11 +60,11 @@ Its one thing to actually write these functions, its _another_ thing to let the
|
|||
|
||||
## Specifying how we pass args to the function
|
||||
### builtins/mono/src/borrow.rs
|
||||
After we have all of this, we need to specify if the arguments we're passing are owned, borrowed or irrelvant. Towards the bottom of this file, add a new case for you builtin and specify each arg. Be sure to read the comment, as it explains this in more detail.
|
||||
After we have all of this, we need to specify if the arguments we're passing are owned, borrowed or irrelevant. Towards the bottom of this file, add a new case for you builtin and specify each arg. Be sure to read the comment, as it explains this in more detail.
|
||||
|
||||
## Testing it
|
||||
### solve/tests/solve_expr.rs
|
||||
To make sure that Roc is properly inferring the type of the new builtin, add a test to this file simlar to:
|
||||
To make sure that Roc is properly inferring the type of the new builtin, add a test to this file similar to:
|
||||
```
|
||||
#[test]
|
||||
fn atan() {
|
||||
|
|
|
@ -6,4 +6,4 @@ set -euxo pipefail
|
|||
zig build test
|
||||
|
||||
# fmt every zig
|
||||
find src/*.zig -type f -print0 | xargs -n 1 -0 zig fmt --check || (echo "zig fmt --check FAILED! Check the previuous lines to see which files were improperly formatted." && exit 1)
|
||||
find src/*.zig -type f -print0 | xargs -n 1 -0 zig fmt --check || (echo "zig fmt --check FAILED! Check the previous lines to see which files were improperly formatted." && exit 1)
|
||||
|
|
|
@ -188,7 +188,7 @@ test "" {
|
|||
//
|
||||
// Thank you Zig Contributors!
|
||||
|
||||
// Export it as weak incase it is alreadly linked in by something else.
|
||||
// Export it as weak incase it is already linked in by something else.
|
||||
comptime {
|
||||
@export(__muloti4, .{ .name = "__muloti4", .linkage = .Weak });
|
||||
}
|
||||
|
|
|
@ -264,7 +264,7 @@ pub const RocStr = extern struct {
|
|||
}
|
||||
|
||||
// Returns (@sizeOf(RocStr) - 1) for small strings and the empty string.
|
||||
// Returns 0 for refcounted stirngs and immortal strings.
|
||||
// Returns 0 for refcounted strings and immortal strings.
|
||||
// Returns the stored capacity value for all other strings.
|
||||
pub fn capacity(self: RocStr) usize {
|
||||
const length = self.len();
|
||||
|
|
|
@ -15,7 +15,7 @@ extern fn roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignmen
|
|||
// This should never be passed a null pointer.
|
||||
extern fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void;
|
||||
|
||||
// Signals to the host that the program has paniced
|
||||
// Signals to the host that the program has panicked
|
||||
extern fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void;
|
||||
|
||||
comptime {
|
||||
|
@ -50,7 +50,7 @@ fn testing_roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void {
|
|||
_ = c_ptr;
|
||||
_ = tag_id;
|
||||
|
||||
@panic("Roc paniced");
|
||||
@panic("Roc panicked");
|
||||
}
|
||||
|
||||
pub fn alloc(size: usize, alignment: u32) [*]u8 {
|
||||
|
@ -70,7 +70,7 @@ pub fn panic(c_ptr: *c_void, alignment: u32) callconv(.C) void {
|
|||
return @call(.{ .modifier = always_inline }, roc_panic, .{ c_ptr, alignment });
|
||||
}
|
||||
|
||||
// indirection because otherwise zig creats an alias to the panic function which our LLVM code
|
||||
// indirection because otherwise zig creates an alias to the panic function which our LLVM code
|
||||
// does not know how to deal with
|
||||
pub fn test_panic(c_ptr: *c_void, alignment: u32) callconv(.C) void {
|
||||
_ = c_ptr;
|
||||
|
|
|
@ -384,7 +384,7 @@ oks : List (Result elem *) -> List elem
|
|||
## ## Performance Details
|
||||
##
|
||||
## [List.keepIf] always returns a list that takes up exactly the same amount
|
||||
## of memory as the original, even if its length decreases. This is becase it
|
||||
## of memory as the original, even if its length decreases. This is because it
|
||||
## can't know in advance exactly how much space it will need, and if it guesses a
|
||||
## length that's too low, it would have to re-allocate.
|
||||
##
|
||||
|
|
|
@ -395,7 +395,7 @@ Nat : Int [ @Natural ]
|
|||
##
|
||||
## A common use for #Nat is to store the length ("len" for short) of a
|
||||
## collection like #List, #Set, or #Map. 64-bit systems can represent longer
|
||||
## lists in memory than 32-bit sytems can, which is why the length of a list
|
||||
## lists in memory than 32-bit systems can, which is why the length of a list
|
||||
## is represented as a #Nat in Roc.
|
||||
##
|
||||
## If any operation would result in an #Int that is either too big
|
||||
|
|
|
@ -979,6 +979,13 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
|||
Box::new(list_type(flex(TVAR1))),
|
||||
);
|
||||
|
||||
// dropFirst : List elem -> List elem
|
||||
add_top_level_function_type!(
|
||||
Symbol::LIST_DROP_FIRST,
|
||||
vec![list_type(flex(TVAR1))],
|
||||
Box::new(list_type(flex(TVAR1))),
|
||||
);
|
||||
|
||||
// swap : List elem, Nat, Nat -> List elem
|
||||
add_top_level_function_type!(
|
||||
Symbol::LIST_SWAP,
|
||||
|
|
|
@ -14,13 +14,10 @@ roc_problem = { path = "../problem" }
|
|||
roc_types = { path = "../types" }
|
||||
roc_builtins = { path = "../builtins" }
|
||||
ven_graph = { path = "../../vendor/pathfinding" }
|
||||
im = "14" # im and im-rc should always have the same version!
|
||||
im-rc = "14" # im and im-rc should always have the same version!
|
||||
bumpalo = { version = "3.6.1", features = ["collections"] }
|
||||
im = "15.0.0"
|
||||
im-rc = "15.0.0"
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.5.1"
|
||||
maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
quickcheck_macros = "0.8"
|
||||
pretty_assertions = "1.0.0"
|
||||
indoc = "1.0.3"
|
||||
|
|
|
@ -20,7 +20,7 @@ pub struct Annotation {
|
|||
pub struct IntroducedVariables {
|
||||
// NOTE on rigids
|
||||
//
|
||||
// Rigids must be unique within a type annoation.
|
||||
// Rigids must be unique within a type annotation.
|
||||
// E.g. in `identity : a -> a`, there should only be one
|
||||
// variable (a rigid one, with name "a").
|
||||
// Hence `rigids : ImMap<Lowercase, Variable>`
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::def::Def;
|
||||
use crate::expr::Expr::*;
|
||||
use crate::expr::{ClosureData, Expr::*};
|
||||
use crate::expr::{Expr, Recursive, WhenBranch};
|
||||
use crate::pattern::Pattern;
|
||||
use roc_collections::all::SendMap;
|
||||
|
@ -92,6 +92,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
|
|||
LIST_MAP4 => list_map4,
|
||||
LIST_DROP => list_drop,
|
||||
LIST_DROP_AT => list_drop_at,
|
||||
LIST_DROP_FIRST => list_drop_first,
|
||||
LIST_DROP_LAST => list_drop_last,
|
||||
LIST_SWAP => list_swap,
|
||||
LIST_MAP_WITH_INDEX => list_map_with_index,
|
||||
|
@ -2049,6 +2050,30 @@ fn list_drop_at(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|||
)
|
||||
}
|
||||
|
||||
fn list_drop_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let list_var = var_store.fresh();
|
||||
let index_var = var_store.fresh();
|
||||
let num_var = Variable::NAT;
|
||||
let num_precision_var = Variable::NATURAL;
|
||||
|
||||
let body = RunLowLevel {
|
||||
op: LowLevel::ListDropAt,
|
||||
args: vec![
|
||||
(list_var, Var(Symbol::ARG_1)),
|
||||
(index_var, int(num_var, num_precision_var, 0)),
|
||||
],
|
||||
ret_var: list_var,
|
||||
};
|
||||
|
||||
defn(
|
||||
symbol,
|
||||
vec![(list_var, Symbol::ARG_1)],
|
||||
var_store,
|
||||
body,
|
||||
list_var,
|
||||
)
|
||||
}
|
||||
|
||||
/// List.dropLast: List elem -> List elem
|
||||
fn list_drop_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let list_var = var_store.fresh();
|
||||
|
@ -2930,7 +2955,7 @@ fn set_walk(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|||
CalledVia::Space,
|
||||
);
|
||||
|
||||
let wrapper = Closure {
|
||||
let wrapper = Closure(ClosureData {
|
||||
function_type: wrapper_var,
|
||||
closure_type: var_store.fresh(),
|
||||
closure_ext_var: var_store.fresh(),
|
||||
|
@ -2944,7 +2969,7 @@ fn set_walk(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|||
(Variable::EMPTY_RECORD, no_region(Pattern::Underscore)),
|
||||
],
|
||||
loc_body: Box::new(no_region(call_func)),
|
||||
};
|
||||
});
|
||||
|
||||
let body = RunLowLevel {
|
||||
op: LowLevel::DictWalk,
|
||||
|
@ -3959,7 +3984,7 @@ fn defn_help(
|
|||
.map(|(var, symbol)| (var, no_region(Identifier(symbol))))
|
||||
.collect();
|
||||
|
||||
Closure {
|
||||
Closure(ClosureData {
|
||||
function_type: var_store.fresh(),
|
||||
closure_type: var_store.fresh(),
|
||||
closure_ext_var: var_store.fresh(),
|
||||
|
@ -3969,7 +3994,7 @@ fn defn_help(
|
|||
recursive: Recursive::NotRecursive,
|
||||
arguments: closure_args,
|
||||
loc_body: Box::new(no_region(body)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use crate::annotation::canonicalize_annotation;
|
||||
use crate::annotation::IntroducedVariables;
|
||||
use crate::env::Env;
|
||||
use crate::expr::ClosureData;
|
||||
use crate::expr::Expr::{self, *};
|
||||
use crate::expr::{
|
||||
canonicalize_expr, local_successors, references_from_call, references_from_local, Output,
|
||||
|
@ -670,10 +671,10 @@ fn group_to_declaration(
|
|||
let mut new_def = can_def.clone();
|
||||
|
||||
// Determine recursivity of closures that are not tail-recursive
|
||||
if let Closure {
|
||||
if let Closure(ClosureData {
|
||||
recursive: recursive @ Recursive::NotRecursive,
|
||||
..
|
||||
} = &mut new_def.loc_expr.value
|
||||
}) = &mut new_def.loc_expr.value
|
||||
{
|
||||
*recursive = closure_recursivity(*symbol, closures);
|
||||
}
|
||||
|
@ -698,10 +699,10 @@ fn group_to_declaration(
|
|||
let mut new_def = can_def.clone();
|
||||
|
||||
// Determine recursivity of closures that are not tail-recursive
|
||||
if let Closure {
|
||||
if let Closure(ClosureData {
|
||||
recursive: recursive @ Recursive::NotRecursive,
|
||||
..
|
||||
} = &mut new_def.loc_expr.value
|
||||
}) = &mut new_def.loc_expr.value
|
||||
{
|
||||
*recursive = closure_recursivity(symbol, closures);
|
||||
}
|
||||
|
@ -787,7 +788,7 @@ fn canonicalize_pending_def<'a>(
|
|||
output.references.referenced_aliases.insert(symbol);
|
||||
}
|
||||
|
||||
aliases.extend(ann.aliases.iter().cloned());
|
||||
aliases.extend(ann.aliases.clone());
|
||||
|
||||
output.introduced_variables.union(&ann.introduced_variables);
|
||||
|
||||
|
@ -838,7 +839,7 @@ fn canonicalize_pending_def<'a>(
|
|||
};
|
||||
|
||||
Located {
|
||||
value: Closure {
|
||||
value: Closure(ClosureData {
|
||||
function_type: var_store.fresh(),
|
||||
closure_type: var_store.fresh(),
|
||||
closure_ext_var: var_store.fresh(),
|
||||
|
@ -848,7 +849,7 @@ fn canonicalize_pending_def<'a>(
|
|||
recursive: Recursive::NotRecursive,
|
||||
arguments: underscores,
|
||||
loc_body: Box::new(body_expr),
|
||||
},
|
||||
}),
|
||||
region: loc_ann.region,
|
||||
}
|
||||
};
|
||||
|
@ -972,7 +973,7 @@ fn canonicalize_pending_def<'a>(
|
|||
env.tailcallable_symbol = Some(*defined_symbol);
|
||||
};
|
||||
|
||||
// regiser the name of this closure, to make sure the closure won't capture it's own name
|
||||
// register the name of this closure, to make sure the closure won't capture it's own name
|
||||
if let (Pattern::Identifier(ref defined_symbol), &ast::Expr::Closure(_, _)) =
|
||||
(&loc_can_pattern.value, &loc_expr.value)
|
||||
{
|
||||
|
@ -1001,7 +1002,7 @@ fn canonicalize_pending_def<'a>(
|
|||
if let (
|
||||
&ast::Pattern::Identifier(_name),
|
||||
&Pattern::Identifier(ref defined_symbol),
|
||||
&Closure {
|
||||
&Closure(ClosureData {
|
||||
function_type,
|
||||
closure_type,
|
||||
closure_ext_var,
|
||||
|
@ -1011,7 +1012,7 @@ fn canonicalize_pending_def<'a>(
|
|||
loc_body: ref body,
|
||||
ref captured_symbols,
|
||||
..
|
||||
},
|
||||
}),
|
||||
) = (
|
||||
&loc_pattern.value,
|
||||
&loc_can_pattern.value,
|
||||
|
@ -1049,7 +1050,7 @@ fn canonicalize_pending_def<'a>(
|
|||
});
|
||||
|
||||
// renamed_closure_def = Some(&defined_symbol);
|
||||
loc_can_expr.value = Closure {
|
||||
loc_can_expr.value = Closure(ClosureData {
|
||||
function_type,
|
||||
closure_type,
|
||||
closure_ext_var,
|
||||
|
@ -1059,7 +1060,7 @@ fn canonicalize_pending_def<'a>(
|
|||
recursive: is_recursive,
|
||||
arguments: arguments.clone(),
|
||||
loc_body: body.clone(),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// Store the referenced locals in the refs_by_symbol map, so we can later figure out
|
||||
|
@ -1119,7 +1120,7 @@ fn canonicalize_pending_def<'a>(
|
|||
vars_by_symbol.insert(*defined_symbol, expr_var);
|
||||
};
|
||||
|
||||
// regiser the name of this closure, to make sure the closure won't capture it's own name
|
||||
// register the name of this closure, to make sure the closure won't capture it's own name
|
||||
if let (Pattern::Identifier(ref defined_symbol), &ast::Expr::Closure(_, _)) =
|
||||
(&loc_can_pattern.value, &loc_expr.value)
|
||||
{
|
||||
|
@ -1144,7 +1145,7 @@ fn canonicalize_pending_def<'a>(
|
|||
if let (
|
||||
&ast::Pattern::Identifier(_name),
|
||||
&Pattern::Identifier(ref defined_symbol),
|
||||
&Closure {
|
||||
&Closure(ClosureData {
|
||||
function_type,
|
||||
closure_type,
|
||||
closure_ext_var,
|
||||
|
@ -1154,7 +1155,7 @@ fn canonicalize_pending_def<'a>(
|
|||
loc_body: ref body,
|
||||
ref captured_symbols,
|
||||
..
|
||||
},
|
||||
}),
|
||||
) = (
|
||||
&loc_pattern.value,
|
||||
&loc_can_pattern.value,
|
||||
|
@ -1191,7 +1192,7 @@ fn canonicalize_pending_def<'a>(
|
|||
refs.lookups = refs.lookups.without(defined_symbol);
|
||||
});
|
||||
|
||||
loc_can_expr.value = Closure {
|
||||
loc_can_expr.value = Closure(ClosureData {
|
||||
function_type,
|
||||
closure_type,
|
||||
closure_ext_var,
|
||||
|
@ -1201,7 +1202,7 @@ fn canonicalize_pending_def<'a>(
|
|||
recursive: is_recursive,
|
||||
arguments: arguments.clone(),
|
||||
loc_body: body.clone(),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// Store the referenced locals in the refs_by_symbol map, so we can later figure out
|
||||
|
|
|
@ -102,17 +102,7 @@ pub enum Expr {
|
|||
ret_var: Variable,
|
||||
},
|
||||
|
||||
Closure {
|
||||
function_type: Variable,
|
||||
closure_type: Variable,
|
||||
closure_ext_var: Variable,
|
||||
return_type: Variable,
|
||||
name: Symbol,
|
||||
captured_symbols: Vec<(Symbol, Variable)>,
|
||||
recursive: Recursive,
|
||||
arguments: Vec<(Variable, Located<Pattern>)>,
|
||||
loc_body: Box<Located<Expr>>,
|
||||
},
|
||||
Closure(ClosureData),
|
||||
|
||||
// Product Types
|
||||
Record {
|
||||
|
@ -173,6 +163,18 @@ pub enum Expr {
|
|||
// Compiles, but will crash if reached
|
||||
RuntimeError(RuntimeError),
|
||||
}
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct ClosureData {
|
||||
pub function_type: Variable,
|
||||
pub closure_type: Variable,
|
||||
pub closure_ext_var: Variable,
|
||||
pub return_type: Variable,
|
||||
pub name: Symbol,
|
||||
pub captured_symbols: Vec<(Symbol, Variable)>,
|
||||
pub recursive: Recursive,
|
||||
pub arguments: Vec<(Variable, Located<Pattern>)>,
|
||||
pub loc_body: Box<Located<Expr>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Field {
|
||||
|
@ -550,7 +552,7 @@ pub fn canonicalize_expr<'a>(
|
|||
|
||||
// We shouldn't ultimately count arguments as referenced locals. Otherwise,
|
||||
// we end up with weird conclusions like the expression (\x -> x + 1)
|
||||
// references the (nonexistant) local variable x!
|
||||
// references the (nonexistent) local variable x!
|
||||
output.references.lookups.remove(sub_symbol);
|
||||
}
|
||||
}
|
||||
|
@ -572,7 +574,7 @@ pub fn canonicalize_expr<'a>(
|
|||
}
|
||||
|
||||
(
|
||||
Closure {
|
||||
Closure(ClosureData {
|
||||
function_type: var_store.fresh(),
|
||||
closure_type: var_store.fresh(),
|
||||
closure_ext_var: var_store.fresh(),
|
||||
|
@ -582,7 +584,7 @@ pub fn canonicalize_expr<'a>(
|
|||
recursive: Recursive::NotRecursive,
|
||||
arguments: can_args,
|
||||
loc_body: Box::new(loc_body_expr),
|
||||
},
|
||||
}),
|
||||
output,
|
||||
)
|
||||
}
|
||||
|
@ -1403,7 +1405,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
|
|||
LetNonRec(Box::new(def), Box::new(loc_expr), var)
|
||||
}
|
||||
|
||||
Closure {
|
||||
Closure(ClosureData {
|
||||
function_type,
|
||||
closure_type,
|
||||
closure_ext_var,
|
||||
|
@ -1413,14 +1415,14 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
|
|||
captured_symbols,
|
||||
arguments,
|
||||
loc_body,
|
||||
} => {
|
||||
}) => {
|
||||
let loc_expr = *loc_body;
|
||||
let loc_expr = Located {
|
||||
value: inline_calls(var_store, scope, loc_expr.value),
|
||||
region: loc_expr.region,
|
||||
};
|
||||
|
||||
Closure {
|
||||
Closure(ClosureData {
|
||||
function_type,
|
||||
closure_type,
|
||||
closure_ext_var,
|
||||
|
@ -1430,7 +1432,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
|
|||
captured_symbols,
|
||||
arguments,
|
||||
loc_body: Box::new(loc_expr),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Record { record_var, fields } => {
|
||||
|
@ -1492,12 +1494,12 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
|
|||
loc_expr:
|
||||
Located {
|
||||
value:
|
||||
Closure {
|
||||
Closure(ClosureData {
|
||||
recursive,
|
||||
arguments: params,
|
||||
loc_body: boxed_body,
|
||||
..
|
||||
},
|
||||
}),
|
||||
..
|
||||
},
|
||||
..
|
||||
|
@ -1681,7 +1683,7 @@ fn flatten_str_lines<'a>(
|
|||
(desugar_str_segments(var_store, segments), output)
|
||||
}
|
||||
|
||||
/// Resolve stirng interpolations by desugaring a sequence of StrSegments
|
||||
/// Resolve string interpolations by desugaring a sequence of StrSegments
|
||||
/// into nested calls to Str.concat
|
||||
fn desugar_str_segments(var_store: &mut VarStore, segments: Vec<StrSegment>) -> Expr {
|
||||
use StrSegment::*;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::def::{canonicalize_defs, sort_can_defs, Declaration, Def};
|
||||
use crate::env::Env;
|
||||
use crate::expr::{Expr, Output};
|
||||
use crate::expr::{ClosureData, Expr, Output};
|
||||
use crate::operator::desugar_def;
|
||||
use crate::pattern::Pattern;
|
||||
use crate::scope::Scope;
|
||||
|
@ -416,13 +416,13 @@ fn fix_values_captured_in_closure_expr(
|
|||
fix_values_captured_in_closure_expr(&mut loc_expr.value, no_capture_symbols);
|
||||
}
|
||||
|
||||
Closure {
|
||||
Closure(ClosureData {
|
||||
captured_symbols,
|
||||
name,
|
||||
arguments,
|
||||
loc_body,
|
||||
..
|
||||
} => {
|
||||
}) => {
|
||||
captured_symbols.retain(|(s, _)| !no_capture_symbols.contains(s));
|
||||
captured_symbols.retain(|(s, _)| s != name);
|
||||
|
||||
|
@ -502,7 +502,7 @@ fn fix_values_captured_in_closure_expr(
|
|||
| Update {
|
||||
updates: fields, ..
|
||||
} => {
|
||||
for field in fields.iter_mut() {
|
||||
for (_, field) in fields.iter_mut() {
|
||||
fix_values_captured_in_closure_expr(&mut field.loc_expr.value, no_capture_symbols);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,11 +63,11 @@ impl Scope {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn idents(&self) -> impl Iterator<Item = &(Ident, (Symbol, Region))> {
|
||||
pub fn idents(&self) -> impl Iterator<Item = (&Ident, &(Symbol, Region))> {
|
||||
self.idents.iter()
|
||||
}
|
||||
|
||||
pub fn symbols(&self) -> impl Iterator<Item = &(Symbol, Region)> {
|
||||
pub fn symbols(&self) -> impl Iterator<Item = (&Symbol, &Region)> {
|
||||
self.symbols.iter()
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ mod test_can {
|
|||
use crate::helpers::{can_expr_with, test_home, CanExprOut};
|
||||
use bumpalo::Bump;
|
||||
use roc_can::expr::Expr::{self, *};
|
||||
use roc_can::expr::Recursive;
|
||||
use roc_can::expr::{ClosureData, Recursive};
|
||||
use roc_problem::can::{CycleEntry, FloatErrorKind, IntErrorKind, Problem, RuntimeError};
|
||||
use roc_region::all::Region;
|
||||
use std::{f64, i64};
|
||||
|
@ -654,10 +654,10 @@ mod test_can {
|
|||
match expr {
|
||||
LetRec(assignments, body, _) => {
|
||||
match &assignments.get(i).map(|def| &def.loc_expr.value) {
|
||||
Some(Closure {
|
||||
Some(Closure(ClosureData {
|
||||
recursive: recursion,
|
||||
..
|
||||
}) => recursion.clone(),
|
||||
})) => recursion.clone(),
|
||||
Some(other) => {
|
||||
panic!("assignment at {} is not a closure, but a {:?}", i, other)
|
||||
}
|
||||
|
@ -676,10 +676,10 @@ mod test_can {
|
|||
get_closure(&body.value, i - 1)
|
||||
} else {
|
||||
match &def.loc_expr.value {
|
||||
Closure {
|
||||
Closure(ClosureData {
|
||||
recursive: recursion,
|
||||
..
|
||||
} => recursion.clone(),
|
||||
}) => recursion.clone(),
|
||||
other => {
|
||||
panic!("assignment at {} is not a closure, but a {:?}", i, other)
|
||||
}
|
||||
|
|
|
@ -6,8 +6,8 @@ license = "UPL-1.0"
|
|||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
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!
|
||||
wyhash = "0.3"
|
||||
bumpalo = { version = "3.6.1", features = ["collections"] }
|
||||
im = "15.0.0"
|
||||
im-rc = "15.0.0"
|
||||
wyhash = "0.5.0"
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
hashbrown = { version = "0.11.2", features = [ "bumpalo" ] }
|
||||
|
|
|
@ -13,10 +13,3 @@ roc_parse = { path = "../parse" }
|
|||
roc_types = { path = "../types" }
|
||||
roc_can = { path = "../can" }
|
||||
roc_builtins = { path = "../builtins" }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.5.1"
|
||||
maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
quickcheck_macros = "0.8"
|
||||
|
|
|
@ -7,7 +7,7 @@ use roc_can::def::{Declaration, Def};
|
|||
use roc_can::expected::Expected::{self, *};
|
||||
use roc_can::expected::PExpected;
|
||||
use roc_can::expr::Expr::{self, *};
|
||||
use roc_can::expr::{Field, WhenBranch};
|
||||
use roc_can::expr::{ClosureData, Field, WhenBranch};
|
||||
use roc_can::pattern::Pattern;
|
||||
use roc_collections::all::{ImMap, Index, MutSet, SendMap};
|
||||
use roc_module::ident::{Lowercase, TagName};
|
||||
|
@ -333,7 +333,7 @@ pub fn constrain_expr(
|
|||
// make lookup constraint to lookup this symbol's type in the environment
|
||||
Lookup(*symbol, expected, region)
|
||||
}
|
||||
Closure {
|
||||
Closure(ClosureData {
|
||||
function_type: fn_var,
|
||||
closure_type: closure_var,
|
||||
closure_ext_var,
|
||||
|
@ -343,7 +343,7 @@ pub fn constrain_expr(
|
|||
captured_symbols,
|
||||
name,
|
||||
..
|
||||
} => {
|
||||
}) => {
|
||||
// NOTE defs are treated somewhere else!
|
||||
let loc_body_expr = &**boxed;
|
||||
|
||||
|
@ -1203,7 +1203,7 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint {
|
|||
// instead of the more generic "something is wrong with the body of `f`"
|
||||
match (&def.loc_expr.value, &signature) {
|
||||
(
|
||||
Closure {
|
||||
Closure(ClosureData {
|
||||
function_type: fn_var,
|
||||
closure_type: closure_var,
|
||||
closure_ext_var,
|
||||
|
@ -1213,7 +1213,7 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint {
|
|||
loc_body,
|
||||
name,
|
||||
..
|
||||
},
|
||||
}),
|
||||
Type::Function(arg_types, signature_closure_type, ret_type),
|
||||
) => {
|
||||
// NOTE if we ever have problems with the closure, the ignored `_closure_type`
|
||||
|
@ -1561,7 +1561,7 @@ pub fn rec_defs_help(
|
|||
// instead of the more generic "something is wrong with the body of `f`"
|
||||
match (&def.loc_expr.value, &signature) {
|
||||
(
|
||||
Closure {
|
||||
Closure(ClosureData {
|
||||
function_type: fn_var,
|
||||
closure_type: closure_var,
|
||||
closure_ext_var,
|
||||
|
@ -1571,7 +1571,7 @@ pub fn rec_defs_help(
|
|||
loc_body,
|
||||
name,
|
||||
..
|
||||
},
|
||||
}),
|
||||
Type::Function(arg_types, _closure_type, ret_type),
|
||||
) => {
|
||||
// NOTE if we ever have trouble with closure type unification, the ignored
|
||||
|
|
|
@ -10,13 +10,10 @@ roc_collections = { path = "../collections" }
|
|||
roc_region = { path = "../region" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_parse = { path = "../parse" }
|
||||
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.6.1", features = ["collections"] }
|
||||
im = "15.0.0"
|
||||
im-rc = "15.0.0"
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.5.1"
|
||||
maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
quickcheck_macros = "0.8"
|
||||
pretty_assertions = "1.0.0"
|
||||
indoc = "1.0.3"
|
||||
|
|
|
@ -9,38 +9,29 @@ edition = "2018"
|
|||
[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_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.6.1", features = ["collections"] }
|
||||
im = "15.0.0"
|
||||
im-rc = "15.0.0"
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
target-lexicon = "0.12.2"
|
||||
libloading = "0.6"
|
||||
object = { version = "0.24", features = ["write"] }
|
||||
# TODO: Deal with the update of object to 0.27.
|
||||
# It looks like it breaks linking the generated objects.
|
||||
# Probably just need to specify an extra field that used to be implicit or something.
|
||||
# When fixed also update the version of object in the linker.
|
||||
object = { version = "0.26.2", 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.6.1", features = ["collections"] }
|
||||
libc = "0.2"
|
||||
tempfile = "3.1.0"
|
||||
itertools = "0.9"
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
|
||||
[features]
|
||||
target-aarch64 = ["roc_build/target-aarch64"]
|
||||
target-aarch64 = []
|
||||
target-x86_64 = []
|
||||
|
|
|
@ -53,7 +53,7 @@ Here are example implementations for [arm](https://github.com/rtfeldman/roc/blob
|
|||
|
||||
### CallConv
|
||||
|
||||
[CallConv](https://github.com/rtfeldman/roc/blob/trunk/compiler/gen_dev/src/generic64/mod.rs) is the abstaction over caling conventions.
|
||||
[CallConv](https://github.com/rtfeldman/roc/blob/trunk/compiler/gen_dev/src/generic64/mod.rs) is the abstraction over calling conventions.
|
||||
It deals with register and stack specific information related to passing and returning arguments.
|
||||
Here are example implementations for [arm](https://github.com/rtfeldman/roc/blob/trunk/compiler/gen_dev/src/generic64/aarch64.rs) and [x86_64](https://github.com/rtfeldman/roc/blob/trunk/compiler/gen_dev/src/generic64/x86_64.rs).
|
||||
|
||||
|
@ -74,7 +74,7 @@ This is the general procedure I follow with some helpful links:
|
|||
1. Uncomment the code to print out procedures [from here](https://github.com/rtfeldman/roc/blob/trunk/compiler/gen_dev/tests/helpers/eval.rs) and run the test.
|
||||
It should fail and print out the mono ir for this test case.
|
||||
Seeing the actual mono ir tends to be very helpful for complex additions.
|
||||
1. Generally it will fail in one of the match statments in the [Backend](https://github.com/rtfeldman/roc/blob/trunk/compiler/gen_dev/src/lib.rs) trait.
|
||||
1. Generally it will fail in one of the match statements in the [Backend](https://github.com/rtfeldman/roc/blob/trunk/compiler/gen_dev/src/lib.rs) trait.
|
||||
Add the correct pattern matching and likely new function for your new builtin.
|
||||
This will break the compile until you add the same function to places that implement the trait,
|
||||
like [Backend64Bit](https://github.com/rtfeldman/roc/blob/trunk/compiler/gen_dev/src/generic64/mod.rs).
|
||||
|
|
|
@ -553,7 +553,7 @@ impl<
|
|||
let jmp_offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678);
|
||||
ret_jumps.push((jmp_location, jmp_offset));
|
||||
|
||||
// Overwite the original jne with the correct offset.
|
||||
// Overwrite the original jne with the correct offset.
|
||||
let end_offset = self.buf.len();
|
||||
let jne_offset = end_offset - start_offset;
|
||||
ASM::jne_reg64_imm64_imm32(&mut tmp, cond_reg, *val, jne_offset as i32);
|
||||
|
@ -664,7 +664,7 @@ impl<
|
|||
},
|
||||
}));
|
||||
|
||||
// Overwite the original jump with the correct offset.
|
||||
// Overwrite the original jump with the correct offset.
|
||||
let mut tmp = bumpalo::vec![in self.env.arena];
|
||||
self.update_jmp_imm32_offset(
|
||||
&mut tmp,
|
||||
|
@ -1296,7 +1296,7 @@ impl<
|
|||
match val {
|
||||
Some(SymbolStorage::GeneralReg(reg)) => {
|
||||
let offset = self.claim_stack_size(8)?;
|
||||
// For base addresssing, use the negative offset - 8.
|
||||
// For base addressing, use the negative offset - 8.
|
||||
ASM::mov_base32_reg64(&mut self.buf, offset, reg);
|
||||
self.symbol_storage_map.insert(
|
||||
*sym,
|
||||
|
@ -1310,7 +1310,7 @@ impl<
|
|||
}
|
||||
Some(SymbolStorage::FloatReg(reg)) => {
|
||||
let offset = self.claim_stack_size(8)?;
|
||||
// For base addresssing, use the negative offset.
|
||||
// For base addressing, use the negative offset.
|
||||
ASM::mov_base32_freg64(&mut self.buf, offset, reg);
|
||||
self.symbol_storage_map.insert(
|
||||
*sym,
|
||||
|
|
|
@ -82,7 +82,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
|
|||
X86_64GeneralReg::RAX,
|
||||
X86_64GeneralReg::RCX,
|
||||
X86_64GeneralReg::RDX,
|
||||
// Don't use stack pionter: X86_64GeneralReg::RSP,
|
||||
// Don't use stack pointer: X86_64GeneralReg::RSP,
|
||||
X86_64GeneralReg::RSI,
|
||||
X86_64GeneralReg::RDI,
|
||||
X86_64GeneralReg::R8,
|
||||
|
@ -258,7 +258,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
|
|||
Layout::Struct(&[]) => {}
|
||||
x => {
|
||||
return Err(format!(
|
||||
"Loading args with layout {:?} not yet implementd",
|
||||
"Loading args with layout {:?} not yet implemented",
|
||||
x
|
||||
));
|
||||
}
|
||||
|
@ -464,7 +464,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
|
|||
// 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
|
||||
|
||||
// Don't use stack pionter: X86_64GeneralReg::RSP,
|
||||
// Don't use stack pointer: X86_64GeneralReg::RSP,
|
||||
// Don't use frame pointer: X86_64GeneralReg::RBP,
|
||||
|
||||
// Use callee saved regs last.
|
||||
|
@ -602,7 +602,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
|
|||
Layout::Struct(&[]) => {}
|
||||
x => {
|
||||
return Err(format!(
|
||||
"Loading args with layout {:?} not yet implementd",
|
||||
"Loading args with layout {:?} not yet implemented",
|
||||
x
|
||||
));
|
||||
}
|
||||
|
|
|
@ -68,7 +68,7 @@ where
|
|||
|
||||
/// 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.
|
||||
/// For example, this can store the frame pointer 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>;
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ pub fn build_module<'a>(
|
|||
architecture: TargetArch::X86_64,
|
||||
binary_format: TargetBF::Elf,
|
||||
..
|
||||
} => {
|
||||
} if cfg!(feature = "target-x86_64") => {
|
||||
let backend: Backend64Bit<
|
||||
x86_64::X86_64GeneralReg,
|
||||
x86_64::X86_64FloatReg,
|
||||
|
@ -46,7 +46,7 @@ pub fn build_module<'a>(
|
|||
architecture: TargetArch::X86_64,
|
||||
binary_format: TargetBF::Macho,
|
||||
..
|
||||
} => {
|
||||
} if cfg!(feature = "target-x86_64") => {
|
||||
let backend: Backend64Bit<
|
||||
x86_64::X86_64GeneralReg,
|
||||
x86_64::X86_64FloatReg,
|
||||
|
@ -68,7 +68,7 @@ pub fn build_module<'a>(
|
|||
architecture: TargetArch::Aarch64(_),
|
||||
binary_format: TargetBF::Elf,
|
||||
..
|
||||
} => {
|
||||
} if cfg!(feature = "target-aarch64") => {
|
||||
let backend: Backend64Bit<
|
||||
aarch64::AArch64GeneralReg,
|
||||
aarch64::AArch64FloatReg,
|
||||
|
|
|
@ -1,875 +0,0 @@
|
|||
#[macro_use]
|
||||
extern crate indoc;
|
||||
|
||||
#[macro_use]
|
||||
mod helpers;
|
||||
|
||||
#[cfg(all(test, any(target_os = "linux", target_os = "macos"), any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))]
|
||||
mod dev_num {
|
||||
#[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 f64_values() {
|
||||
assert_evals_to!("0.0", 0.0, f64);
|
||||
assert_evals_to!("-0.0", 0.0, f64);
|
||||
assert_evals_to!("1.0", 1.0, f64);
|
||||
assert_evals_to!("-1.0", -1.0, f64);
|
||||
assert_evals_to!("3.1415926535897932", 3.141_592_653_589_793, f64);
|
||||
assert_evals_to!(&format!("{:0.1}", f64::MIN), f64::MIN, f64);
|
||||
assert_evals_to!(&format!("{:0.1}", f64::MAX), f64::MAX, f64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_add_i64() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
1 + 2 + 3
|
||||
"#
|
||||
),
|
||||
6,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_add_f64() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
1.1 + 2.4 + 3
|
||||
"#
|
||||
),
|
||||
6.5,
|
||||
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 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
|
||||
|
||||
a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + 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 gen_int_eq() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
4 == 4
|
||||
"#
|
||||
),
|
||||
true,
|
||||
bool
|
||||
);
|
||||
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
3 == 4
|
||||
"#
|
||||
),
|
||||
false,
|
||||
bool
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_basic_fn() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
always42 : Num.Num (Num.Integer Num.Signed64) -> Num.Num (Num.Integer Num.Signed64)
|
||||
always42 = \_ -> 42
|
||||
|
||||
always42 5
|
||||
"#
|
||||
),
|
||||
42,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_wrap_add_nums() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
add2 = \num1, num2 -> num1 + num2
|
||||
|
||||
add2 4 5
|
||||
"#
|
||||
),
|
||||
9,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_wrap_add_nums_force_stack() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
add9 = \num1, num2, num3, num4, num5, num6, num7, num8, num9 -> num1 + num2 + num3 + num4 + num5 + num6 + num7 + num8 + num9
|
||||
|
||||
add9 1 2 3 4 5 6 7 8 9
|
||||
"#
|
||||
),
|
||||
45,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pow_int() {
|
||||
assert_evals_to!("Num.powInt 2 3", 8, i64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn acos() {
|
||||
assert_evals_to!("Num.acos 0.5", 1.0471975511965979, f64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn asin() {
|
||||
assert_evals_to!("Num.asin 0.5", 0.5235987755982989, f64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn atan() {
|
||||
assert_evals_to!("Num.atan 10", 1.4711276743037347, 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
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_fib_fn() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
fib = \n ->
|
||||
if n == 0 then
|
||||
0
|
||||
else if n == 1 then
|
||||
1
|
||||
else
|
||||
(fib (n - 1)) + (fib (n - 2))
|
||||
|
||||
fib 10
|
||||
"#
|
||||
),
|
||||
55,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_fast_fib_fn() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
fib = \n, a, b ->
|
||||
if n == 0 then
|
||||
a
|
||||
else
|
||||
fib (n - 1) b (a + b)
|
||||
fib 10 0 1
|
||||
"#
|
||||
),
|
||||
55,
|
||||
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 f64_round() {
|
||||
assert_evals_to!("Num.round 3.6", 4, i64);
|
||||
assert_evals_to!("Num.round 3.4", 3, i64);
|
||||
assert_evals_to!("Num.round 2.5", 3, i64);
|
||||
assert_evals_to!("Num.round -2.3", -2, i64);
|
||||
assert_evals_to!("Num.round -2.5", -3, 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 gen_float_eq() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// 1.0 == 1.0
|
||||
// "#
|
||||
// ),
|
||||
// true,
|
||||
// bool
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[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_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_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 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]
|
||||
// // #[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 max_i128() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// Num.maxI128
|
||||
// "#
|
||||
// ),
|
||||
// i128::MAX,
|
||||
// i128
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[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
|
||||
// );
|
||||
// }
|
||||
}
|
|
@ -1,937 +0,0 @@
|
|||
#[macro_use]
|
||||
extern crate indoc;
|
||||
|
||||
#[macro_use]
|
||||
mod helpers;
|
||||
|
||||
#[cfg(all(test, target_os = "linux", any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))]
|
||||
mod dev_records {
|
||||
#[test]
|
||||
fn basic_record() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
{ y: 17, x: 15, z: 19 }.x
|
||||
"#
|
||||
),
|
||||
15,
|
||||
i64
|
||||
);
|
||||
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
{ x: 15, y: 17, z: 19 }.y
|
||||
"#
|
||||
),
|
||||
17,
|
||||
i64
|
||||
);
|
||||
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
{ x: 15, y: 17, z: 19 }.z
|
||||
"#
|
||||
),
|
||||
19,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_record() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
{ x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.x
|
||||
"#
|
||||
),
|
||||
15,
|
||||
i64
|
||||
);
|
||||
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
{ x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.y.a
|
||||
"#
|
||||
),
|
||||
12,
|
||||
i64
|
||||
);
|
||||
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
{ x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.y.b
|
||||
"#
|
||||
),
|
||||
15,
|
||||
i64
|
||||
);
|
||||
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
{ x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.y.c
|
||||
"#
|
||||
),
|
||||
2,
|
||||
i64
|
||||
);
|
||||
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
{ x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.z
|
||||
"#
|
||||
),
|
||||
19,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn f64_record() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
rec = { y: 17.2, x: 15.1, z: 19.3 }
|
||||
|
||||
rec.x
|
||||
"#
|
||||
),
|
||||
15.1,
|
||||
f64
|
||||
);
|
||||
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
rec = { y: 17.2, x: 15.1, z: 19.3 }
|
||||
|
||||
rec.y
|
||||
"#
|
||||
),
|
||||
17.2,
|
||||
f64
|
||||
);
|
||||
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
rec = { y: 17.2, x: 15.1, z: 19.3 }
|
||||
|
||||
rec.z
|
||||
"#
|
||||
),
|
||||
19.3,
|
||||
f64
|
||||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn fn_record() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// getRec = \x -> { y: 17, x, z: 19 }
|
||||
|
||||
// (getRec 15).x
|
||||
// "#
|
||||
// ),
|
||||
// 15,
|
||||
// i64
|
||||
// );
|
||||
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// rec = { x: 15, y: 17, z: 19 }
|
||||
|
||||
// rec.y
|
||||
// "#
|
||||
// ),
|
||||
// 17,
|
||||
// i64
|
||||
// );
|
||||
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// rec = { x: 15, y: 17, z: 19 }
|
||||
|
||||
// rec.z
|
||||
// "#
|
||||
// ),
|
||||
// 19,
|
||||
// i64
|
||||
// );
|
||||
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// rec = { x: 15, y: 17, z: 19 }
|
||||
|
||||
// rec.z + rec.x
|
||||
// "#
|
||||
// ),
|
||||
// 34,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn def_record() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
rec = { y: 17, x: 15, z: 19 }
|
||||
|
||||
rec.x
|
||||
"#
|
||||
),
|
||||
15,
|
||||
i64
|
||||
);
|
||||
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
rec = { x: 15, y: 17, z: 19 }
|
||||
|
||||
rec.y
|
||||
"#
|
||||
),
|
||||
17,
|
||||
i64
|
||||
);
|
||||
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
rec = { x: 15, y: 17, z: 19 }
|
||||
|
||||
rec.z
|
||||
"#
|
||||
),
|
||||
19,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn when_on_record() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when { x: 0x2 } is
|
||||
{ x } -> x + 3
|
||||
"#
|
||||
),
|
||||
5,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn when_record_with_guard_pattern() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when { x: 0x2, y: 3.14 } is
|
||||
{ x: var } -> var + 3
|
||||
"#
|
||||
),
|
||||
5,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn let_with_record_pattern() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
{ x } = { x: 0x2, y: 3.14 }
|
||||
|
||||
x
|
||||
"#
|
||||
),
|
||||
2,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_guard_pattern() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when { x: 0x2, y: 3.14 } is
|
||||
{ x: 0x4 } -> 5
|
||||
{ x } -> x + 3
|
||||
"#
|
||||
),
|
||||
5,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn twice_record_access() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
x = {a: 0x2, b: 0x3 }
|
||||
|
||||
x.a + x.b
|
||||
"#
|
||||
),
|
||||
5,
|
||||
i64
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn empty_record() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
v = {}
|
||||
|
||||
v
|
||||
"#
|
||||
),
|
||||
(),
|
||||
()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn i64_record1_literal() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
{ x: 3 }
|
||||
"#
|
||||
),
|
||||
3,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn i64_record2_literal() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { x: 3, y: 5 }
|
||||
// "#
|
||||
// ),
|
||||
// (3, 5),
|
||||
// (i64, i64)
|
||||
// );
|
||||
// }
|
||||
|
||||
// // #[test]
|
||||
// // fn i64_record3_literal() {
|
||||
// // assert_evals_to!(
|
||||
// // indoc!(
|
||||
// // r#"
|
||||
// // { x: 3, y: 5, z: 17 }
|
||||
// // "#
|
||||
// // ),
|
||||
// // (3, 5, 17),
|
||||
// // (i64, i64, i64)
|
||||
// // );
|
||||
// // }
|
||||
|
||||
// #[test]
|
||||
// fn f64_record2_literal() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { x: 3.1, y: 5.1 }
|
||||
// "#
|
||||
// ),
|
||||
// (3.1, 5.1),
|
||||
// (f64, f64)
|
||||
// );
|
||||
// }
|
||||
|
||||
// // #[test]
|
||||
// // fn f64_record3_literal() {
|
||||
// // assert_evals_to!(
|
||||
// // indoc!(
|
||||
// // r#"
|
||||
// // { x: 3.1, y: 5.1, z: 17.1 }
|
||||
// // "#
|
||||
// // ),
|
||||
// // (3.1, 5.1, 17.1),
|
||||
// // (f64, f64, f64)
|
||||
// // );
|
||||
// // }
|
||||
|
||||
// // #[test]
|
||||
// // fn bool_record4_literal() {
|
||||
// // assert_evals_to!(
|
||||
// // indoc!(
|
||||
// // r#"
|
||||
// // record : { a : Bool, b : Bool, c : Bool, d : Bool }
|
||||
// // record = { a: True, b: True, c : True, d : Bool }
|
||||
|
||||
// // record
|
||||
// // "#
|
||||
// // ),
|
||||
// // (true, false, false, true),
|
||||
// // (bool, bool, bool, bool)
|
||||
// // );
|
||||
// // }
|
||||
|
||||
// #[test]
|
||||
// fn i64_record1_literal() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { a: 3 }
|
||||
// "#
|
||||
// ),
|
||||
// 3,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// // #[test]
|
||||
// // fn i64_record9_literal() {
|
||||
// // assert_evals_to!(
|
||||
// // indoc!(
|
||||
// // r#"
|
||||
// // { a: 3, b: 5, c: 17, d: 1, e: 9, f: 12, g: 13, h: 14, i: 15 }
|
||||
// // "#
|
||||
// // ),
|
||||
// // (3, 5, 17, 1, 9, 12, 13, 14, 15),
|
||||
// // (i64, i64, i64, i64, i64, i64, i64, i64, i64)
|
||||
// // );
|
||||
// // }
|
||||
|
||||
// // #[test]
|
||||
// // fn f64_record3_literal() {
|
||||
// // assert_evals_to!(
|
||||
// // indoc!(
|
||||
// // r#"
|
||||
// // { x: 3.1, y: 5.1, z: 17.1 }
|
||||
// // "#
|
||||
// // ),
|
||||
// // (3.1, 5.1, 17.1),
|
||||
// // (f64, f64, f64)
|
||||
// // );
|
||||
// // }
|
||||
|
||||
// #[test]
|
||||
// fn bool_literal() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// x : Bool
|
||||
// x = True
|
||||
|
||||
// x
|
||||
// "#
|
||||
// ),
|
||||
// true,
|
||||
// bool
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn optional_field_when_use_default() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// app "test" provides [ main ] to "./platform"
|
||||
|
||||
// f = \r ->
|
||||
// when r is
|
||||
// { x: Blue, y ? 3 } -> y
|
||||
// { x: Red, y ? 5 } -> y
|
||||
|
||||
// main =
|
||||
// a = f { x: Blue, y: 7 }
|
||||
// b = f { x: Blue }
|
||||
// c = f { x: Red, y: 11 }
|
||||
// d = f { x: Red }
|
||||
|
||||
// a * b * c * d
|
||||
// "#
|
||||
// ),
|
||||
// 3 * 5 * 7 * 11,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn optional_field_when_use_default_nested() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// f = \r ->
|
||||
// when r is
|
||||
// { x: Blue, y ? 3 } -> y
|
||||
// { x: Red, y ? 5 } -> y
|
||||
|
||||
// a = f { x: Blue, y: 7 }
|
||||
// b = f { x: Blue }
|
||||
// c = f { x: Red, y: 11 }
|
||||
// d = f { x: Red }
|
||||
|
||||
// a * b * c * d
|
||||
// "#
|
||||
// ),
|
||||
// 3 * 5 * 7 * 11,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn optional_field_when_no_use_default() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// app "test" provides [ main ] to "./platform"
|
||||
|
||||
// f = \r ->
|
||||
// { x ? 10, y } = r
|
||||
// x + y
|
||||
|
||||
// main =
|
||||
// f { x: 4, y: 9 }
|
||||
// "#
|
||||
// ),
|
||||
// 13,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn optional_field_when_no_use_default_nested() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// f = \r ->
|
||||
// { x ? 10, y } = r
|
||||
// x + y
|
||||
|
||||
// f { x: 4, y: 9 }
|
||||
// "#
|
||||
// ),
|
||||
// 13,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn optional_field_let_use_default() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// app "test" provides [ main ] to "./platform"
|
||||
|
||||
// f = \r ->
|
||||
// { x ? 10, y } = r
|
||||
// x + y
|
||||
|
||||
// main =
|
||||
// f { y: 9 }
|
||||
// "#
|
||||
// ),
|
||||
// 19,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn optional_field_let_no_use_default() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// app "test" provides [ main ] to "./platform"
|
||||
|
||||
// f = \r ->
|
||||
// { x ? 10, y } = r
|
||||
// x + y
|
||||
|
||||
// main =
|
||||
// f { x: 4, y: 9 }
|
||||
// "#
|
||||
// ),
|
||||
// 13,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn optional_field_let_no_use_default_nested() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// f = \r ->
|
||||
// { x ? 10, y } = r
|
||||
// x + y
|
||||
|
||||
// f { x: 4, y: 9 }
|
||||
// "#
|
||||
// ),
|
||||
// 13,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn optional_field_function_use_default() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// f = \{ x ? 10, y } -> x + y
|
||||
|
||||
// f { y: 9 }
|
||||
// "#
|
||||
// ),
|
||||
// 19,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// #[ignore]
|
||||
// fn optional_field_function_no_use_default() {
|
||||
// // blocked on https://github.com/rtfeldman/roc/issues/786
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// app "test" provides [ main ] to "./platform"
|
||||
|
||||
// f = \{ x ? 10, y } -> x + y
|
||||
|
||||
// main =
|
||||
// f { x: 4, y: 9 }
|
||||
// "#
|
||||
// ),
|
||||
// 13,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// #[ignore]
|
||||
// fn optional_field_function_no_use_default_nested() {
|
||||
// // blocked on https://github.com/rtfeldman/roc/issues/786
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// f = \{ x ? 10, y } -> x + y
|
||||
|
||||
// f { x: 4, y: 9 }
|
||||
// "#
|
||||
// ),
|
||||
// 13,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn optional_field_singleton_record() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when { x : 4 } is
|
||||
// { x ? 3 } -> x
|
||||
// "#
|
||||
// ),
|
||||
// 4,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn optional_field_empty_record() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when { } is
|
||||
// { x ? 3 } -> x
|
||||
// "#
|
||||
// ),
|
||||
// 3,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn return_record_2() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { x: 3, y: 5 }
|
||||
// "#
|
||||
// ),
|
||||
// [3, 5],
|
||||
// [i64; 2]
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn return_record_3() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { x: 3, y: 5, z: 4 }
|
||||
// "#
|
||||
// ),
|
||||
// (3, 5, 4),
|
||||
// (i64, i64, i64)
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn return_record_4() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { a: 3, b: 5, c: 4, d: 2 }
|
||||
// "#
|
||||
// ),
|
||||
// [3, 5, 4, 2],
|
||||
// [i64; 4]
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn return_record_5() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { a: 3, b: 5, c: 4, d: 2, e: 1 }
|
||||
// "#
|
||||
// ),
|
||||
// [3, 5, 4, 2, 1],
|
||||
// [i64; 5]
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn return_record_6() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { a: 3, b: 5, c: 4, d: 2, e: 1, f: 7 }
|
||||
// "#
|
||||
// ),
|
||||
// [3, 5, 4, 2, 1, 7],
|
||||
// [i64; 6]
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn return_record_7() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { a: 3, b: 5, c: 4, d: 2, e: 1, f: 7, g: 8 }
|
||||
// "#
|
||||
// ),
|
||||
// [3, 5, 4, 2, 1, 7, 8],
|
||||
// [i64; 7]
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn return_record_float_int() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { a: 3.14, b: 0x1 }
|
||||
// "#
|
||||
// ),
|
||||
// (3.14, 0x1),
|
||||
// (f64, i64)
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn return_record_int_float() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { a: 0x1, b: 3.14 }
|
||||
// "#
|
||||
// ),
|
||||
// (0x1, 3.14),
|
||||
// (i64, f64)
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn return_record_float_float() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { a: 6.28, b: 3.14 }
|
||||
// "#
|
||||
// ),
|
||||
// (6.28, 3.14),
|
||||
// (f64, f64)
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn return_record_float_float_float() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { a: 6.28, b: 3.14, c: 0.1 }
|
||||
// "#
|
||||
// ),
|
||||
// (6.28, 3.14, 0.1),
|
||||
// (f64, f64, f64)
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn return_nested_record() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { flag: 0x0, payload: { a: 6.28, b: 3.14, c: 0.1 } }
|
||||
// "#
|
||||
// ),
|
||||
// (0x0, (6.28, 3.14, 0.1)),
|
||||
// (i64, (f64, f64, f64))
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn accessor() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// .foo { foo: 4 } + .foo { bar: 6.28, foo: 3 }
|
||||
// "#
|
||||
// ),
|
||||
// 7,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn accessor_single_element_record() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// .foo { foo: 4 }
|
||||
// "#
|
||||
// ),
|
||||
// 4,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn update_record() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// rec = { foo: 42, bar: 6 }
|
||||
|
||||
// { rec & foo: rec.foo + 1 }
|
||||
// "#
|
||||
// ),
|
||||
// (6, 43),
|
||||
// (i64, i64)
|
||||
// );
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn update_single_element_record() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
rec = { foo: 42}
|
||||
|
||||
{ rec & foo: rec.foo + 1 }
|
||||
"#
|
||||
),
|
||||
43,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn booleans_in_record() {
|
||||
// assert_evals_to!(
|
||||
// indoc!("{ x: 1 == 1, y: 1 == 1 }"),
|
||||
// (true, true),
|
||||
// (bool, bool)
|
||||
// );
|
||||
// assert_evals_to!(
|
||||
// indoc!("{ x: 1 != 1, y: 1 == 1 }"),
|
||||
// (false, true),
|
||||
// (bool, bool)
|
||||
// );
|
||||
// assert_evals_to!(
|
||||
// indoc!("{ x: 1 == 1, y: 1 != 1 }"),
|
||||
// (true, false),
|
||||
// (bool, bool)
|
||||
// );
|
||||
// assert_evals_to!(
|
||||
// indoc!("{ x: 1 != 1, y: 1 != 1 }"),
|
||||
// (false, false),
|
||||
// (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
|
||||
// );
|
||||
// }
|
||||
}
|
|
@ -1,954 +0,0 @@
|
|||
// #[macro_use]
|
||||
// extern crate indoc;
|
||||
|
||||
#[macro_use]
|
||||
mod helpers;
|
||||
|
||||
#[cfg(all(test, any(target_os = "linux", target_os = "macos"), any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))]
|
||||
mod dev_str {
|
||||
// use roc_std::{RocList, RocStr};
|
||||
// #[test]
|
||||
// fn str_split_bigger_delimiter_small_str() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// List.len (Str.split "hello" "JJJJ there")
|
||||
// "#
|
||||
// ),
|
||||
// 1,
|
||||
// i64
|
||||
// );
|
||||
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when List.first (Str.split "JJJ" "JJJJ there") is
|
||||
// Ok str ->
|
||||
// Str.countGraphemes str
|
||||
|
||||
// _ ->
|
||||
// -1
|
||||
|
||||
// "#
|
||||
// ),
|
||||
// 3,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_split_str_concat_repeated() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when List.first (Str.split "JJJJJ" "JJJJ there") is
|
||||
// Ok str ->
|
||||
// str
|
||||
// |> Str.concat str
|
||||
// |> Str.concat str
|
||||
// |> Str.concat str
|
||||
// |> Str.concat str
|
||||
|
||||
// _ ->
|
||||
// "Not Str!"
|
||||
|
||||
// "#
|
||||
// ),
|
||||
// RocStr::from_slice(b"JJJJJJJJJJJJJJJJJJJJJJJJJ"),
|
||||
// RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_split_small_str_bigger_delimiter() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when
|
||||
// List.first
|
||||
// (Str.split "JJJ" "0123456789abcdefghi")
|
||||
// is
|
||||
// Ok str -> str
|
||||
// _ -> ""
|
||||
// "#
|
||||
// ),
|
||||
// RocStr::from_slice(b"JJJ"),
|
||||
// RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_split_big_str_small_delimiter() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// Str.split "01234567789abcdefghi?01234567789abcdefghi" "?"
|
||||
// "#
|
||||
// ),
|
||||
// RocList::from_slice(&[
|
||||
// RocStr::from_slice(b"01234567789abcdefghi"),
|
||||
// RocStr::from_slice(b"01234567789abcdefghi")
|
||||
// ]),
|
||||
// RocList<RocStr>
|
||||
// );
|
||||
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// Str.split "01234567789abcdefghi 3ch 01234567789abcdefghi" "3ch"
|
||||
// "#
|
||||
// ),
|
||||
// RocList::from_slice(&[
|
||||
// RocStr::from_slice(b"01234567789abcdefghi "),
|
||||
// RocStr::from_slice(b" 01234567789abcdefghi")
|
||||
// ]),
|
||||
// RocList<RocStr>
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_split_small_str_small_delimiter() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// Str.split "J!J!J" "!"
|
||||
// "#
|
||||
// ),
|
||||
// RocList::from_slice(&[
|
||||
// RocStr::from_slice(b"J"),
|
||||
// RocStr::from_slice(b"J"),
|
||||
// RocStr::from_slice(b"J")
|
||||
// ]),
|
||||
// RocList<RocStr>
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_split_bigger_delimiter_big_strs() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// Str.split
|
||||
// "string to split is shorter"
|
||||
// "than the delimiter which happens to be very very long"
|
||||
// "#
|
||||
// ),
|
||||
// RocList::from_slice(&[RocStr::from_slice(b"string to split is shorter")]),
|
||||
// RocList<RocStr>
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_split_empty_strs() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// Str.split "" ""
|
||||
// "#
|
||||
// ),
|
||||
// RocList::from_slice(&[RocStr::from_slice(b"")]),
|
||||
// RocList<RocStr>
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_split_minimal_example() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// Str.split "a," ","
|
||||
// "#
|
||||
// ),
|
||||
// RocList::from_slice(&[RocStr::from_slice(b"a"), RocStr::from_slice(b"")]),
|
||||
// RocList<RocStr>
|
||||
// )
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_split_small_str_big_delimiter() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// Str.split
|
||||
// "1---- ---- ---- ---- ----2---- ---- ---- ---- ----"
|
||||
// "---- ---- ---- ---- ----"
|
||||
// |> List.len
|
||||
// "#
|
||||
// ),
|
||||
// 3,
|
||||
// i64
|
||||
// );
|
||||
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// Str.split
|
||||
// "1---- ---- ---- ---- ----2---- ---- ---- ---- ----"
|
||||
// "---- ---- ---- ---- ----"
|
||||
// "#
|
||||
// ),
|
||||
// RocList::from_slice(&[
|
||||
// RocStr::from_slice(b"1"),
|
||||
// RocStr::from_slice(b"2"),
|
||||
// RocStr::from_slice(b"")
|
||||
// ]),
|
||||
// RocList<RocStr>
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_split_small_str_20_char_delimiter() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// Str.split
|
||||
// "3|-- -- -- -- -- -- |4|-- -- -- -- -- -- |"
|
||||
// "|-- -- -- -- -- -- |"
|
||||
// "#
|
||||
// ),
|
||||
// RocList::from_slice(&[
|
||||
// RocStr::from_slice(b"3"),
|
||||
// RocStr::from_slice(b"4"),
|
||||
// RocStr::from_slice(b"")
|
||||
// ]),
|
||||
// RocList<RocStr>
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_concat_big_to_big() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// Str.concat
|
||||
// "First string that is fairly long. Longer strings make for different errors. "
|
||||
// "Second string that is also fairly long. Two long strings test things that might not appear with short strings."
|
||||
// "#
|
||||
// ),
|
||||
// RocStr::from_slice(b"First string that is fairly long. Longer strings make for different errors. Second string that is also fairly long. Two long strings test things that might not appear with short strings."),
|
||||
// RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn small_str_literal() {
|
||||
assert_evals_to!(
|
||||
"\"JJJJJJJJJJJJJJJ\"",
|
||||
[
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0b1000_1111
|
||||
],
|
||||
[u8; 16]
|
||||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn small_str_zeroed_literal() {
|
||||
// // Verifies that we zero out unused bytes in the string.
|
||||
// // This is important so that string equality tests don't randomly
|
||||
// // fail due to unused memory being there!
|
||||
// assert_evals_to!(
|
||||
// "\"J\"",
|
||||
// [
|
||||
// 0x4a,
|
||||
// 0x00,
|
||||
// 0x00,
|
||||
// 0x00,
|
||||
// 0x00,
|
||||
// 0x00,
|
||||
// 0x00,
|
||||
// 0x00,
|
||||
// 0x00,
|
||||
// 0x00,
|
||||
// 0x00,
|
||||
// 0x00,
|
||||
// 0x00,
|
||||
// 0x00,
|
||||
// 0x00,
|
||||
// 0b1000_0001
|
||||
// ],
|
||||
// [u8; 16]
|
||||
// );
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn small_str_concat_empty_first_arg() {
|
||||
assert_evals_to!(
|
||||
r#"Str.concat "" "JJJJJJJJJJJJJJJ""#,
|
||||
[
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0b1000_1111
|
||||
],
|
||||
[u8; 16]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn small_str_concat_empty_second_arg() {
|
||||
assert_evals_to!(
|
||||
r#"Str.concat "JJJJJJJJJJJJJJJ" """#,
|
||||
[
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0b1000_1111
|
||||
],
|
||||
[u8; 16]
|
||||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn small_str_concat_small_to_big() {
|
||||
// assert_evals_to!(
|
||||
// r#"Str.concat "abc" " this is longer than 15 chars""#,
|
||||
// RocStr::from_slice(b"abc this is longer than 15 chars"),
|
||||
// RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn small_str_concat_small_to_small_staying_small() {
|
||||
assert_evals_to!(
|
||||
r#"Str.concat "J" "JJJJJJJJJJJJJJ""#,
|
||||
[
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0b1000_1111
|
||||
],
|
||||
[u8; 16]
|
||||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn small_str_concat_small_to_small_overflow_to_big() {
|
||||
// assert_evals_to!(
|
||||
// r#"Str.concat "abcdefghijklm" "nopqrstuvwxyz""#,
|
||||
// RocStr::from_slice(b"abcdefghijklmnopqrstuvwxyz"),
|
||||
// RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_concat_empty() {
|
||||
// assert_evals_to!(r#"Str.concat "" """#, RocStr::default(), RocStr);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn small_str_is_empty() {
|
||||
// assert_evals_to!(r#"Str.isEmpty "abc""#, false, bool);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn big_str_is_empty() {
|
||||
// assert_evals_to!(
|
||||
// r#"Str.isEmpty "this is more than 15 chars long""#,
|
||||
// false,
|
||||
// bool
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn empty_str_is_empty() {
|
||||
// assert_evals_to!(r#"Str.isEmpty """#, true, bool);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_starts_with() {
|
||||
// assert_evals_to!(r#"Str.startsWith "hello world" "hell""#, true, bool);
|
||||
// assert_evals_to!(r#"Str.startsWith "hello world" """#, true, bool);
|
||||
// assert_evals_to!(r#"Str.startsWith "nope" "hello world""#, false, bool);
|
||||
// assert_evals_to!(r#"Str.startsWith "hell" "hello world""#, false, bool);
|
||||
// assert_evals_to!(r#"Str.startsWith "" "hello world""#, false, bool);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_starts_with_code_point() {
|
||||
// assert_evals_to!(
|
||||
// &format!(r#"Str.startsWithCodePt "foobar" {}"#, 'f' as u32),
|
||||
// true,
|
||||
// bool
|
||||
// );
|
||||
// assert_evals_to!(
|
||||
// &format!(r#"Str.startsWithCodePt "zoobar" {}"#, 'f' as u32),
|
||||
// false,
|
||||
// bool
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_ends_with() {
|
||||
// assert_evals_to!(r#"Str.endsWith "hello world" "world""#, true, bool);
|
||||
// assert_evals_to!(r#"Str.endsWith "nope" "hello world""#, false, bool);
|
||||
// assert_evals_to!(r#"Str.endsWith "" "hello world""#, false, bool);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_count_graphemes_small_str() {
|
||||
// assert_evals_to!(r#"Str.countGraphemes "å🤔""#, 2, usize);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_count_graphemes_three_js() {
|
||||
// assert_evals_to!(r#"Str.countGraphemes "JJJ""#, 3, usize);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_count_graphemes_big_str() {
|
||||
// assert_evals_to!(
|
||||
// r#"Str.countGraphemes "6🤔å🤔e¥🤔çppkd🙃1jdal🦯asdfa∆ltråø˚waia8918.,🏅jjc""#,
|
||||
// 45,
|
||||
// usize
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_starts_with_same_big_str() {
|
||||
// assert_evals_to!(
|
||||
// r#"Str.startsWith "123456789123456789" "123456789123456789""#,
|
||||
// true,
|
||||
// bool
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_starts_with_different_big_str() {
|
||||
// assert_evals_to!(
|
||||
// r#"Str.startsWith "12345678912345678910" "123456789123456789""#,
|
||||
// true,
|
||||
// bool
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_starts_with_same_small_str() {
|
||||
// assert_evals_to!(r#"Str.startsWith "1234" "1234""#, true, bool);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_starts_with_different_small_str() {
|
||||
// assert_evals_to!(r#"Str.startsWith "1234" "12""#, true, bool);
|
||||
// }
|
||||
// #[test]
|
||||
// fn str_starts_with_false_small_str() {
|
||||
// assert_evals_to!(r#"Str.startsWith "1234" "23""#, false, bool);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_from_int() {
|
||||
// assert_evals_to!(
|
||||
// r#"Str.fromInt 1234"#,
|
||||
// roc_std::RocStr::from_slice("1234".as_bytes()),
|
||||
// roc_std::RocStr
|
||||
// );
|
||||
// assert_evals_to!(
|
||||
// r#"Str.fromInt 0"#,
|
||||
// roc_std::RocStr::from_slice("0".as_bytes()),
|
||||
// roc_std::RocStr
|
||||
// );
|
||||
// assert_evals_to!(
|
||||
// r#"Str.fromInt -1"#,
|
||||
// roc_std::RocStr::from_slice("-1".as_bytes()),
|
||||
// roc_std::RocStr
|
||||
// );
|
||||
|
||||
// let max = format!("{}", i64::MAX);
|
||||
// assert_evals_to!(
|
||||
// r#"Str.fromInt Num.maxInt"#,
|
||||
// RocStr::from_slice(max.as_bytes()),
|
||||
// RocStr
|
||||
// );
|
||||
|
||||
// let min = format!("{}", i64::MIN);
|
||||
// assert_evals_to!(
|
||||
// r#"Str.fromInt Num.minInt"#,
|
||||
// RocStr::from_slice(min.as_bytes()),
|
||||
// RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_from_utf8_pass_single_ascii() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when Str.fromUtf8 [ 97 ] is
|
||||
// Ok val -> val
|
||||
// Err _ -> ""
|
||||
// "#
|
||||
// ),
|
||||
// roc_std::RocStr::from_slice("a".as_bytes()),
|
||||
// roc_std::RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_from_utf8_pass_many_ascii() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when Str.fromUtf8 [ 97, 98, 99, 0x7E ] is
|
||||
// Ok val -> val
|
||||
// Err _ -> ""
|
||||
// "#
|
||||
// ),
|
||||
// roc_std::RocStr::from_slice("abc~".as_bytes()),
|
||||
// roc_std::RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_from_utf8_pass_single_unicode() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when Str.fromUtf8 [ 0xE2, 0x88, 0x86 ] is
|
||||
// Ok val -> val
|
||||
// Err _ -> ""
|
||||
// "#
|
||||
// ),
|
||||
// roc_std::RocStr::from_slice("∆".as_bytes()),
|
||||
// roc_std::RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_from_utf8_pass_many_unicode() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when Str.fromUtf8 [ 0xE2, 0x88, 0x86, 0xC5, 0x93, 0xC2, 0xAC ] is
|
||||
// Ok val -> val
|
||||
// Err _ -> ""
|
||||
// "#
|
||||
// ),
|
||||
// roc_std::RocStr::from_slice("∆œ¬".as_bytes()),
|
||||
// roc_std::RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_from_utf8_pass_single_grapheme() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when Str.fromUtf8 [ 0xF0, 0x9F, 0x92, 0x96 ] is
|
||||
// Ok val -> val
|
||||
// Err _ -> ""
|
||||
// "#
|
||||
// ),
|
||||
// roc_std::RocStr::from_slice("💖".as_bytes()),
|
||||
// roc_std::RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_from_utf8_pass_many_grapheme() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when Str.fromUtf8 [ 0xF0, 0x9F, 0x92, 0x96, 0xF0, 0x9F, 0xA4, 0xA0, 0xF0, 0x9F, 0x9A, 0x80 ] is
|
||||
// Ok val -> val
|
||||
// Err _ -> ""
|
||||
// "#
|
||||
// ),
|
||||
// roc_std::RocStr::from_slice("💖🤠🚀".as_bytes()),
|
||||
// roc_std::RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_from_utf8_pass_all() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when Str.fromUtf8 [ 0xF0, 0x9F, 0x92, 0x96, 98, 0xE2, 0x88, 0x86 ] is
|
||||
// Ok val -> val
|
||||
// Err _ -> ""
|
||||
// "#
|
||||
// ),
|
||||
// roc_std::RocStr::from_slice("💖b∆".as_bytes()),
|
||||
// roc_std::RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_from_utf8_fail_invalid_start_byte() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when Str.fromUtf8 [ 97, 98, 0x80, 99 ] is
|
||||
// Err (BadUtf8 InvalidStartByte byteIndex) ->
|
||||
// if byteIndex == 2 then
|
||||
// "a"
|
||||
// else
|
||||
// "b"
|
||||
// _ -> ""
|
||||
// "#
|
||||
// ),
|
||||
// roc_std::RocStr::from_slice("a".as_bytes()),
|
||||
// roc_std::RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_from_utf8_fail_unexpected_end_of_sequence() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when Str.fromUtf8 [ 97, 98, 99, 0xC2 ] is
|
||||
// Err (BadUtf8 UnexpectedEndOfSequence byteIndex) ->
|
||||
// if byteIndex == 3 then
|
||||
// "a"
|
||||
// else
|
||||
// "b"
|
||||
// _ -> ""
|
||||
// "#
|
||||
// ),
|
||||
// roc_std::RocStr::from_slice("a".as_bytes()),
|
||||
// roc_std::RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_from_utf8_fail_expected_continuation() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when Str.fromUtf8 [ 97, 98, 99, 0xC2, 0x00 ] is
|
||||
// Err (BadUtf8 ExpectedContinuation byteIndex) ->
|
||||
// if byteIndex == 3 then
|
||||
// "a"
|
||||
// else
|
||||
// "b"
|
||||
// _ -> ""
|
||||
// "#
|
||||
// ),
|
||||
// roc_std::RocStr::from_slice("a".as_bytes()),
|
||||
// roc_std::RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_from_utf8_fail_overlong_encoding() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when Str.fromUtf8 [ 97, 0xF0, 0x80, 0x80, 0x80 ] is
|
||||
// Err (BadUtf8 OverlongEncoding byteIndex) ->
|
||||
// if byteIndex == 1 then
|
||||
// "a"
|
||||
// else
|
||||
// "b"
|
||||
// _ -> ""
|
||||
// "#
|
||||
// ),
|
||||
// roc_std::RocStr::from_slice("a".as_bytes()),
|
||||
// roc_std::RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_from_utf8_fail_codepoint_too_large() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when Str.fromUtf8 [ 97, 0xF4, 0x90, 0x80, 0x80 ] is
|
||||
// Err (BadUtf8 CodepointTooLarge byteIndex) ->
|
||||
// if byteIndex == 1 then
|
||||
// "a"
|
||||
// else
|
||||
// "b"
|
||||
// _ -> ""
|
||||
// "#
|
||||
// ),
|
||||
// roc_std::RocStr::from_slice("a".as_bytes()),
|
||||
// roc_std::RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_from_utf8_fail_surrogate_half() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when Str.fromUtf8 [ 97, 98, 0xED, 0xA0, 0x80 ] is
|
||||
// Err (BadUtf8 EncodesSurrogateHalf byteIndex) ->
|
||||
// if byteIndex == 2 then
|
||||
// "a"
|
||||
// else
|
||||
// "b"
|
||||
// _ -> ""
|
||||
// "#
|
||||
// ),
|
||||
// roc_std::RocStr::from_slice("a".as_bytes()),
|
||||
// roc_std::RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_equality() {
|
||||
// assert_evals_to!(r#""a" == "a""#, true, bool);
|
||||
// assert_evals_to!(
|
||||
// r#""loremipsumdolarsitamet" == "loremipsumdolarsitamet""#,
|
||||
// true,
|
||||
// bool
|
||||
// );
|
||||
// assert_evals_to!(r#""a" != "b""#, true, bool);
|
||||
// assert_evals_to!(r#""a" == "b""#, false, bool);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_clone() {
|
||||
// use roc_std::RocStr;
|
||||
// let long = RocStr::from_slice("loremipsumdolarsitamet".as_bytes());
|
||||
// let short = RocStr::from_slice("x".as_bytes());
|
||||
// let empty = RocStr::from_slice("".as_bytes());
|
||||
|
||||
// debug_assert_eq!(long.clone(), long);
|
||||
// debug_assert_eq!(short.clone(), short);
|
||||
// debug_assert_eq!(empty.clone(), empty);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn nested_recursive_literal() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// Expr : [ Add Expr Expr, Val I64, Var I64 ]
|
||||
|
||||
// expr : Expr
|
||||
// expr = Add (Add (Val 3) (Val 1)) (Add (Val 1) (Var 1))
|
||||
|
||||
// printExpr : Expr -> Str
|
||||
// printExpr = \e ->
|
||||
// when e is
|
||||
// Add a b ->
|
||||
// "Add ("
|
||||
// |> Str.concat (printExpr a)
|
||||
// |> Str.concat ") ("
|
||||
// |> Str.concat (printExpr b)
|
||||
// |> Str.concat ")"
|
||||
// Val v -> "Val " |> Str.concat (Str.fromInt v)
|
||||
// Var v -> "Var " |> Str.concat (Str.fromInt v)
|
||||
|
||||
// printExpr expr
|
||||
// "#
|
||||
// ),
|
||||
// RocStr::from_slice(b"Add (Add (Val 3) (Val 1)) (Add (Val 1) (Var 1))"),
|
||||
// RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_join_comma_small() {
|
||||
// assert_evals_to!(
|
||||
// r#"Str.joinWith ["1", "2"] ", " "#,
|
||||
// RocStr::from("1, 2"),
|
||||
// RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_join_comma_big() {
|
||||
// assert_evals_to!(
|
||||
// r#"Str.joinWith ["10000000", "2000000", "30000000"] ", " "#,
|
||||
// RocStr::from("10000000, 2000000, 30000000"),
|
||||
// RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_join_comma_single() {
|
||||
// assert_evals_to!(r#"Str.joinWith ["1"] ", " "#, RocStr::from("1"), RocStr);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_from_float() {
|
||||
// assert_evals_to!(r#"Str.fromFloat 3.14"#, RocStr::from("3.14"), RocStr);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_to_utf8() {
|
||||
// assert_evals_to!(
|
||||
// r#"Str.toUtf8 "hello""#,
|
||||
// RocList::from_slice(&[104, 101, 108, 108, 111]),
|
||||
// RocList<u8>
|
||||
// );
|
||||
// assert_evals_to!(
|
||||
// r#"Str.toUtf8 "this is a long string""#,
|
||||
// RocList::from_slice(&[
|
||||
// 116, 104, 105, 115, 32, 105, 115, 32, 97, 32, 108, 111, 110, 103, 32, 115, 116,
|
||||
// 114, 105, 110, 103
|
||||
// ]),
|
||||
// RocList<u8>
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_from_utf8_range() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// bytes = Str.toUtf8 "hello"
|
||||
// when Str.fromUtf8Range bytes { count: 5, start: 0 } is
|
||||
// Ok utf8String -> utf8String
|
||||
// _ -> ""
|
||||
// "#
|
||||
// ),
|
||||
// RocStr::from("hello"),
|
||||
// RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_from_utf8_range_slice() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// bytes = Str.toUtf8 "hello"
|
||||
// when Str.fromUtf8Range bytes { count: 4, start: 1 } is
|
||||
// Ok utf8String -> utf8String
|
||||
// _ -> ""
|
||||
// "#
|
||||
// ),
|
||||
// RocStr::from("ello"),
|
||||
// RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_from_utf8_range_slice_not_end() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// bytes = Str.toUtf8 "hello"
|
||||
// when Str.fromUtf8Range bytes { count: 3, start: 1 } is
|
||||
// Ok utf8String -> utf8String
|
||||
// _ -> ""
|
||||
// "#
|
||||
// ),
|
||||
// RocStr::from("ell"),
|
||||
// RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_from_utf8_range_order_does_not_matter() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// bytes = Str.toUtf8 "hello"
|
||||
// when Str.fromUtf8Range bytes { start: 1, count: 3 } is
|
||||
// Ok utf8String -> utf8String
|
||||
// _ -> ""
|
||||
// "#
|
||||
// ),
|
||||
// RocStr::from("ell"),
|
||||
// RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_from_utf8_range_out_of_bounds_start_value() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// bytes = Str.toUtf8 "hello"
|
||||
// when Str.fromUtf8Range bytes { start: 7, count: 3 } is
|
||||
// Ok _ -> ""
|
||||
// Err (BadUtf8 _ _) -> ""
|
||||
// Err OutOfBounds -> "out of bounds"
|
||||
// "#
|
||||
// ),
|
||||
// RocStr::from("out of bounds"),
|
||||
// RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_from_utf8_range_count_too_high() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// bytes = Str.toUtf8 "hello"
|
||||
// when Str.fromUtf8Range bytes { start: 0, count: 6 } is
|
||||
// Ok _ -> ""
|
||||
// Err (BadUtf8 _ _) -> ""
|
||||
// Err OutOfBounds -> "out of bounds"
|
||||
// "#
|
||||
// ),
|
||||
// RocStr::from("out of bounds"),
|
||||
// RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_from_utf8_range_count_too_high_for_start() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// bytes = Str.toUtf8 "hello"
|
||||
// when Str.fromUtf8Range bytes { start: 4, count: 3 } is
|
||||
// Ok _ -> ""
|
||||
// Err (BadUtf8 _ _) -> ""
|
||||
// Err OutOfBounds -> "out of bounds"
|
||||
// "#
|
||||
// ),
|
||||
// RocStr::from("out of bounds"),
|
||||
// RocStr
|
||||
// );
|
||||
// }
|
||||
}
|
|
@ -18,23 +18,13 @@ roc_solve = { path = "../solve" }
|
|||
roc_mono = { path = "../mono" }
|
||||
roc_std = { path = "../../roc_std" }
|
||||
morphic_lib = { path = "../../vendor/morphic_lib" }
|
||||
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.6.1", features = ["collections"] }
|
||||
im = "15.0.0"
|
||||
im-rc = "15.0.0"
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
inkwell = { path = "../../vendor/inkwell" }
|
||||
target-lexicon = "0.12.2"
|
||||
|
||||
[dev-dependencies]
|
||||
roc_can = { path = "../can" }
|
||||
roc_parse = { path = "../parse" }
|
||||
roc_load = { path = "../load" }
|
||||
roc_reporting = { path = "../reporting" }
|
||||
roc_build = { path = "../build" }
|
||||
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.6.1", features = ["collections"] }
|
||||
libc = "0.2"
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
|
|
|
@ -328,7 +328,7 @@ enum Mode {
|
|||
Dec,
|
||||
}
|
||||
|
||||
/// a functin that accepts two arguments: the value to increment, and an amount to increment by
|
||||
/// a function that accepts two arguments: the value to increment, and an amount to increment by
|
||||
pub fn build_inc_n_wrapper<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
layout_ids: &mut LayoutIds<'a>,
|
||||
|
@ -337,7 +337,7 @@ pub fn build_inc_n_wrapper<'a, 'ctx, 'env>(
|
|||
build_rc_wrapper(env, layout_ids, layout, Mode::IncN)
|
||||
}
|
||||
|
||||
/// a functin that accepts two arguments: the value to increment; increments by 1
|
||||
/// a function that accepts two arguments: the value to increment; increments by 1
|
||||
pub fn build_inc_wrapper<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
layout_ids: &mut LayoutIds<'a>,
|
||||
|
|
|
@ -3108,7 +3108,7 @@ fn expose_function_to_host_help_c_abi_generic<'a, 'ctx, 'env>(
|
|||
return_layout: Layout<'a>,
|
||||
c_function_name: &str,
|
||||
) -> FunctionValue<'ctx> {
|
||||
// NOTE we ingore env.is_gen_test here
|
||||
// NOTE we ignore env.is_gen_test here
|
||||
let wrapper_return_type = roc_function.get_type().get_return_type().unwrap();
|
||||
|
||||
let mut cc_argument_types = Vec::with_capacity_in(arguments.len(), env.arena);
|
||||
|
@ -3765,7 +3765,7 @@ fn make_exception_catching_wrapper<'a, 'ctx, 'env>(
|
|||
// our exposed main function adheres to the C calling convention
|
||||
wrapper_function.set_call_conventions(FAST_CALL_CONV);
|
||||
|
||||
// invoke instead of call, so that we can catch any exeptions thrown in Roc code
|
||||
// invoke instead of call, so that we can catch any exceptions thrown in Roc code
|
||||
let arguments = wrapper_function.get_params();
|
||||
|
||||
let basic_block = context.append_basic_block(wrapper_function, "entry");
|
||||
|
|
|
@ -1379,7 +1379,7 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
|
|||
) {
|
||||
debug_assert_eq!(cases.len(), 1);
|
||||
|
||||
// in this case, don't switch, because the `else` branch below would try to read the (nonexistant) tag id
|
||||
// in this case, don't switch, because the `else` branch below would try to read the (nonexistent) tag id
|
||||
let (_, only_branch) = cases.pop().unwrap();
|
||||
env.builder.build_unconditional_branch(only_branch);
|
||||
} else {
|
||||
|
|
|
@ -9,22 +9,14 @@ edition = "2018"
|
|||
roc_collections = { path = "../collections" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_mono = { path = "../mono" }
|
||||
bumpalo = { version = "3.6.1", features = ["collections"] }
|
||||
# TODO: switch to parity-wasm 0.44 once it's out (allows bumpalo vectors in some places)
|
||||
parity-wasm = { git = "https://github.com/brian-carroll/parity-wasm", branch = "master" }
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
|
||||
roc_std = { path = "../../roc_std" }
|
||||
wasmer = "2.0.0"
|
||||
wasmer-wasi = "2.0.0"
|
||||
wasmer = { version = "2.0.0", default-features = false, features = ["default-cranelift", "default-universal"] }
|
||||
|
||||
[dev-dependencies]
|
||||
roc_can = { path = "../can" }
|
||||
roc_builtins = { path = "../builtins" }
|
||||
roc_load = { path = "../load" }
|
||||
roc_types = { path = "../types" }
|
||||
roc_module = { path = "../module" }
|
||||
indoc = "0.3.3"
|
||||
pretty_assertions = "0.5.1"
|
||||
libc = "0.2"
|
||||
target-lexicon = "0.12.2"
|
||||
tempfile = "3.1.0"
|
||||
|
|
|
@ -56,7 +56,7 @@ There is also an improvement on Relooper called ["Stackifier"](https://medium.co
|
|||
|
||||
## Stack machine vs register machine
|
||||
|
||||
Wasm's instruction set is based on a stack-machine VM. Whereas CPU instructions have named registers that they operate on, Wasm has no named registers at all. The instructions don't contain register names. Instructions can oly operate on whatever data is at the top of the stack.
|
||||
Wasm's instruction set is based on a stack-machine VM. Whereas CPU instructions have named registers that they operate on, Wasm has no named registers at all. The instructions don't contain register names. Instructions can only operate on whatever data is at the top of the stack.
|
||||
|
||||
For example the instruction `i64.add` takes two operands. It pops the top two arguments off the VM stack and pushes the result back.
|
||||
|
||||
|
@ -225,6 +225,4 @@ The Module is a _specification_ for how to create an Instance of the program. Th
|
|||
|
||||
A WebAssembly module is equivalent to an executable file. It doesn't normally need relocations since at the WebAssembly layer, there is no Address Space Layout Randomisation. If it has relocations then it's an object file.
|
||||
|
||||
The [official spec](https://webassembly.github.io/spec/core/binary/modules.html#sections) lists the sections that are part of the final module. It doesn't mention any sections for relocations or symbol names, but it has room for "custom sections" that in practice seem to be used for that.
|
||||
|
||||
The WebAssembly `tool-conventions` repo has a document on [linking](https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md), and the `parity_wasm` crate supports "name" and "relocation" [sections](https://docs.rs/parity-wasm/0.42.2/parity_wasm/elements/enum.Section.html).
|
||||
The [official spec](https://webassembly.github.io/spec/core/binary/modules.html#sections) lists the sections that are part of the final module. It doesn't mention any sections for relocations or symbol names, but it does support "custom" sections. Conventions to use those for linking are documented in the WebAssembly `tool-conventions` repo [here](https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md) and it mentions that LLVM is using those conventions.
|
||||
|
|
|
@ -1,37 +1,36 @@
|
|||
use bumpalo::collections::Vec;
|
||||
use parity_wasm::builder;
|
||||
use parity_wasm::builder::{FunctionDefinition, ModuleBuilder};
|
||||
|
||||
use code_builder::Align;
|
||||
use roc_collections::all::MutMap;
|
||||
use roc_module::low_level::LowLevel;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt};
|
||||
use roc_mono::layout::{Builtin, Layout};
|
||||
use roc_mono::layout::Layout;
|
||||
|
||||
use crate::code_builder::{BlockType, CodeBuilder, ValueType};
|
||||
use crate::layout::WasmLayout;
|
||||
use crate::module_builder::RelocationEntry;
|
||||
use crate::serialize::SerialBuffer;
|
||||
use crate::storage::{Storage, StoredValue, StoredValueKind};
|
||||
use crate::{copy_memory, CopyMemoryConfig, Env, LocalId, PTR_TYPE};
|
||||
use crate::wasm_module::linking::{LinkingSection, RelocationSection};
|
||||
use crate::wasm_module::sections::{
|
||||
CodeSection, DataMode, DataSection, DataSegment, ExportSection, FunctionSection, GlobalSection,
|
||||
ImportSection, MemorySection, TypeSection, WasmModule,
|
||||
};
|
||||
use crate::wasm_module::{
|
||||
code_builder, BlockType, CodeBuilder, ConstExpr, Export, ExportType, Global, GlobalType,
|
||||
LocalId, Signature, ValueType,
|
||||
};
|
||||
use crate::{copy_memory, CopyMemoryConfig, Env, PTR_TYPE};
|
||||
|
||||
// Don't allocate any constant data at address zero or near it. Would be valid, but bug-prone.
|
||||
// Follow Emscripten's example by using 1kB (4 bytes would probably do)
|
||||
const UNUSED_DATA_SECTION_BYTES: u32 = 1024;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct LabelId(u32);
|
||||
|
||||
pub struct WasmBackend<'a> {
|
||||
env: &'a Env<'a>,
|
||||
|
||||
// Module-level data
|
||||
pub module_builder: ModuleBuilder,
|
||||
pub code_section_bytes: std::vec::Vec<u8>,
|
||||
pub code_relocations: Vec<'a, RelocationEntry>,
|
||||
_data_offset_map: MutMap<Literal<'a>, u32>,
|
||||
_data_offset_next: u32,
|
||||
proc_symbols: &'a [Symbol],
|
||||
pub module: WasmModule<'a>,
|
||||
next_literal_addr: u32,
|
||||
proc_symbols: Vec<'a, Symbol>,
|
||||
|
||||
// Function-level data
|
||||
code_builder: CodeBuilder<'a>,
|
||||
|
@ -43,23 +42,56 @@ pub struct WasmBackend<'a> {
|
|||
}
|
||||
|
||||
impl<'a> WasmBackend<'a> {
|
||||
pub fn new(env: &'a Env<'a>, proc_symbols: &'a [Symbol]) -> Self {
|
||||
let mut code_section_bytes = std::vec::Vec::with_capacity(4096);
|
||||
pub fn new(env: &'a Env<'a>, proc_symbols: Vec<'a, Symbol>) -> Self {
|
||||
const MEMORY_INIT_SIZE: u32 = 1024 * 1024;
|
||||
|
||||
// Code section header
|
||||
code_section_bytes.reserve_padded_u32(); // byte length, to be written at the end
|
||||
code_section_bytes.encode_padded_u32(proc_symbols.len() as u32); // modified later in unit tests
|
||||
let mut module = WasmModule {
|
||||
types: TypeSection::new(env.arena),
|
||||
import: ImportSection::new(env.arena),
|
||||
function: FunctionSection::new(env.arena),
|
||||
table: (), // Unused in Roc (mainly for function pointers)
|
||||
memory: MemorySection::new(MEMORY_INIT_SIZE),
|
||||
global: GlobalSection::new(env.arena),
|
||||
export: ExportSection::new(env.arena),
|
||||
start: (), // Entry function. In Roc this would be part of the platform.
|
||||
element: (), // Unused in Roc (related to table section)
|
||||
code: CodeSection::new(env.arena),
|
||||
data: DataSection::new(env.arena),
|
||||
linking: LinkingSection::new(env.arena),
|
||||
reloc_code: RelocationSection::new(env.arena, "reloc.CODE"),
|
||||
reloc_data: RelocationSection::new(env.arena, "reloc.DATA"),
|
||||
};
|
||||
|
||||
module.export.entries.push(Export {
|
||||
name: "memory".to_string(),
|
||||
ty: ExportType::Mem,
|
||||
index: 0,
|
||||
});
|
||||
|
||||
let stack_pointer_global = Global {
|
||||
ty: GlobalType {
|
||||
value_type: ValueType::I32,
|
||||
is_mutable: true,
|
||||
},
|
||||
init: ConstExpr::I32(MEMORY_INIT_SIZE as i32),
|
||||
};
|
||||
module.global.entries.push(stack_pointer_global);
|
||||
|
||||
let literal_segment = DataSegment {
|
||||
mode: DataMode::Active {
|
||||
offset: ConstExpr::I32(UNUSED_DATA_SECTION_BYTES as i32),
|
||||
},
|
||||
init: Vec::with_capacity_in(64, env.arena),
|
||||
};
|
||||
module.data.segments.push(literal_segment);
|
||||
|
||||
WasmBackend {
|
||||
env,
|
||||
|
||||
// Module-level data
|
||||
module_builder: builder::module(),
|
||||
code_section_bytes,
|
||||
_data_offset_map: MutMap::default(),
|
||||
_data_offset_next: UNUSED_DATA_SECTION_BYTES,
|
||||
module,
|
||||
next_literal_addr: UNUSED_DATA_SECTION_BYTES,
|
||||
proc_symbols,
|
||||
code_relocations: Vec::with_capacity_in(256, env.arena),
|
||||
|
||||
// Function-level data
|
||||
block_depth: 0,
|
||||
|
@ -69,8 +101,13 @@ impl<'a> WasmBackend<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Reset function-level data
|
||||
fn reset(&mut self) {
|
||||
self.code_builder.clear();
|
||||
// Push the completed CodeBuilder into the module and swap it for a new empty one
|
||||
let mut swap_code_builder = CodeBuilder::new(self.env.arena);
|
||||
std::mem::swap(&mut swap_code_builder, &mut self.code_builder);
|
||||
self.module.code.code_builders.push(swap_code_builder);
|
||||
|
||||
self.storage.clear();
|
||||
self.joinpoint_label_map.clear();
|
||||
assert_eq!(self.block_depth, 0);
|
||||
|
@ -82,52 +119,43 @@ impl<'a> WasmBackend<'a> {
|
|||
|
||||
***********************************************************/
|
||||
|
||||
pub fn build_proc(&mut self, proc: Proc<'a>, _sym: Symbol) -> Result<u32, String> {
|
||||
// println!("\ngenerating procedure {:?}\n", sym);
|
||||
pub fn build_proc(&mut self, proc: Proc<'a>, _sym: Symbol) -> Result<(), String> {
|
||||
// println!("\ngenerating procedure {:?}\n", _sym);
|
||||
|
||||
// Use parity-wasm to add the signature in "types" and "functions" sections
|
||||
// but no instructions, since we are building our own code section
|
||||
let empty_function_def = self.start_proc(&proc);
|
||||
let location = self.module_builder.push_function(empty_function_def);
|
||||
let function_index = location.body;
|
||||
self.start_proc(&proc);
|
||||
|
||||
self.build_stmt(&proc.body, &proc.ret_layout)?;
|
||||
|
||||
self.finalize_proc()?;
|
||||
self.reset();
|
||||
|
||||
// println!("\nfinished generating {:?}\n", sym);
|
||||
// println!("\nfinished generating {:?}\n", _sym);
|
||||
|
||||
Ok(function_index)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn start_proc(&mut self, proc: &Proc<'a>) -> FunctionDefinition {
|
||||
fn start_proc(&mut self, proc: &Proc<'a>) {
|
||||
let ret_layout = WasmLayout::new(&proc.ret_layout);
|
||||
|
||||
let signature_builder = if let WasmLayout::StackMemory { .. } = ret_layout {
|
||||
let ret_type = if ret_layout.is_stack_memory() {
|
||||
self.storage.arg_types.push(PTR_TYPE);
|
||||
self.start_block(BlockType::NoResult); // block to ensure all paths pop stack memory (if any)
|
||||
builder::signature()
|
||||
None
|
||||
} else {
|
||||
let ret_type = ret_layout.value_type();
|
||||
self.start_block(BlockType::Value(ret_type)); // block to ensure all paths pop stack memory (if any)
|
||||
builder::signature().with_result(ret_type.to_parity_wasm())
|
||||
let ty = ret_layout.value_type();
|
||||
self.start_block(BlockType::Value(ty)); // block to ensure all paths pop stack memory (if any)
|
||||
Some(ty)
|
||||
};
|
||||
|
||||
for (layout, symbol) in proc.args {
|
||||
self.storage.allocate(
|
||||
&WasmLayout::new(layout),
|
||||
*symbol,
|
||||
StoredValueKind::Parameter,
|
||||
);
|
||||
let arg_layout = WasmLayout::new(layout);
|
||||
self.storage
|
||||
.allocate(&arg_layout, *symbol, StoredValueKind::Parameter);
|
||||
}
|
||||
|
||||
let parity_params = self.storage.arg_types.iter().map(|t| t.to_parity_wasm());
|
||||
|
||||
let signature = signature_builder.with_params(parity_params).build_sig();
|
||||
|
||||
// parity-wasm FunctionDefinition with no instructions
|
||||
builder::function().with_signature(signature).build()
|
||||
self.module.add_function_signature(Signature {
|
||||
param_types: self.storage.arg_types.clone(),
|
||||
ret_type,
|
||||
});
|
||||
}
|
||||
|
||||
fn finalize_proc(&mut self) -> Result<(), String> {
|
||||
|
@ -141,8 +169,6 @@ impl<'a> WasmBackend<'a> {
|
|||
self.storage.stack_frame_pointer,
|
||||
);
|
||||
|
||||
let relocs = self.code_builder.serialize(&mut self.code_section_bytes);
|
||||
self.code_relocations.extend(relocs);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -178,9 +204,9 @@ impl<'a> WasmBackend<'a> {
|
|||
_ => StoredValueKind::Variable,
|
||||
};
|
||||
|
||||
self.storage.allocate(&wasm_layout, *sym, kind);
|
||||
let sym_storage = self.storage.allocate(&wasm_layout, *sym, kind);
|
||||
|
||||
self.build_expr(sym, expr, layout)?;
|
||||
self.build_expr(sym, expr, layout, &sym_storage)?;
|
||||
|
||||
// For primitives, we record that this symbol is at the top of the VM stack
|
||||
// (For other values, we wrote to memory and there's nothing on the VM stack)
|
||||
|
@ -367,9 +393,11 @@ impl<'a> WasmBackend<'a> {
|
|||
sym: &Symbol,
|
||||
expr: &Expr<'a>,
|
||||
layout: &Layout<'a>,
|
||||
storage: &StoredValue,
|
||||
) -> Result<(), String> {
|
||||
let wasm_layout = WasmLayout::new(layout);
|
||||
match expr {
|
||||
Expr::Literal(lit) => self.load_literal(lit, layout),
|
||||
Expr::Literal(lit) => self.load_literal(lit, storage),
|
||||
|
||||
Expr::Call(roc_mono::ir::Call {
|
||||
call_type,
|
||||
|
@ -379,7 +407,6 @@ impl<'a> WasmBackend<'a> {
|
|||
// TODO: See if we can make this more efficient
|
||||
// Recreating the same WasmLayout again, rather than passing it down,
|
||||
// to match signature of Backend::build_expr
|
||||
let wasm_layout = WasmLayout::new(layout);
|
||||
|
||||
let mut wasm_args_tmp: Vec<Symbol>;
|
||||
let (wasm_args, has_return_val) = match wasm_layout {
|
||||
|
@ -433,32 +460,69 @@ impl<'a> WasmBackend<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn load_literal(&mut self, lit: &Literal<'a>, layout: &Layout<'a>) -> Result<(), String> {
|
||||
match lit {
|
||||
Literal::Bool(x) => self.code_builder.i32_const(*x as i32),
|
||||
Literal::Byte(x) => self.code_builder.i32_const(*x as i32),
|
||||
Literal::Int(x) => match layout {
|
||||
Layout::Builtin(Builtin::Int64) => self.code_builder.i64_const(*x as i64),
|
||||
Layout::Builtin(
|
||||
Builtin::Int32
|
||||
| Builtin::Int16
|
||||
| Builtin::Int8
|
||||
| Builtin::Int1
|
||||
| Builtin::Usize,
|
||||
) => self.code_builder.i32_const(*x as i32),
|
||||
x => {
|
||||
return Err(format!("loading literal, {:?}, is not yet implemented", x));
|
||||
fn load_literal(&mut self, lit: &Literal<'a>, storage: &StoredValue) -> Result<(), String> {
|
||||
let not_supported_error = || Err(format!("Literal value {:?} is not yet implemented", lit));
|
||||
|
||||
match storage {
|
||||
StoredValue::VirtualMachineStack { value_type, .. } => {
|
||||
match (lit, value_type) {
|
||||
(Literal::Float(x), ValueType::F64) => self.code_builder.f64_const(*x as f64),
|
||||
(Literal::Float(x), ValueType::F32) => self.code_builder.f32_const(*x as f32),
|
||||
(Literal::Int(x), ValueType::I64) => self.code_builder.i64_const(*x as i64),
|
||||
(Literal::Int(x), ValueType::I32) => self.code_builder.i32_const(*x as i32),
|
||||
(Literal::Bool(x), ValueType::I32) => self.code_builder.i32_const(*x as i32),
|
||||
(Literal::Byte(x), ValueType::I32) => self.code_builder.i32_const(*x as i32),
|
||||
_ => {
|
||||
return not_supported_error();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
StoredValue::StackMemory { location, .. } => match lit {
|
||||
Literal::Str(s) => {
|
||||
// Load the stack memory address where we want to write
|
||||
let (local_id, offset) =
|
||||
location.local_and_offset(self.storage.stack_frame_pointer);
|
||||
self.code_builder.get_local(local_id);
|
||||
self.code_builder.i32_const(offset as i32);
|
||||
self.code_builder.i32_add();
|
||||
|
||||
// For either small or regular strings, we write 8 bytes to stack memory
|
||||
let mut stack_mem_bytes = [0; 8];
|
||||
|
||||
let len = s.len();
|
||||
if len < 8 {
|
||||
// Small string payload
|
||||
stack_mem_bytes[0..len].clone_from_slice(s.as_bytes());
|
||||
// Small string flag and length
|
||||
stack_mem_bytes[7] = 0x80 | (len as u8);
|
||||
} else {
|
||||
// Store the bytes in the data section, to be initialised on module load.
|
||||
// Point there instead of to the heap
|
||||
let literal_bytes: &mut Vec<'a, u8> =
|
||||
&mut self.module.data.segments[0].init;
|
||||
literal_bytes.extend_from_slice(s.as_bytes());
|
||||
|
||||
// Calculate bytes for elements and length
|
||||
let len32 = len as u32;
|
||||
let elements_addr = self.next_literal_addr;
|
||||
self.next_literal_addr += len32;
|
||||
stack_mem_bytes[0..3].clone_from_slice(&elements_addr.to_le_bytes());
|
||||
stack_mem_bytes[4..8].clone_from_slice(&len32.to_le_bytes());
|
||||
};
|
||||
|
||||
// Since we have 64 bits of data, we can squeeze them into one instruction
|
||||
self.code_builder
|
||||
.i64_const(i64::from_le_bytes(stack_mem_bytes));
|
||||
self.code_builder.i64_store(Align::Bytes4, offset);
|
||||
}
|
||||
_ => {
|
||||
return not_supported_error();
|
||||
}
|
||||
},
|
||||
Literal::Float(x) => match layout {
|
||||
Layout::Builtin(Builtin::Float64) => self.code_builder.f64_const(*x as f64),
|
||||
Layout::Builtin(Builtin::Float32) => self.code_builder.f32_const(*x as f32),
|
||||
x => {
|
||||
return Err(format!("loading literal, {:?}, is not yet implemented", x));
|
||||
}
|
||||
},
|
||||
x => {
|
||||
return Err(format!("loading literal, {:?}, is not yet implemented", x));
|
||||
|
||||
_ => {
|
||||
return not_supported_error();
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use roc_mono::layout::{Layout, UnionLayout};
|
||||
|
||||
use crate::{code_builder::ValueType, PTR_SIZE, PTR_TYPE};
|
||||
use crate::{wasm_module::ValueType, PTR_SIZE, PTR_TYPE};
|
||||
|
||||
// See README for background information on Wasm locals, memory and function calls
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -71,11 +71,7 @@ impl WasmLayout {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn stack_memory(&self) -> u32 {
|
||||
match self {
|
||||
Self::StackMemory { size, .. } => *size,
|
||||
_ => 0,
|
||||
}
|
||||
pub fn is_stack_memory(&self) -> bool {
|
||||
matches!(self, Self::StackMemory { .. })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,27 +1,21 @@
|
|||
mod backend;
|
||||
pub mod code_builder;
|
||||
pub mod from_wasm32_memory;
|
||||
mod layout;
|
||||
pub mod module_builder;
|
||||
pub mod opcodes;
|
||||
pub mod serialize;
|
||||
mod storage;
|
||||
pub mod wasm_module;
|
||||
|
||||
use bumpalo::{self, collections::Vec, Bump};
|
||||
use parity_wasm::builder;
|
||||
|
||||
use parity_wasm::elements::{Instruction, Internal, Module, Section};
|
||||
use roc_collections::all::{MutMap, MutSet};
|
||||
use roc_module::symbol::{Interns, Symbol};
|
||||
use roc_mono::ir::{Proc, ProcLayout};
|
||||
use roc_mono::layout::LayoutIds;
|
||||
|
||||
use crate::backend::WasmBackend;
|
||||
use crate::code_builder::{Align, CodeBuilder, ValueType};
|
||||
use crate::module_builder::{
|
||||
LinkingSection, LinkingSubSection, RelocationSection, SectionId, SymInfo,
|
||||
use crate::wasm_module::{
|
||||
Align, CodeBuilder, Export, ExportType, LinkingSubSection, LocalId, SymInfo, ValueType,
|
||||
WasmModule,
|
||||
};
|
||||
use crate::serialize::{SerialBuffer, Serialize};
|
||||
|
||||
const PTR_SIZE: u32 = 4;
|
||||
const PTR_TYPE: ValueType = ValueType::I32;
|
||||
|
@ -29,13 +23,6 @@ const PTR_TYPE: ValueType = ValueType::I32;
|
|||
pub const STACK_POINTER_GLOBAL_ID: u32 = 0;
|
||||
pub const FRAME_ALIGNMENT_BYTES: i32 = 16;
|
||||
|
||||
/// Code section ID from spec
|
||||
/// https://webassembly.github.io/spec/core/binary/modules.html#sections
|
||||
pub const CODE_SECTION_ID: u8 = 10;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct LocalId(pub u32);
|
||||
|
||||
pub struct Env<'a> {
|
||||
pub arena: &'a Bump,
|
||||
pub interns: Interns,
|
||||
|
@ -46,21 +33,19 @@ pub fn build_module<'a>(
|
|||
env: &'a Env,
|
||||
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
|
||||
) -> Result<std::vec::Vec<u8>, String> {
|
||||
let (builder, code_section_bytes) = build_module_help(env, procedures)?;
|
||||
let mut module = builder.build();
|
||||
replace_code_section(&mut module, code_section_bytes);
|
||||
|
||||
module
|
||||
.into_bytes()
|
||||
.map_err(|e| -> String { format!("Error serialising Wasm module {:?}", e) })
|
||||
let mut wasm_module = build_module_help(env, procedures)?;
|
||||
let mut buffer = std::vec::Vec::with_capacity(4096);
|
||||
wasm_module.serialize(&mut buffer);
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
pub fn build_module_help<'a>(
|
||||
env: &'a Env,
|
||||
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
|
||||
) -> Result<(builder::ModuleBuilder, std::vec::Vec<u8>), String> {
|
||||
) -> Result<WasmModule<'a>, String> {
|
||||
let proc_symbols = Vec::from_iter_in(procedures.keys().map(|(sym, _)| *sym), env.arena);
|
||||
let mut backend = WasmBackend::new(env, &proc_symbols);
|
||||
let mut backend = WasmBackend::new(env, proc_symbols);
|
||||
|
||||
let mut layout_ids = LayoutIds::default();
|
||||
let mut symbol_table_entries = Vec::with_capacity_in(procedures.len(), env.arena);
|
||||
|
||||
|
@ -70,97 +55,25 @@ pub fn build_module_help<'a>(
|
|||
.to_symbol_string(proc.name, &env.interns);
|
||||
symbol_table_entries.push(SymInfo::for_function(i as u32, proc_name));
|
||||
|
||||
let function_index = backend.build_proc(proc, sym)?;
|
||||
backend.build_proc(proc, sym)?;
|
||||
|
||||
if env.exposed_to_host.contains(&sym) {
|
||||
let fn_name = layout_ids
|
||||
.get_toplevel(sym, &layout)
|
||||
.to_symbol_string(sym, &env.interns);
|
||||
|
||||
let export = builder::export()
|
||||
.field(fn_name.as_str())
|
||||
.with_internal(Internal::Function(function_index))
|
||||
.build();
|
||||
|
||||
backend.module_builder.push_export(export);
|
||||
backend.module.export.entries.push(Export {
|
||||
name: fn_name,
|
||||
ty: ExportType::Func,
|
||||
index: i as u32,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Update code section length
|
||||
let inner_length = (backend.code_section_bytes.len() - 5) as u32;
|
||||
backend
|
||||
.code_section_bytes
|
||||
.overwrite_padded_u32(0, inner_length);
|
||||
let symbol_table = LinkingSubSection::SymbolTable(symbol_table_entries);
|
||||
backend.module.linking.subsections.push(symbol_table);
|
||||
|
||||
// linking metadata section
|
||||
let mut linking_section_bytes = std::vec::Vec::with_capacity(symbol_table_entries.len() * 20);
|
||||
let linking_section = LinkingSection {
|
||||
subsections: bumpalo::vec![in env.arena;
|
||||
LinkingSubSection::SymbolTable(symbol_table_entries)
|
||||
],
|
||||
};
|
||||
linking_section.serialize(&mut linking_section_bytes);
|
||||
backend.module_builder = backend.module_builder.with_section(Section::Unparsed {
|
||||
id: SectionId::Custom as u8,
|
||||
payload: linking_section_bytes,
|
||||
});
|
||||
|
||||
// We always output the code section at the same index relative to other sections, and we need that for relocations.
|
||||
// TODO: If there's a data section, this will be 6 so we'll need logic for that
|
||||
// TODO: Build a cleaner solution after we replace parity-wasm with our own module_builder
|
||||
const CODE_SECTION_INDEX: u32 = 5;
|
||||
|
||||
let code_reloc_section = RelocationSection {
|
||||
name: "reloc.CODE",
|
||||
target_section_index: CODE_SECTION_INDEX,
|
||||
entries: &backend.code_relocations,
|
||||
};
|
||||
|
||||
let mut code_reloc_section_bytes = std::vec::Vec::with_capacity(256);
|
||||
code_reloc_section.serialize(&mut code_reloc_section_bytes);
|
||||
|
||||
// Must come after linking section
|
||||
backend.module_builder = backend.module_builder.with_section(Section::Unparsed {
|
||||
id: SectionId::Custom as u8,
|
||||
payload: code_reloc_section_bytes,
|
||||
});
|
||||
|
||||
const MIN_MEMORY_SIZE_KB: u32 = 1024;
|
||||
const PAGE_SIZE_KB: u32 = 64;
|
||||
|
||||
let memory = builder::MemoryBuilder::new()
|
||||
.with_min(MIN_MEMORY_SIZE_KB / PAGE_SIZE_KB)
|
||||
.build();
|
||||
backend.module_builder.push_memory(memory);
|
||||
let memory_export = builder::export()
|
||||
.field("memory")
|
||||
.with_internal(Internal::Memory(0))
|
||||
.build();
|
||||
backend.module_builder.push_export(memory_export);
|
||||
|
||||
let stack_pointer_global = builder::global()
|
||||
.with_type(parity_wasm::elements::ValueType::I32)
|
||||
.mutable()
|
||||
.init_expr(Instruction::I32Const((MIN_MEMORY_SIZE_KB * 1024) as i32))
|
||||
.build();
|
||||
backend.module_builder.push_global(stack_pointer_global);
|
||||
|
||||
Ok((backend.module_builder, backend.code_section_bytes))
|
||||
}
|
||||
|
||||
/// Replace parity-wasm's code section with our own handmade one
|
||||
pub fn replace_code_section(module: &mut Module, code_section_bytes: std::vec::Vec<u8>) {
|
||||
let sections = module.sections_mut();
|
||||
|
||||
let code_section_index = sections
|
||||
.iter()
|
||||
.position(|s| matches!(s, Section::Code(_)))
|
||||
.unwrap();
|
||||
|
||||
sections[code_section_index] = Section::Unparsed {
|
||||
id: SectionId::Code as u8,
|
||||
payload: code_section_bytes,
|
||||
};
|
||||
Ok(backend.module)
|
||||
}
|
||||
|
||||
pub struct CopyMemoryConfig {
|
||||
|
|
|
@ -4,9 +4,9 @@ use bumpalo::Bump;
|
|||
use roc_collections::all::MutMap;
|
||||
use roc_module::symbol::Symbol;
|
||||
|
||||
use crate::code_builder::{CodeBuilder, ValueType, VirtualMachineSymbolState};
|
||||
use crate::layout::WasmLayout;
|
||||
use crate::{copy_memory, round_up_to_alignment, CopyMemoryConfig, LocalId, PTR_SIZE, PTR_TYPE};
|
||||
use crate::wasm_module::{CodeBuilder, LocalId, ValueType, VirtualMachineSymbolState};
|
||||
use crate::{copy_memory, round_up_to_alignment, CopyMemoryConfig, PTR_SIZE, PTR_TYPE};
|
||||
|
||||
pub enum StoredValueKind {
|
||||
Parameter,
|
||||
|
@ -291,7 +291,7 @@ impl<'a> Storage<'a> {
|
|||
| StoredValue::Local {
|
||||
value_type, size, ..
|
||||
} => {
|
||||
use crate::code_builder::Align::*;
|
||||
use crate::wasm_module::Align::*;
|
||||
code_builder.get_local(to_ptr);
|
||||
self.load_symbols(code_builder, &[from_symbol]);
|
||||
match (value_type, size) {
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
use bumpalo::collections::vec::{Drain, Vec};
|
||||
use bumpalo::collections::vec::Vec;
|
||||
use bumpalo::Bump;
|
||||
use core::panic;
|
||||
use std::fmt::Debug;
|
||||
|
||||
use roc_module::symbol::Symbol;
|
||||
|
||||
use crate::module_builder::{IndexRelocType, RelocationEntry};
|
||||
use crate::opcodes::*;
|
||||
use crate::serialize::SerialBuffer;
|
||||
use crate::{round_up_to_alignment, LocalId, FRAME_ALIGNMENT_BYTES, STACK_POINTER_GLOBAL_ID};
|
||||
use super::linking::{IndexRelocType, RelocationEntry};
|
||||
use super::opcodes::*;
|
||||
use super::serialize::{SerialBuffer, Serialize};
|
||||
use crate::{round_up_to_alignment, FRAME_ALIGNMENT_BYTES, STACK_POINTER_GLOBAL_ID};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct LocalId(pub u32);
|
||||
|
||||
/// Wasm value type. (Rust representation matches Wasm encoding)
|
||||
#[repr(u8)]
|
||||
|
@ -20,15 +23,9 @@ pub enum ValueType {
|
|||
F64 = 0x7c,
|
||||
}
|
||||
|
||||
// This is a bit unfortunate. Will go away if we generate our own Types section.
|
||||
impl ValueType {
|
||||
pub fn to_parity_wasm(&self) -> parity_wasm::elements::ValueType {
|
||||
match self {
|
||||
Self::I32 => parity_wasm::elements::ValueType::I32,
|
||||
Self::I64 => parity_wasm::elements::ValueType::I64,
|
||||
Self::F32 => parity_wasm::elements::ValueType::F32,
|
||||
Self::F64 => parity_wasm::elements::ValueType::F64,
|
||||
}
|
||||
impl Serialize for ValueType {
|
||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
||||
buffer.append_u8(*self as u8);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -92,8 +89,8 @@ pub enum VirtualMachineSymbolState {
|
|||
|
||||
// An instruction (local.set or local.tee) to be inserted into the function code
|
||||
#[derive(Debug)]
|
||||
struct InsertLocation {
|
||||
insert_at: usize,
|
||||
struct Insertion {
|
||||
at: usize,
|
||||
start: usize,
|
||||
end: usize,
|
||||
}
|
||||
|
@ -124,7 +121,7 @@ pub struct CodeBuilder<'a> {
|
|||
insert_bytes: Vec<'a, u8>,
|
||||
|
||||
/// Code locations where the insert_bytes should go
|
||||
insert_locations: Vec<'a, InsertLocation>,
|
||||
insertions: Vec<'a, Insertion>,
|
||||
|
||||
/// Bytes for local variable declarations and stack-frame setup code.
|
||||
/// We can't write this until we've finished the main code. But it goes
|
||||
|
@ -151,7 +148,7 @@ impl<'a> CodeBuilder<'a> {
|
|||
pub fn new(arena: &'a Bump) -> Self {
|
||||
CodeBuilder {
|
||||
code: Vec::with_capacity_in(1024, arena),
|
||||
insert_locations: Vec::with_capacity_in(32, arena),
|
||||
insertions: Vec::with_capacity_in(32, arena),
|
||||
insert_bytes: Vec::with_capacity_in(64, arena),
|
||||
preamble: Vec::with_capacity_in(32, arena),
|
||||
inner_length: Vec::with_capacity_in(5, arena),
|
||||
|
@ -160,15 +157,6 @@ impl<'a> CodeBuilder<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.code.clear();
|
||||
self.insert_locations.clear();
|
||||
self.insert_bytes.clear();
|
||||
self.preamble.clear();
|
||||
self.inner_length.clear();
|
||||
self.vm_stack.clear();
|
||||
}
|
||||
|
||||
/**********************************************************
|
||||
|
||||
SYMBOLS
|
||||
|
@ -220,8 +208,8 @@ impl<'a> CodeBuilder<'a> {
|
|||
self.insert_bytes.push(opcode);
|
||||
self.insert_bytes.encode_u32(immediate);
|
||||
|
||||
self.insert_locations.push(InsertLocation {
|
||||
insert_at,
|
||||
self.insertions.push(Insertion {
|
||||
at: insert_at,
|
||||
start,
|
||||
end: self.insert_bytes.len(),
|
||||
});
|
||||
|
@ -384,52 +372,59 @@ impl<'a> CodeBuilder<'a> {
|
|||
|
||||
let inner_len = self.preamble.len() + self.code.len() + self.insert_bytes.len();
|
||||
self.inner_length.encode_u32(inner_len as u32);
|
||||
}
|
||||
|
||||
/// Write out all the bytes in the right order
|
||||
pub fn serialize<T: SerialBuffer>(
|
||||
&mut self,
|
||||
code_section_buf: &mut T,
|
||||
) -> Drain<RelocationEntry> {
|
||||
code_section_buf.append_slice(&self.inner_length);
|
||||
code_section_buf.append_slice(&self.preamble);
|
||||
|
||||
// Sort insertions. They are not created in order of assignment, but in order of *second* usage.
|
||||
self.insert_locations.sort_by_key(|loc| loc.insert_at);
|
||||
self.insertions.sort_by_key(|ins| ins.at);
|
||||
}
|
||||
|
||||
/// Serialize all byte vectors in the right order
|
||||
/// Also update relocation offsets relative to the base offset (code section body start)
|
||||
pub fn serialize_with_relocs<T: SerialBuffer>(
|
||||
&self,
|
||||
buffer: &mut T,
|
||||
final_relocs: &mut Vec<'a, RelocationEntry>,
|
||||
reloc_base_offset: usize,
|
||||
) {
|
||||
buffer.append_slice(&self.inner_length);
|
||||
buffer.append_slice(&self.preamble);
|
||||
|
||||
// Do the insertions & update relocation offsets
|
||||
const CODE_SECTION_BODY_OFFSET: usize = 5;
|
||||
let mut reloc_index = 0;
|
||||
let mut code_pos: usize = 0;
|
||||
for location in self.insert_locations.iter() {
|
||||
let mut code_pos = 0;
|
||||
let mut insert_iter = self.insertions.iter();
|
||||
|
||||
loop {
|
||||
let next_insert = insert_iter.next();
|
||||
let next_pos = match next_insert {
|
||||
Some(Insertion { at, .. }) => *at,
|
||||
None => self.code.len(),
|
||||
};
|
||||
|
||||
// Relocation offset needs to be an index into the body of the code section, but
|
||||
// at this point it is an index into self.code. Need to adjust for all previous functions
|
||||
// in the code section, and for insertions in the current function.
|
||||
let section_body_pos = code_section_buf.size() - CODE_SECTION_BODY_OFFSET;
|
||||
let section_body_pos = buffer.size() - reloc_base_offset;
|
||||
while reloc_index < self.relocations.len()
|
||||
&& self.relocations[reloc_index].offset() < location.insert_at as u32
|
||||
&& self.relocations[reloc_index].offset() < next_pos as u32
|
||||
{
|
||||
let offset_ref = self.relocations[reloc_index].offset_mut();
|
||||
*offset_ref += (section_body_pos - code_pos) as u32;
|
||||
let mut reloc_clone = self.relocations[reloc_index].clone();
|
||||
*reloc_clone.offset_mut() += (section_body_pos - code_pos) as u32;
|
||||
final_relocs.push(reloc_clone);
|
||||
reloc_index += 1;
|
||||
}
|
||||
|
||||
code_section_buf.append_slice(&self.code[code_pos..location.insert_at]);
|
||||
code_section_buf.append_slice(&self.insert_bytes[location.start..location.end]);
|
||||
code_pos = location.insert_at;
|
||||
buffer.append_slice(&self.code[code_pos..next_pos]);
|
||||
|
||||
match next_insert {
|
||||
Some(Insertion { at, start, end }) => {
|
||||
buffer.append_slice(&self.insert_bytes[*start..*end]);
|
||||
code_pos = *at;
|
||||
}
|
||||
None => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let section_body_pos = code_section_buf.size() - CODE_SECTION_BODY_OFFSET;
|
||||
while reloc_index < self.relocations.len() {
|
||||
let offset_ref = self.relocations[reloc_index].offset_mut();
|
||||
*offset_ref += (section_body_pos - code_pos) as u32;
|
||||
reloc_index += 1;
|
||||
}
|
||||
|
||||
let len = self.code.len();
|
||||
code_section_buf.append_slice(&self.code[code_pos..len]);
|
||||
|
||||
self.relocations.drain(0..)
|
||||
}
|
||||
|
||||
/**********************************************************
|
|
@ -1,73 +1,9 @@
|
|||
use bumpalo::collections::vec::Vec;
|
||||
use bumpalo::Bump;
|
||||
|
||||
use crate::code_builder::Align;
|
||||
use crate::serialize::{SerialBuffer, Serialize};
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||
pub enum SectionId {
|
||||
Custom = 0,
|
||||
Type = 1,
|
||||
Import = 2,
|
||||
Function = 3,
|
||||
Table = 4,
|
||||
Memory = 5,
|
||||
Global = 6,
|
||||
Export = 7,
|
||||
Start = 8,
|
||||
Element = 9,
|
||||
Code = 10,
|
||||
Data = 11,
|
||||
DataCount = 12,
|
||||
}
|
||||
|
||||
struct SectionHeaderIndices {
|
||||
size_index: usize,
|
||||
body_index: usize,
|
||||
}
|
||||
|
||||
/// Write a section header, returning the position of the encoded length
|
||||
fn _write_section_header<T: SerialBuffer>(buffer: &mut T, id: SectionId) -> SectionHeaderIndices {
|
||||
buffer.append_byte(id as u8);
|
||||
let size_index = buffer.reserve_padded_u32();
|
||||
let body_index = buffer.size();
|
||||
SectionHeaderIndices {
|
||||
size_index,
|
||||
body_index,
|
||||
}
|
||||
}
|
||||
|
||||
/// Write a custom section header, returning the position of the encoded length
|
||||
fn write_custom_section_header<T: SerialBuffer>(
|
||||
buffer: &mut T,
|
||||
name: &str,
|
||||
) -> SectionHeaderIndices {
|
||||
// buffer.append_byte(SectionId::Custom as u8); // TODO: uncomment when we get rid of parity_wasm
|
||||
let size_index = buffer.reserve_padded_u32();
|
||||
let body_index = buffer.size();
|
||||
name.serialize(buffer);
|
||||
SectionHeaderIndices {
|
||||
size_index,
|
||||
body_index,
|
||||
}
|
||||
}
|
||||
|
||||
/// Update a section header with its final size, after writing the bytes
|
||||
fn update_section_size<T: SerialBuffer>(buffer: &mut T, header_indices: SectionHeaderIndices) {
|
||||
let size = buffer.size() - header_indices.body_index;
|
||||
buffer.overwrite_padded_u32(header_indices.size_index, size as u32);
|
||||
}
|
||||
|
||||
fn serialize_vector_with_count<'a, SB, S>(buffer: &mut SB, items: &Vec<'a, S>)
|
||||
where
|
||||
SB: SerialBuffer,
|
||||
S: Serialize,
|
||||
{
|
||||
buffer.encode_u32(items.len() as u32);
|
||||
for item in items.iter() {
|
||||
item.serialize(buffer);
|
||||
}
|
||||
}
|
||||
use super::sections::{update_section_size, write_custom_section_header};
|
||||
use super::serialize::{SerialBuffer, Serialize};
|
||||
use super::Align;
|
||||
|
||||
/*******************************************************************
|
||||
*
|
||||
|
@ -132,7 +68,7 @@ pub enum OffsetRelocType {
|
|||
MemoryAddrI64 = 16,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum RelocationEntry {
|
||||
Index {
|
||||
type_id: IndexRelocType,
|
||||
|
@ -181,7 +117,7 @@ impl Serialize for RelocationEntry {
|
|||
offset,
|
||||
symbol_index,
|
||||
} => {
|
||||
buffer.append_byte(*type_id as u8);
|
||||
buffer.append_u8(*type_id as u8);
|
||||
buffer.encode_u32(*offset);
|
||||
buffer.encode_u32(*symbol_index);
|
||||
}
|
||||
|
@ -191,7 +127,7 @@ impl Serialize for RelocationEntry {
|
|||
symbol_index,
|
||||
addend,
|
||||
} => {
|
||||
buffer.append_byte(*type_id as u8);
|
||||
buffer.append_u8(*type_id as u8);
|
||||
buffer.encode_u32(*offset);
|
||||
buffer.encode_u32(*symbol_index);
|
||||
buffer.encode_i32(*addend);
|
||||
|
@ -204,17 +140,29 @@ impl Serialize for RelocationEntry {
|
|||
pub struct RelocationSection<'a> {
|
||||
pub name: &'a str,
|
||||
/// The *index* (not ID!) of the target section in the module
|
||||
pub target_section_index: u32,
|
||||
pub entries: &'a Vec<'a, RelocationEntry>,
|
||||
pub target_section_index: Option<u32>,
|
||||
pub entries: Vec<'a, RelocationEntry>,
|
||||
}
|
||||
|
||||
impl<'a> RelocationSection<'a> {
|
||||
pub fn new(arena: &'a Bump, name: &'a str) -> Self {
|
||||
RelocationSection {
|
||||
name,
|
||||
target_section_index: None,
|
||||
entries: Vec::with_capacity_in(64, arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Serialize for RelocationSection<'a> {
|
||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
||||
if !self.entries.is_empty() {
|
||||
let header_indices = write_custom_section_header(buffer, self.name);
|
||||
buffer.encode_u32(self.target_section_index);
|
||||
serialize_vector_with_count(buffer, self.entries);
|
||||
buffer.encode_u32(self.target_section_index.unwrap());
|
||||
self.entries.serialize(buffer);
|
||||
update_section_size(buffer, header_indices);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************
|
||||
|
@ -231,6 +179,7 @@ pub struct LinkingSegment {
|
|||
pub alignment: Align,
|
||||
pub flags: u32,
|
||||
}
|
||||
|
||||
impl Serialize for LinkingSegment {
|
||||
fn serialize<T: SerialBuffer>(&self, _buffer: &mut T) {
|
||||
todo!();
|
||||
|
@ -242,17 +191,16 @@ pub struct LinkingInitFunc {
|
|||
pub priority: u32,
|
||||
pub symbol_index: u32, // index in the symbol table, not the function index
|
||||
}
|
||||
|
||||
impl Serialize for LinkingInitFunc {
|
||||
fn serialize<T: SerialBuffer>(&self, _buffer: &mut T) {
|
||||
todo!();
|
||||
}
|
||||
}
|
||||
|
||||
//----------------
|
||||
//
|
||||
//------------------------------------------------
|
||||
// Common data
|
||||
//
|
||||
//----------------
|
||||
//------------------------------------------------
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||
|
@ -269,6 +217,7 @@ pub struct ComdatSym {
|
|||
pub kind: ComdatSymKind,
|
||||
pub index: u32,
|
||||
}
|
||||
|
||||
impl Serialize for ComdatSym {
|
||||
fn serialize<T: SerialBuffer>(&self, _buffer: &mut T) {
|
||||
todo!();
|
||||
|
@ -285,17 +234,16 @@ pub struct LinkingComdat<'a> {
|
|||
flags: u32,
|
||||
syms: Vec<'a, ComdatSym>,
|
||||
}
|
||||
|
||||
impl<'a> Serialize for LinkingComdat<'a> {
|
||||
fn serialize<T: SerialBuffer>(&self, _buffer: &mut T) {
|
||||
todo!();
|
||||
}
|
||||
}
|
||||
|
||||
//----------------
|
||||
//
|
||||
//------------------------------------------------
|
||||
// Symbol table
|
||||
//
|
||||
//----------------
|
||||
//------------------------------------------------
|
||||
|
||||
/// Indicating that this is a weak symbol. When
|
||||
/// linking multiple modules defining the same symbol, all weak definitions are
|
||||
|
@ -339,6 +287,7 @@ pub enum WasmObjectSymbol {
|
|||
Defined { index: u32, name: String },
|
||||
Imported { index: u32 },
|
||||
}
|
||||
|
||||
impl Serialize for WasmObjectSymbol {
|
||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
||||
match self {
|
||||
|
@ -365,6 +314,7 @@ pub enum DataSymbol {
|
|||
name: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl Serialize for DataSymbol {
|
||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
||||
match self {
|
||||
|
@ -405,6 +355,7 @@ pub struct SymInfo {
|
|||
flags: u32,
|
||||
info: SymInfoFields,
|
||||
}
|
||||
|
||||
impl SymInfo {
|
||||
pub fn for_function(wasm_function_index: u32, name: String) -> Self {
|
||||
let linking_symbol = WasmObjectSymbol::Defined {
|
||||
|
@ -420,7 +371,7 @@ impl SymInfo {
|
|||
|
||||
impl Serialize for SymInfo {
|
||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
||||
buffer.append_byte(match self.info {
|
||||
buffer.append_u8(match self.info {
|
||||
SymInfoFields::Function(_) => 0,
|
||||
SymInfoFields::Data(_) => 1,
|
||||
SymInfoFields::Global(_) => 2,
|
||||
|
@ -442,11 +393,9 @@ impl Serialize for SymInfo {
|
|||
}
|
||||
}
|
||||
|
||||
//--------------------------------
|
||||
//
|
||||
//----------------------------------------------------------------
|
||||
// Linking subsections
|
||||
//
|
||||
//--------------------------------
|
||||
//----------------------------------------------------------------
|
||||
|
||||
pub enum LinkingSubSection<'a> {
|
||||
/// Extra metadata about the data segments.
|
||||
|
@ -459,9 +408,10 @@ pub enum LinkingSubSection<'a> {
|
|||
/// Specifies extra information about the symbols present in the module.
|
||||
SymbolTable(Vec<'a, SymInfo>),
|
||||
}
|
||||
|
||||
impl<'a> Serialize for LinkingSubSection<'a> {
|
||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
||||
buffer.append_byte(match self {
|
||||
buffer.append_u8(match self {
|
||||
Self::SegmentInfo(_) => 5,
|
||||
Self::InitFuncs(_) => 6,
|
||||
Self::ComdatInfo(_) => 7,
|
||||
|
@ -470,10 +420,10 @@ impl<'a> Serialize for LinkingSubSection<'a> {
|
|||
let payload_len_index = buffer.reserve_padded_u32();
|
||||
let payload_start_index = buffer.size();
|
||||
match self {
|
||||
Self::SegmentInfo(items) => serialize_vector_with_count(buffer, items),
|
||||
Self::InitFuncs(items) => serialize_vector_with_count(buffer, items),
|
||||
Self::ComdatInfo(items) => serialize_vector_with_count(buffer, items),
|
||||
Self::SymbolTable(items) => serialize_vector_with_count(buffer, items),
|
||||
Self::SegmentInfo(items) => items.serialize(buffer),
|
||||
Self::InitFuncs(items) => items.serialize(buffer),
|
||||
Self::ComdatInfo(items) => items.serialize(buffer),
|
||||
Self::SymbolTable(items) => items.serialize(buffer),
|
||||
}
|
||||
buffer.overwrite_padded_u32(
|
||||
payload_len_index,
|
||||
|
@ -482,15 +432,28 @@ impl<'a> Serialize for LinkingSubSection<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------
|
||||
// Linking metadata section
|
||||
//----------------------------------------------------------------
|
||||
|
||||
const LINKING_VERSION: u8 = 2;
|
||||
|
||||
pub struct LinkingSection<'a> {
|
||||
pub subsections: Vec<'a, LinkingSubSection<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> LinkingSection<'a> {
|
||||
pub fn new(arena: &'a Bump) -> Self {
|
||||
LinkingSection {
|
||||
subsections: Vec::with_capacity_in(1, arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Serialize for LinkingSection<'a> {
|
||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
||||
let header_indices = write_custom_section_header(buffer, "linking");
|
||||
buffer.append_byte(LINKING_VERSION);
|
||||
buffer.append_u8(LINKING_VERSION);
|
||||
for subsection in self.subsections.iter() {
|
||||
subsection.serialize(buffer);
|
||||
}
|
11
compiler/gen_wasm/src/wasm_module/mod.rs
Normal file
11
compiler/gen_wasm/src/wasm_module/mod.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
pub mod code_builder;
|
||||
pub mod linking;
|
||||
pub mod opcodes;
|
||||
pub mod sections;
|
||||
pub mod serialize;
|
||||
|
||||
pub use code_builder::{
|
||||
Align, BlockType, CodeBuilder, LocalId, ValueType, VirtualMachineSymbolState,
|
||||
};
|
||||
pub use linking::{LinkingSubSection, SymInfo};
|
||||
pub use sections::{ConstExpr, Export, ExportType, Global, GlobalType, Signature, WasmModule};
|
675
compiler/gen_wasm/src/wasm_module/sections.rs
Normal file
675
compiler/gen_wasm/src/wasm_module/sections.rs
Normal file
|
@ -0,0 +1,675 @@
|
|||
use bumpalo::collections::vec::Vec;
|
||||
use bumpalo::Bump;
|
||||
|
||||
use super::linking::{LinkingSection, RelocationEntry, RelocationSection};
|
||||
use super::opcodes;
|
||||
use super::serialize::{SerialBuffer, Serialize};
|
||||
use super::{CodeBuilder, ValueType};
|
||||
|
||||
/*******************************************************************
|
||||
*
|
||||
* Helpers
|
||||
*
|
||||
*******************************************************************/
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||
pub enum SectionId {
|
||||
Custom = 0,
|
||||
Type = 1,
|
||||
Import = 2,
|
||||
Function = 3,
|
||||
Table = 4,
|
||||
Memory = 5,
|
||||
Global = 6,
|
||||
Export = 7,
|
||||
Start = 8,
|
||||
Element = 9,
|
||||
Code = 10,
|
||||
Data = 11,
|
||||
DataCount = 12,
|
||||
}
|
||||
|
||||
pub struct SectionHeaderIndices {
|
||||
size_index: usize,
|
||||
body_index: usize,
|
||||
}
|
||||
|
||||
/// Write a section header, returning the position of the encoded length
|
||||
fn write_section_header<T: SerialBuffer>(buffer: &mut T, id: SectionId) -> SectionHeaderIndices {
|
||||
buffer.append_u8(id as u8);
|
||||
let size_index = buffer.reserve_padded_u32();
|
||||
let body_index = buffer.size();
|
||||
SectionHeaderIndices {
|
||||
size_index,
|
||||
body_index,
|
||||
}
|
||||
}
|
||||
|
||||
/// Write a custom section header, returning the position of the encoded length
|
||||
pub fn write_custom_section_header<T: SerialBuffer>(
|
||||
buffer: &mut T,
|
||||
name: &str,
|
||||
) -> SectionHeaderIndices {
|
||||
buffer.append_u8(SectionId::Custom as u8);
|
||||
let size_index = buffer.reserve_padded_u32();
|
||||
let body_index = buffer.size();
|
||||
name.serialize(buffer);
|
||||
SectionHeaderIndices {
|
||||
size_index,
|
||||
body_index,
|
||||
}
|
||||
}
|
||||
|
||||
/// Update a section header with its final size, after writing the bytes
|
||||
pub fn update_section_size<T: SerialBuffer>(buffer: &mut T, header_indices: SectionHeaderIndices) {
|
||||
let size = buffer.size() - header_indices.body_index;
|
||||
buffer.overwrite_padded_u32(header_indices.size_index, size as u32);
|
||||
}
|
||||
|
||||
/// Serialize a section that is just a vector of some struct
|
||||
fn serialize_vector_section<B: SerialBuffer, T: Serialize>(
|
||||
buffer: &mut B,
|
||||
section_id: SectionId,
|
||||
subsections: &[T],
|
||||
) {
|
||||
if !subsections.is_empty() {
|
||||
let header_indices = write_section_header(buffer, section_id);
|
||||
subsections.serialize(buffer);
|
||||
update_section_size(buffer, header_indices);
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************
|
||||
*
|
||||
* Type section
|
||||
* Deduplicated list of function type signatures
|
||||
*
|
||||
*******************************************************************/
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
pub struct Signature<'a> {
|
||||
pub param_types: Vec<'a, ValueType>,
|
||||
pub ret_type: Option<ValueType>,
|
||||
}
|
||||
|
||||
impl<'a> Serialize for Signature<'a> {
|
||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
||||
buffer.append_u8(0x60);
|
||||
self.param_types.serialize(buffer);
|
||||
self.ret_type.serialize(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TypeSection<'a> {
|
||||
/// Private. See WasmModule::add_function_signature
|
||||
signatures: Vec<'a, Signature<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> TypeSection<'a> {
|
||||
pub fn new(arena: &'a Bump) -> Self {
|
||||
TypeSection {
|
||||
signatures: Vec::with_capacity_in(8, arena),
|
||||
}
|
||||
}
|
||||
|
||||
/// Find a matching signature or insert a new one. Return the index.
|
||||
fn insert(&mut self, signature: Signature<'a>) -> u32 {
|
||||
// Using linear search because we need to preserve indices stored in
|
||||
// the Function section. (Also for practical sizes it's fast)
|
||||
let maybe_index = self.signatures.iter().position(|s| *s == signature);
|
||||
match maybe_index {
|
||||
Some(index) => index as u32,
|
||||
None => {
|
||||
let index = self.signatures.len();
|
||||
self.signatures.push(signature);
|
||||
index as u32
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Serialize for TypeSection<'a> {
|
||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
||||
serialize_vector_section(buffer, SectionId::Type, &self.signatures);
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************
|
||||
*
|
||||
* Import section
|
||||
*
|
||||
*******************************************************************/
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||
pub enum RefType {
|
||||
Func = 0x70,
|
||||
Extern = 0x6f,
|
||||
}
|
||||
|
||||
pub struct TableType {
|
||||
pub ref_type: RefType,
|
||||
pub limits: Limits,
|
||||
}
|
||||
|
||||
impl Serialize for TableType {
|
||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
||||
buffer.append_u8(self.ref_type as u8);
|
||||
self.limits.serialize(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ImportDesc {
|
||||
Func { signature_index: u32 },
|
||||
Table { ty: TableType },
|
||||
Mem { limits: Limits },
|
||||
Global { ty: GlobalType },
|
||||
}
|
||||
|
||||
pub struct Import {
|
||||
pub module: String,
|
||||
pub name: String,
|
||||
pub description: ImportDesc,
|
||||
}
|
||||
|
||||
impl Serialize for Import {
|
||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
||||
self.module.serialize(buffer);
|
||||
self.name.serialize(buffer);
|
||||
match &self.description {
|
||||
ImportDesc::Func { signature_index } => {
|
||||
buffer.append_u8(0);
|
||||
buffer.encode_u32(*signature_index);
|
||||
}
|
||||
ImportDesc::Table { ty } => {
|
||||
buffer.append_u8(1);
|
||||
ty.serialize(buffer);
|
||||
}
|
||||
ImportDesc::Mem { limits } => {
|
||||
buffer.append_u8(2);
|
||||
limits.serialize(buffer);
|
||||
}
|
||||
ImportDesc::Global { ty } => {
|
||||
buffer.append_u8(3);
|
||||
ty.serialize(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ImportSection<'a> {
|
||||
entries: Vec<'a, Import>,
|
||||
}
|
||||
|
||||
impl<'a> ImportSection<'a> {
|
||||
pub fn new(arena: &'a Bump) -> Self {
|
||||
ImportSection {
|
||||
entries: bumpalo::vec![in arena],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Serialize for ImportSection<'a> {
|
||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
||||
serialize_vector_section(buffer, SectionId::Import, &self.entries);
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************
|
||||
*
|
||||
* Function section
|
||||
* Maps function indices (Code section) to signature indices (Type section)
|
||||
*
|
||||
*******************************************************************/
|
||||
|
||||
pub struct FunctionSection<'a> {
|
||||
/// Private. See WasmModule::add_function_signature
|
||||
signature_indices: Vec<'a, u32>,
|
||||
}
|
||||
|
||||
impl<'a> FunctionSection<'a> {
|
||||
pub fn new(arena: &'a Bump) -> Self {
|
||||
FunctionSection {
|
||||
signature_indices: Vec::with_capacity_in(8, arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Serialize for FunctionSection<'a> {
|
||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
||||
serialize_vector_section(buffer, SectionId::Function, &self.signature_indices);
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************
|
||||
*
|
||||
* Memory section
|
||||
*
|
||||
*******************************************************************/
|
||||
|
||||
pub enum Limits {
|
||||
Min(u32),
|
||||
MinMax(u32, u32),
|
||||
}
|
||||
|
||||
impl Serialize for Limits {
|
||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
||||
match self {
|
||||
Self::Min(min) => {
|
||||
buffer.append_u8(0);
|
||||
buffer.encode_u32(*min);
|
||||
}
|
||||
Self::MinMax(min, max) => {
|
||||
buffer.append_u8(1);
|
||||
buffer.encode_u32(*min);
|
||||
buffer.encode_u32(*max);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MemorySection(Option<Limits>);
|
||||
|
||||
impl MemorySection {
|
||||
pub const PAGE_SIZE: u32 = 64 * 1024;
|
||||
|
||||
pub fn new(bytes: u32) -> Self {
|
||||
if bytes == 0 {
|
||||
MemorySection(None)
|
||||
} else {
|
||||
let pages = (bytes + Self::PAGE_SIZE - 1) / Self::PAGE_SIZE;
|
||||
MemorySection(Some(Limits::Min(pages)))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn min_size(&self) -> Option<u32> {
|
||||
match self {
|
||||
MemorySection(Some(Limits::Min(min))) | MemorySection(Some(Limits::MinMax(min, _))) => {
|
||||
Some(min * Self::PAGE_SIZE)
|
||||
}
|
||||
MemorySection(None) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for MemorySection {
|
||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
||||
if let Some(limits) = &self.0 {
|
||||
let header_indices = write_section_header(buffer, SectionId::Memory);
|
||||
buffer.append_u8(1);
|
||||
limits.serialize(buffer);
|
||||
update_section_size(buffer, header_indices);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************
|
||||
*
|
||||
* Global section
|
||||
*
|
||||
*******************************************************************/
|
||||
|
||||
pub struct GlobalType {
|
||||
pub value_type: ValueType,
|
||||
pub is_mutable: bool,
|
||||
}
|
||||
|
||||
impl Serialize for GlobalType {
|
||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
||||
buffer.append_u8(self.value_type as u8);
|
||||
buffer.append_u8(self.is_mutable as u8);
|
||||
}
|
||||
}
|
||||
|
||||
/// Constant expression for initialising globals or data segments
|
||||
/// Note: This is restricted for simplicity, but the spec allows arbitrary constant expressions
|
||||
pub enum ConstExpr {
|
||||
I32(i32),
|
||||
I64(i64),
|
||||
F32(f32),
|
||||
F64(f64),
|
||||
}
|
||||
|
||||
impl Serialize for ConstExpr {
|
||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
||||
match self {
|
||||
ConstExpr::I32(x) => {
|
||||
buffer.append_u8(opcodes::I32CONST);
|
||||
buffer.encode_i32(*x);
|
||||
}
|
||||
ConstExpr::I64(x) => {
|
||||
buffer.append_u8(opcodes::I64CONST);
|
||||
buffer.encode_i64(*x);
|
||||
}
|
||||
ConstExpr::F32(x) => {
|
||||
buffer.append_u8(opcodes::F32CONST);
|
||||
buffer.encode_f32(*x);
|
||||
}
|
||||
ConstExpr::F64(x) => {
|
||||
buffer.append_u8(opcodes::F64CONST);
|
||||
buffer.encode_f64(*x);
|
||||
}
|
||||
}
|
||||
buffer.append_u8(opcodes::END);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Global {
|
||||
/// Type and mutability of the global
|
||||
pub ty: GlobalType,
|
||||
/// Initial value of the global.
|
||||
pub init: ConstExpr,
|
||||
}
|
||||
|
||||
impl Serialize for Global {
|
||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
||||
self.ty.serialize(buffer);
|
||||
self.init.serialize(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GlobalSection<'a> {
|
||||
pub entries: Vec<'a, Global>,
|
||||
}
|
||||
|
||||
impl<'a> GlobalSection<'a> {
|
||||
pub fn new(arena: &'a Bump) -> Self {
|
||||
GlobalSection {
|
||||
entries: Vec::with_capacity_in(1, arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Serialize for GlobalSection<'a> {
|
||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
||||
serialize_vector_section(buffer, SectionId::Global, &self.entries);
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************
|
||||
*
|
||||
* Export section
|
||||
*
|
||||
*******************************************************************/
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||
pub enum ExportType {
|
||||
Func = 0,
|
||||
Table = 1,
|
||||
Mem = 2,
|
||||
Global = 3,
|
||||
}
|
||||
|
||||
pub struct Export {
|
||||
pub name: String,
|
||||
pub ty: ExportType,
|
||||
pub index: u32,
|
||||
}
|
||||
impl Serialize for Export {
|
||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
||||
self.name.serialize(buffer);
|
||||
buffer.append_u8(self.ty as u8);
|
||||
buffer.encode_u32(self.index);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ExportSection<'a> {
|
||||
pub entries: Vec<'a, Export>,
|
||||
}
|
||||
|
||||
impl<'a> ExportSection<'a> {
|
||||
pub fn new(arena: &'a Bump) -> Self {
|
||||
ExportSection {
|
||||
entries: bumpalo::vec![in arena],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Serialize for ExportSection<'a> {
|
||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
||||
serialize_vector_section(buffer, SectionId::Export, &self.entries);
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************
|
||||
*
|
||||
* Code section (see also code_builder.rs)
|
||||
*
|
||||
*******************************************************************/
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CodeSection<'a> {
|
||||
pub code_builders: Vec<'a, CodeBuilder<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> CodeSection<'a> {
|
||||
pub fn new(arena: &'a Bump) -> Self {
|
||||
CodeSection {
|
||||
code_builders: Vec::with_capacity_in(8, arena),
|
||||
}
|
||||
}
|
||||
|
||||
/// Serialize the code builders for all functions, and get code relocations with final offsets
|
||||
pub fn serialize_with_relocs<T: SerialBuffer>(
|
||||
&self,
|
||||
buffer: &mut T,
|
||||
relocations: &mut Vec<'a, RelocationEntry>,
|
||||
) {
|
||||
let header_indices = write_section_header(buffer, SectionId::Code);
|
||||
buffer.encode_u32(self.code_builders.len() as u32);
|
||||
|
||||
for code_builder in self.code_builders.iter() {
|
||||
code_builder.serialize_with_relocs(buffer, relocations, header_indices.body_index);
|
||||
}
|
||||
|
||||
update_section_size(buffer, header_indices);
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************
|
||||
*
|
||||
* Data section
|
||||
*
|
||||
*******************************************************************/
|
||||
|
||||
pub enum DataMode {
|
||||
/// A data segment that auto-loads into memory on instantiation
|
||||
Active { offset: ConstExpr },
|
||||
/// A data segment that can be loaded with the `memory.init` instruction
|
||||
Passive,
|
||||
}
|
||||
|
||||
pub struct DataSegment<'a> {
|
||||
pub mode: DataMode,
|
||||
pub init: Vec<'a, u8>,
|
||||
}
|
||||
|
||||
impl Serialize for DataSegment<'_> {
|
||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
||||
match &self.mode {
|
||||
DataMode::Active { offset } => {
|
||||
buffer.append_u8(0);
|
||||
offset.serialize(buffer);
|
||||
}
|
||||
DataMode::Passive => {
|
||||
buffer.append_u8(1);
|
||||
}
|
||||
}
|
||||
|
||||
self.init.serialize(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DataSection<'a> {
|
||||
pub segments: Vec<'a, DataSegment<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> DataSection<'a> {
|
||||
pub fn new(arena: &'a Bump) -> Self {
|
||||
DataSection {
|
||||
segments: Vec::with_capacity_in(1, arena),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
self.segments.is_empty() || self.segments.iter().all(|seg| seg.init.is_empty())
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for DataSection<'_> {
|
||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
||||
if !self.is_empty() {
|
||||
serialize_vector_section(buffer, SectionId::Data, &self.segments);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************
|
||||
*
|
||||
* Data Count section
|
||||
*
|
||||
* Pre-declares the number of segments in the Data section.
|
||||
* This helps the runtime to validate the module in a single pass.
|
||||
* The order of sections is DataCount -> Code -> Data
|
||||
*
|
||||
*******************************************************************/
|
||||
|
||||
struct DataCountSection {
|
||||
count: u32,
|
||||
}
|
||||
|
||||
impl DataCountSection {
|
||||
fn new(data_section: &DataSection<'_>) -> Self {
|
||||
let count = data_section
|
||||
.segments
|
||||
.iter()
|
||||
.filter(|seg| !seg.init.is_empty())
|
||||
.count() as u32;
|
||||
DataCountSection { count }
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for DataCountSection {
|
||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
||||
if self.count > 0 {
|
||||
let header_indices = write_section_header(buffer, SectionId::DataCount);
|
||||
buffer.encode_u32(self.count);
|
||||
update_section_size(buffer, header_indices);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************
|
||||
*
|
||||
* Module
|
||||
*
|
||||
* https://webassembly.github.io/spec/core/binary/modules.html
|
||||
*
|
||||
*******************************************************************/
|
||||
|
||||
/// Helper struct to count non-empty sections.
|
||||
/// Needed to generate linking data, which refers to target sections by index.
|
||||
struct SectionCounter {
|
||||
buffer_size: usize,
|
||||
section_index: u32,
|
||||
}
|
||||
|
||||
impl SectionCounter {
|
||||
/// Update the section counter if buffer size increased since last call
|
||||
#[inline]
|
||||
fn update<SB: SerialBuffer>(&mut self, buffer: &mut SB) {
|
||||
let new_size = buffer.size();
|
||||
if new_size > self.buffer_size {
|
||||
self.section_index += 1;
|
||||
self.buffer_size = new_size;
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_and_count<SB: SerialBuffer, S: Serialize>(
|
||||
&mut self,
|
||||
buffer: &mut SB,
|
||||
section: &S,
|
||||
) {
|
||||
section.serialize(buffer);
|
||||
self.update(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WasmModule<'a> {
|
||||
pub types: TypeSection<'a>,
|
||||
pub import: ImportSection<'a>,
|
||||
pub function: FunctionSection<'a>,
|
||||
/// Dummy placeholder for tables (used for function pointers and host references)
|
||||
pub table: (),
|
||||
pub memory: MemorySection,
|
||||
pub global: GlobalSection<'a>,
|
||||
pub export: ExportSection<'a>,
|
||||
/// Dummy placeholder for start function. In Roc, this would be part of the platform.
|
||||
pub start: (),
|
||||
/// Dummy placeholder for table elements. Roc does not use tables.
|
||||
pub element: (),
|
||||
pub code: CodeSection<'a>,
|
||||
pub data: DataSection<'a>,
|
||||
pub linking: LinkingSection<'a>,
|
||||
pub reloc_code: RelocationSection<'a>,
|
||||
pub reloc_data: RelocationSection<'a>,
|
||||
}
|
||||
|
||||
impl<'a> WasmModule<'a> {
|
||||
pub const WASM_VERSION: u32 = 1;
|
||||
|
||||
/// Create entries in the Type and Function sections for a function signature
|
||||
pub fn add_function_signature(&mut self, signature: Signature<'a>) {
|
||||
let index = self.types.insert(signature);
|
||||
self.function.signature_indices.push(index);
|
||||
}
|
||||
|
||||
#[allow(clippy::unit_arg)]
|
||||
pub fn serialize<T: SerialBuffer>(&mut self, buffer: &mut T) {
|
||||
buffer.append_u8(0);
|
||||
buffer.append_slice("asm".as_bytes());
|
||||
buffer.write_unencoded_u32(Self::WASM_VERSION);
|
||||
|
||||
// Keep track of (non-empty) section indices for linking
|
||||
let mut counter = SectionCounter {
|
||||
buffer_size: buffer.size(),
|
||||
section_index: 0,
|
||||
};
|
||||
|
||||
counter.serialize_and_count(buffer, &self.types);
|
||||
counter.serialize_and_count(buffer, &self.import);
|
||||
counter.serialize_and_count(buffer, &self.function);
|
||||
counter.serialize_and_count(buffer, &self.table);
|
||||
counter.serialize_and_count(buffer, &self.memory);
|
||||
counter.serialize_and_count(buffer, &self.global);
|
||||
counter.serialize_and_count(buffer, &self.export);
|
||||
counter.serialize_and_count(buffer, &self.start);
|
||||
counter.serialize_and_count(buffer, &self.element);
|
||||
|
||||
// Data Count section forward-declares the size of the Data section
|
||||
// so that Code section can be validated in one pass
|
||||
let data_count_section = DataCountSection::new(&self.data);
|
||||
counter.serialize_and_count(buffer, &data_count_section);
|
||||
|
||||
// Code section mutates its linker relocation data during serialization
|
||||
let code_section_index = counter.section_index;
|
||||
self.code
|
||||
.serialize_with_relocs(buffer, &mut self.reloc_code.entries);
|
||||
counter.update(buffer);
|
||||
|
||||
// Data section is the last one before linking, so we can stop counting
|
||||
let data_section_index = counter.section_index;
|
||||
self.data.serialize(buffer);
|
||||
|
||||
self.linking.serialize(buffer);
|
||||
|
||||
self.reloc_code.target_section_index = Some(code_section_index);
|
||||
self.reloc_code.serialize(buffer);
|
||||
|
||||
self.reloc_data.target_section_index = Some(data_section_index);
|
||||
self.reloc_data.serialize(buffer);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,67 @@
|
|||
use std::fmt::Debug;
|
||||
|
||||
use bumpalo::collections::vec::Vec;
|
||||
|
||||
pub trait Serialize {
|
||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T);
|
||||
}
|
||||
|
||||
impl Serialize for str {
|
||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
||||
buffer.encode_u32(self.len() as u32);
|
||||
buffer.append_slice(self.as_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for u8 {
|
||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
||||
buffer.append_u8(*self);
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for u32 {
|
||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
||||
buffer.encode_u32(*self);
|
||||
}
|
||||
}
|
||||
|
||||
// Unit is used as a placeholder in parts of the Wasm spec we don't use yet
|
||||
impl Serialize for () {
|
||||
#[inline(always)]
|
||||
fn serialize<T: SerialBuffer>(&self, _buffer: &mut T) {}
|
||||
}
|
||||
|
||||
impl<S: Serialize> Serialize for [S] {
|
||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
||||
buffer.encode_u32(self.len() as u32);
|
||||
for item in self.iter() {
|
||||
item.serialize(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Vec<'_, u8> {
|
||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
||||
buffer.encode_u32(self.len() as u32);
|
||||
buffer.append_slice(self);
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Serialize> Serialize for Option<S> {
|
||||
/// serialize Option as a vector of length 1 or 0
|
||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
||||
match self {
|
||||
Some(x) => {
|
||||
buffer.append_u8(1);
|
||||
x.serialize(buffer);
|
||||
}
|
||||
None => {
|
||||
buffer.append_u8(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Write an unsigned integer into the provided buffer in LEB-128 format, returning byte length
|
||||
///
|
||||
/// All integers in Wasm are variable-length encoded, which saves space for small values.
|
||||
|
@ -10,10 +72,10 @@ macro_rules! encode_uleb128 {
|
|||
let mut x = value;
|
||||
let start_len = self.size();
|
||||
while x >= 0x80 {
|
||||
self.append_byte(0x80 | ((x & 0x7f) as u8));
|
||||
self.append_u8(0x80 | ((x & 0x7f) as u8));
|
||||
x >>= 7;
|
||||
}
|
||||
self.append_byte(x as u8);
|
||||
self.append_u8(x as u8);
|
||||
self.size() - start_len
|
||||
}
|
||||
};
|
||||
|
@ -30,10 +92,10 @@ macro_rules! encode_sleb128 {
|
|||
x >>= 7;
|
||||
let byte_is_negative = (byte & 0x40) != 0;
|
||||
if ((x == 0 && !byte_is_negative) || (x == -1 && byte_is_negative)) {
|
||||
self.append_byte(byte);
|
||||
self.append_u8(byte);
|
||||
break;
|
||||
}
|
||||
self.append_byte(byte | 0x80);
|
||||
self.append_u8(byte | 0x80);
|
||||
}
|
||||
self.size() - start_len
|
||||
}
|
||||
|
@ -47,7 +109,7 @@ macro_rules! write_unencoded {
|
|||
let mut x = value;
|
||||
let size = std::mem::size_of::<$ty>();
|
||||
for _ in 0..size {
|
||||
self.append_byte((x & 0xff) as u8);
|
||||
self.append_u8((x & 0xff) as u8);
|
||||
x >>= 8;
|
||||
}
|
||||
}
|
||||
|
@ -61,17 +123,19 @@ macro_rules! encode_padded_sleb128 {
|
|||
let mut x = value;
|
||||
let size = (std::mem::size_of::<$ty>() / 4) * 5;
|
||||
for _ in 0..(size - 1) {
|
||||
self.append_byte(0x80 | (x & 0x7f) as u8);
|
||||
self.append_u8(0x80 | (x & 0x7f) as u8);
|
||||
x >>= 7;
|
||||
}
|
||||
self.append_byte((x & 0x7f) as u8);
|
||||
self.append_u8((x & 0x7f) as u8);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub trait SerialBuffer {
|
||||
fn append_byte(&mut self, b: u8);
|
||||
pub trait SerialBuffer: Debug {
|
||||
fn append_u8(&mut self, b: u8);
|
||||
fn overwrite_u8(&mut self, index: usize, b: u8);
|
||||
fn append_slice(&mut self, b: &[u8]);
|
||||
|
||||
fn size(&self) -> usize;
|
||||
|
||||
encode_uleb128!(encode_u32, u32);
|
||||
|
@ -98,17 +162,6 @@ pub trait SerialBuffer {
|
|||
encode_padded_sleb128!(encode_padded_i64, i64);
|
||||
}
|
||||
|
||||
pub trait Serialize {
|
||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T);
|
||||
}
|
||||
|
||||
impl Serialize for str {
|
||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
||||
buffer.encode_u32(self.len() as u32);
|
||||
buffer.append_slice(self.as_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
fn overwrite_padded_u32_help(buffer: &mut [u8], value: u32) {
|
||||
let mut x = value;
|
||||
for byte in buffer.iter_mut().take(4) {
|
||||
|
@ -119,9 +172,12 @@ fn overwrite_padded_u32_help(buffer: &mut [u8], value: u32) {
|
|||
}
|
||||
|
||||
impl SerialBuffer for std::vec::Vec<u8> {
|
||||
fn append_byte(&mut self, b: u8) {
|
||||
fn append_u8(&mut self, b: u8) {
|
||||
self.push(b);
|
||||
}
|
||||
fn overwrite_u8(&mut self, index: usize, b: u8) {
|
||||
self[index] = b;
|
||||
}
|
||||
fn append_slice(&mut self, b: &[u8]) {
|
||||
self.extend_from_slice(b);
|
||||
}
|
||||
|
@ -146,9 +202,12 @@ impl SerialBuffer for std::vec::Vec<u8> {
|
|||
}
|
||||
|
||||
impl<'a> SerialBuffer for Vec<'a, u8> {
|
||||
fn append_byte(&mut self, b: u8) {
|
||||
fn append_u8(&mut self, b: u8) {
|
||||
self.push(b);
|
||||
}
|
||||
fn overwrite_u8(&mut self, index: usize, b: u8) {
|
||||
self[index] = b;
|
||||
}
|
||||
fn append_slice(&mut self, b: &[u8]) {
|
||||
self.extend_from_slice(b);
|
||||
}
|
|
@ -21,15 +21,13 @@ roc_mono = { path = "../mono" }
|
|||
roc_reporting = { path = "../reporting" }
|
||||
morphic_lib = { path = "../../vendor/morphic_lib" }
|
||||
ven_pretty = { path = "../../vendor/pretty" }
|
||||
bumpalo = { version = "3.6.1", features = ["collections"] }
|
||||
parking_lot = { version = "0.11", features = ["deadlock_detection"] }
|
||||
crossbeam = "0.7"
|
||||
num_cpus = "1"
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
parking_lot = { version = "0.11.2", features = ["deadlock_detection"] }
|
||||
crossbeam = "0.8.1"
|
||||
num_cpus = "1.13.0"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.1.0"
|
||||
pretty_assertions = "0.5.1"
|
||||
maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
quickcheck_macros = "0.8"
|
||||
tempfile = "3.2.0"
|
||||
pretty_assertions = "1.0.0"
|
||||
maplit = "1.0.2"
|
||||
indoc = "1.0.3"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use roc_can::annotation::IntroducedVariables;
|
||||
use roc_can::def::{Declaration, Def};
|
||||
use roc_can::env::Env;
|
||||
use roc_can::expr::{Expr, Recursive};
|
||||
use roc_can::expr::{ClosureData, Expr, Recursive};
|
||||
use roc_can::pattern::Pattern;
|
||||
use roc_can::scope::Scope;
|
||||
use roc_collections::all::{MutSet, SendMap};
|
||||
|
@ -117,7 +117,7 @@ fn build_effect_always(
|
|||
|
||||
let body = Expr::Var(value_symbol);
|
||||
|
||||
Expr::Closure {
|
||||
Expr::Closure(ClosureData {
|
||||
function_type: var_store.fresh(),
|
||||
closure_type: var_store.fresh(),
|
||||
closure_ext_var: var_store.fresh(),
|
||||
|
@ -127,7 +127,7 @@ fn build_effect_always(
|
|||
recursive: Recursive::NotRecursive,
|
||||
arguments,
|
||||
loc_body: Box::new(Located::at_zero(body)),
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
// \value -> @Effect \{} -> value
|
||||
|
@ -146,7 +146,7 @@ fn build_effect_always(
|
|||
)];
|
||||
|
||||
let function_var = var_store.fresh();
|
||||
let closure = Expr::Closure {
|
||||
let closure = Expr::Closure(ClosureData {
|
||||
function_type: function_var,
|
||||
closure_type: var_store.fresh(),
|
||||
closure_ext_var: var_store.fresh(),
|
||||
|
@ -156,7 +156,7 @@ fn build_effect_always(
|
|||
recursive: Recursive::NotRecursive,
|
||||
arguments,
|
||||
loc_body: Box::new(Located::at_zero(body)),
|
||||
};
|
||||
});
|
||||
|
||||
(function_var, closure)
|
||||
};
|
||||
|
@ -295,7 +295,7 @@ fn build_effect_map(
|
|||
Located::at_zero(empty_record_pattern(var_store)),
|
||||
)];
|
||||
|
||||
Expr::Closure {
|
||||
Expr::Closure(ClosureData {
|
||||
function_type: var_store.fresh(),
|
||||
closure_type: var_store.fresh(),
|
||||
closure_ext_var: var_store.fresh(),
|
||||
|
@ -308,7 +308,7 @@ fn build_effect_map(
|
|||
recursive: Recursive::NotRecursive,
|
||||
arguments,
|
||||
loc_body: Box::new(Located::at_zero(mapper_call)),
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
let arguments = vec![
|
||||
|
@ -339,7 +339,7 @@ fn build_effect_map(
|
|||
};
|
||||
|
||||
let function_var = var_store.fresh();
|
||||
let map_closure = Expr::Closure {
|
||||
let map_closure = Expr::Closure(ClosureData {
|
||||
function_type: function_var,
|
||||
closure_type: var_store.fresh(),
|
||||
closure_ext_var: var_store.fresh(),
|
||||
|
@ -349,7 +349,7 @@ fn build_effect_map(
|
|||
recursive: Recursive::NotRecursive,
|
||||
arguments,
|
||||
loc_body: Box::new(Located::at_zero(body)),
|
||||
};
|
||||
});
|
||||
|
||||
let mut introduced_variables = IntroducedVariables::default();
|
||||
|
||||
|
@ -509,7 +509,7 @@ fn build_effect_after(
|
|||
];
|
||||
|
||||
let function_var = var_store.fresh();
|
||||
let after_closure = Expr::Closure {
|
||||
let after_closure = Expr::Closure(ClosureData {
|
||||
function_type: function_var,
|
||||
closure_type: var_store.fresh(),
|
||||
closure_ext_var: var_store.fresh(),
|
||||
|
@ -519,7 +519,7 @@ fn build_effect_after(
|
|||
recursive: Recursive::NotRecursive,
|
||||
arguments,
|
||||
loc_body: Box::new(Located::at_zero(to_effect_call)),
|
||||
};
|
||||
});
|
||||
|
||||
let mut introduced_variables = IntroducedVariables::default();
|
||||
|
||||
|
@ -653,7 +653,7 @@ pub fn build_host_exposed_def(
|
|||
.unwrap()
|
||||
};
|
||||
|
||||
let effect_closure = Expr::Closure {
|
||||
let effect_closure = Expr::Closure(ClosureData {
|
||||
function_type: var_store.fresh(),
|
||||
closure_type: var_store.fresh(),
|
||||
closure_ext_var: var_store.fresh(),
|
||||
|
@ -666,7 +666,7 @@ pub fn build_host_exposed_def(
|
|||
Located::at_zero(empty_record_pattern(var_store)),
|
||||
)],
|
||||
loc_body: Box::new(Located::at_zero(low_level_call)),
|
||||
};
|
||||
});
|
||||
|
||||
let body = Expr::Tag {
|
||||
variant_var: var_store.fresh(),
|
||||
|
@ -675,7 +675,7 @@ pub fn build_host_exposed_def(
|
|||
arguments: vec![(var_store.fresh(), Located::at_zero(effect_closure))],
|
||||
};
|
||||
|
||||
Expr::Closure {
|
||||
Expr::Closure(ClosureData {
|
||||
function_type: var_store.fresh(),
|
||||
closure_type: var_store.fresh(),
|
||||
closure_ext_var: var_store.fresh(),
|
||||
|
@ -685,7 +685,7 @@ pub fn build_host_exposed_def(
|
|||
recursive: Recursive::NotRecursive,
|
||||
arguments,
|
||||
loc_body: Box::new(Located::at_zero(body)),
|
||||
}
|
||||
})
|
||||
}
|
||||
_ => {
|
||||
// not a function
|
||||
|
@ -717,7 +717,7 @@ pub fn build_host_exposed_def(
|
|||
destructs: vec![],
|
||||
};
|
||||
|
||||
let effect_closure = Expr::Closure {
|
||||
let effect_closure = Expr::Closure(ClosureData {
|
||||
function_type: var_store.fresh(),
|
||||
closure_type: var_store.fresh(),
|
||||
closure_ext_var: var_store.fresh(),
|
||||
|
@ -727,7 +727,7 @@ pub fn build_host_exposed_def(
|
|||
recursive: Recursive::NotRecursive,
|
||||
arguments: vec![(var_store.fresh(), Located::at_zero(empty_record_pattern))],
|
||||
loc_body: Box::new(Located::at_zero(low_level_call)),
|
||||
};
|
||||
});
|
||||
|
||||
Expr::Tag {
|
||||
variant_var: var_store.fresh(),
|
||||
|
|
|
@ -3958,7 +3958,10 @@ fn make_specializations<'a>(
|
|||
|
||||
let mut procs = Procs::new_in(arena);
|
||||
|
||||
procs.partial_procs = procs_base.partial_procs;
|
||||
for (symbol, partial_proc) in procs_base.partial_procs.into_iter() {
|
||||
procs.partial_procs.insert(symbol, partial_proc);
|
||||
}
|
||||
|
||||
procs.module_thunks = procs_base.module_thunks;
|
||||
procs.runtime_errors = procs_base.runtime_errors;
|
||||
procs.imported_module_thunks = procs_base.imported_module_thunks;
|
||||
|
@ -4126,6 +4129,7 @@ fn add_def_to_module<'a>(
|
|||
exposed_to_host: &MutMap<Symbol, Variable>,
|
||||
is_recursive: bool,
|
||||
) {
|
||||
use roc_can::expr::ClosureData;
|
||||
use roc_can::expr::Expr::*;
|
||||
use roc_can::pattern::Pattern::*;
|
||||
|
||||
|
@ -4134,14 +4138,14 @@ fn add_def_to_module<'a>(
|
|||
let is_exposed = exposed_to_host.contains_key(&symbol);
|
||||
|
||||
match def.loc_expr.value {
|
||||
Closure {
|
||||
Closure(ClosureData {
|
||||
function_type: annotation,
|
||||
return_type: ret_var,
|
||||
arguments: loc_args,
|
||||
loc_body,
|
||||
captured_symbols,
|
||||
..
|
||||
} => {
|
||||
}) => {
|
||||
// this is a top-level definition, it should not capture anything
|
||||
debug_assert!(captured_symbols.is_empty());
|
||||
|
||||
|
|
|
@ -9,12 +9,7 @@ license = "UPL-1.0"
|
|||
roc_region = { path = "../region" }
|
||||
roc_ident = { path = "../ident" }
|
||||
roc_collections = { path = "../collections" }
|
||||
bumpalo = { version = "3.6.1", features = ["collections"] }
|
||||
lazy_static = "1.4"
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
lazy_static = "1.4.0"
|
||||
static_assertions = "1.1.0"
|
||||
snafu = { version = "0.6", features = ["backtraces"] }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.5.1"
|
||||
maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
snafu = { version = "0.6.10", features = ["backtraces"] }
|
||||
|
|
|
@ -1061,6 +1061,7 @@ define_builtins! {
|
|||
38 LIST_MAX: "max"
|
||||
39 LIST_MAX_GT: "#maxGt"
|
||||
40 LIST_MAP4: "map4"
|
||||
41 LIST_DROP_FIRST: "dropFirst"
|
||||
}
|
||||
5 RESULT: "Result" => {
|
||||
0 RESULT_RESULT: "Result" imported // the Result.Result type alias
|
||||
|
|
|
@ -17,19 +17,13 @@ roc_std = { path = "../../roc_std" }
|
|||
roc_problem = { path = "../problem" }
|
||||
ven_pretty = { path = "../../vendor/pretty" }
|
||||
morphic_lib = { path = "../../vendor/morphic_lib" }
|
||||
bumpalo = { version = "3.6.1", features = ["collections"] }
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
hashbrown = { version = "0.11.2", features = [ "bumpalo" ] }
|
||||
ven_ena = { path = "../../vendor/ena" }
|
||||
ven_graph = { path = "../../vendor/pathfinding" }
|
||||
linked-hash-map = "0.5.4"
|
||||
|
||||
[dev-dependencies]
|
||||
roc_load= { path = "../load" }
|
||||
roc_builtins = { path = "../builtins" }
|
||||
roc_parse = { path = "../parse" }
|
||||
roc_solve = { path = "../solve" }
|
||||
pretty_assertions = "0.5.1"
|
||||
maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
quickcheck_macros = "0.8"
|
||||
|
|
|
@ -9,6 +9,7 @@ use crate::layout::{
|
|||
use bumpalo::collections::Vec;
|
||||
use bumpalo::Bump;
|
||||
use hashbrown::hash_map::Entry;
|
||||
use roc_can::expr::ClosureData;
|
||||
use roc_collections::all::{default_hasher, BumpMap, BumpMapDefault, MutMap};
|
||||
use roc_module::ident::{ForeignSymbol, Lowercase, TagName};
|
||||
use roc_module::low_level::LowLevel;
|
||||
|
@ -71,6 +72,61 @@ pub struct EntryPoint<'a> {
|
|||
pub layout: ProcLayout<'a>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct PartialProcId(usize);
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct PartialProcs<'a> {
|
||||
/// maps a function name (symbol) to an index
|
||||
symbols: Vec<'a, Symbol>,
|
||||
|
||||
partial_procs: Vec<'a, PartialProc<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> PartialProcs<'a> {
|
||||
fn new_in(arena: &'a Bump) -> Self {
|
||||
Self {
|
||||
symbols: Vec::new_in(arena),
|
||||
partial_procs: Vec::new_in(arena),
|
||||
}
|
||||
}
|
||||
fn contains_key(&self, symbol: Symbol) -> bool {
|
||||
self.symbol_to_id(symbol).is_some()
|
||||
}
|
||||
|
||||
fn symbol_to_id(&self, symbol: Symbol) -> Option<PartialProcId> {
|
||||
self.symbols
|
||||
.iter()
|
||||
.position(|s| *s == symbol)
|
||||
.map(PartialProcId)
|
||||
}
|
||||
|
||||
fn get_symbol(&self, symbol: Symbol) -> Option<&PartialProc<'a>> {
|
||||
let id = self.symbol_to_id(symbol)?;
|
||||
|
||||
Some(self.get_id(id))
|
||||
}
|
||||
|
||||
fn get_id(&self, id: PartialProcId) -> &PartialProc<'a> {
|
||||
&self.partial_procs[id.0]
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, symbol: Symbol, partial_proc: PartialProc<'a>) -> PartialProcId {
|
||||
debug_assert!(
|
||||
!self.contains_key(symbol),
|
||||
"The {:?} is inserted as a partial proc twice: that's a bug!",
|
||||
symbol,
|
||||
);
|
||||
|
||||
let id = PartialProcId(self.symbols.len());
|
||||
|
||||
self.symbols.push(symbol);
|
||||
self.partial_procs.push(partial_proc);
|
||||
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct PartialProc<'a> {
|
||||
pub annotation: Variable,
|
||||
|
@ -129,7 +185,7 @@ impl<'a> PartialProc<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum CapturedSymbols<'a> {
|
||||
None,
|
||||
Captured(&'a [(Symbol, Variable)]),
|
||||
|
@ -418,7 +474,7 @@ impl<'a> ExternalSpecializations<'a> {
|
|||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Procs<'a> {
|
||||
pub partial_procs: BumpMap<Symbol, PartialProc<'a>>,
|
||||
pub partial_procs: PartialProcs<'a>,
|
||||
pub imported_module_thunks: &'a [Symbol],
|
||||
pub module_thunks: &'a [Symbol],
|
||||
pub pending_specializations:
|
||||
|
@ -432,7 +488,7 @@ pub struct Procs<'a> {
|
|||
impl<'a> Procs<'a> {
|
||||
pub fn new_in(arena: &'a Bump) -> Self {
|
||||
Self {
|
||||
partial_procs: BumpMap::new_in(arena),
|
||||
partial_procs: PartialProcs::new_in(arena),
|
||||
imported_module_thunks: &[],
|
||||
module_thunks: &[],
|
||||
pending_specializations: Some(BumpMap::new_in(arena)),
|
||||
|
@ -459,6 +515,10 @@ impl<'a> Procs<'a> {
|
|||
self.module_thunks.iter().any(|x| *x == symbol)
|
||||
}
|
||||
|
||||
fn get_partial_proc<'b>(&'b self, symbol: Symbol) -> Option<&'b PartialProc<'a>> {
|
||||
self.partial_procs.get_symbol(symbol)
|
||||
}
|
||||
|
||||
pub fn get_specialized_procs_without_rc(
|
||||
self,
|
||||
env: &mut Env<'a, '_>,
|
||||
|
@ -535,9 +595,9 @@ impl<'a> Procs<'a> {
|
|||
// register the pending specialization, so this gets code genned later
|
||||
add_pending(pending_specializations, symbol, layout, pending);
|
||||
|
||||
match self.partial_procs.entry(symbol) {
|
||||
Entry::Occupied(occupied) => {
|
||||
let existing = occupied.get();
|
||||
match self.partial_procs.symbol_to_id(symbol) {
|
||||
Some(occupied) => {
|
||||
let existing = self.partial_procs.get_id(occupied);
|
||||
// if we're adding the same partial proc twice, they must be the actual same!
|
||||
//
|
||||
// NOTE we can't skip extra work! we still need to make the specialization for this
|
||||
|
@ -549,7 +609,7 @@ impl<'a> Procs<'a> {
|
|||
|
||||
// the partial proc is already in there, do nothing
|
||||
}
|
||||
Entry::Vacant(vacant) => {
|
||||
None => {
|
||||
let pattern_symbols = pattern_symbols.into_bump_slice();
|
||||
|
||||
let partial_proc = PartialProc {
|
||||
|
@ -560,7 +620,7 @@ impl<'a> Procs<'a> {
|
|||
is_self_recursive,
|
||||
};
|
||||
|
||||
vacant.insert(partial_proc);
|
||||
self.partial_procs.insert(symbol, partial_proc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -572,8 +632,10 @@ impl<'a> Procs<'a> {
|
|||
|
||||
let outside_layout = layout;
|
||||
|
||||
let partial_proc;
|
||||
if let Some(existing) = self.partial_procs.get(&symbol) {
|
||||
let partial_proc_id = if let Some(partial_proc_id) =
|
||||
self.partial_procs.symbol_to_id(symbol)
|
||||
{
|
||||
let existing = self.partial_procs.get_id(partial_proc_id);
|
||||
// if we're adding the same partial proc twice, they must be the actual same!
|
||||
//
|
||||
// NOTE we can't skip extra work! we still need to make the specialization for this
|
||||
|
@ -583,21 +645,29 @@ impl<'a> Procs<'a> {
|
|||
debug_assert_eq!(captured_symbols, existing.captured_symbols);
|
||||
debug_assert_eq!(is_self_recursive, existing.is_self_recursive);
|
||||
|
||||
partial_proc = existing.clone();
|
||||
partial_proc_id
|
||||
} else {
|
||||
let pattern_symbols = pattern_symbols.into_bump_slice();
|
||||
|
||||
partial_proc = PartialProc {
|
||||
let partial_proc = PartialProc {
|
||||
annotation,
|
||||
pattern_symbols,
|
||||
captured_symbols,
|
||||
body: body.value,
|
||||
is_self_recursive,
|
||||
};
|
||||
}
|
||||
|
||||
match specialize(env, self, symbol, layout_cache, pending, partial_proc)
|
||||
{
|
||||
self.partial_procs.insert(symbol, partial_proc)
|
||||
};
|
||||
|
||||
match specialize(
|
||||
env,
|
||||
self,
|
||||
symbol,
|
||||
layout_cache,
|
||||
pending,
|
||||
partial_proc_id,
|
||||
) {
|
||||
Ok((proc, layout)) => {
|
||||
let top_level = ProcLayout::from_raw(env.arena, layout);
|
||||
|
||||
|
@ -662,9 +732,8 @@ impl<'a> Procs<'a> {
|
|||
None => {
|
||||
let symbol = name;
|
||||
|
||||
// TODO should pending_procs hold a Rc<Proc>?
|
||||
let partial_proc = match self.partial_procs.get(&symbol) {
|
||||
Some(p) => p.clone(),
|
||||
let partial_proc_id = match self.partial_procs.symbol_to_id(symbol) {
|
||||
Some(p) => p,
|
||||
None => panic!("no partial_proc for {:?} in module {:?}", symbol, env.home),
|
||||
};
|
||||
|
||||
|
@ -691,7 +760,7 @@ impl<'a> Procs<'a> {
|
|||
layout_cache,
|
||||
fn_var,
|
||||
Default::default(),
|
||||
partial_proc,
|
||||
partial_proc_id,
|
||||
) {
|
||||
Ok((proc, _ignore_layout)) => {
|
||||
// the `layout` is a function pointer, while `_ignore_layout` can be a
|
||||
|
@ -1726,14 +1795,14 @@ pub fn specialize_all<'a>(
|
|||
continue;
|
||||
}
|
||||
Entry::Vacant(vacant) => {
|
||||
match procs.partial_procs.get(&name) {
|
||||
match procs.partial_procs.symbol_to_id(name) {
|
||||
Some(v) => {
|
||||
// Mark this proc as in-progress, so if we're dealing with
|
||||
// mutually recursive functions, we don't loop forever.
|
||||
// (We had a bug around this before this system existed!)
|
||||
vacant.insert(InProgress);
|
||||
|
||||
v.clone()
|
||||
v
|
||||
}
|
||||
None => {
|
||||
// TODO this assumes the specialization is done by another module
|
||||
|
@ -1808,8 +1877,8 @@ fn specialize_externals_others_need<'a>(
|
|||
|
||||
let name = *symbol;
|
||||
|
||||
let partial_proc = match procs.partial_procs.get(&name) {
|
||||
Some(v) => v.clone(),
|
||||
let partial_proc_id = match procs.partial_procs.symbol_to_id(name) {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
panic!("Cannot find a partial proc for {:?}", name);
|
||||
}
|
||||
|
@ -1823,7 +1892,7 @@ fn specialize_externals_others_need<'a>(
|
|||
layout_cache,
|
||||
solved_type,
|
||||
BumpMap::new_in(env.arena),
|
||||
partial_proc,
|
||||
partial_proc_id,
|
||||
) {
|
||||
Ok((proc, layout)) => {
|
||||
let top_level = ProcLayout::from_raw(env.arena, layout);
|
||||
|
@ -1901,32 +1970,28 @@ fn specialize_external<'a>(
|
|||
layout_cache: &mut LayoutCache<'a>,
|
||||
fn_var: Variable,
|
||||
host_exposed_variables: &[(Symbol, Variable)],
|
||||
partial_proc: PartialProc<'a>,
|
||||
partial_proc_id: PartialProcId,
|
||||
) -> Result<Proc<'a>, LayoutProblem> {
|
||||
let PartialProc {
|
||||
annotation,
|
||||
pattern_symbols,
|
||||
captured_symbols,
|
||||
body,
|
||||
is_self_recursive,
|
||||
} = partial_proc;
|
||||
let partial_proc = procs.partial_procs.get_id(partial_proc_id);
|
||||
let captured_symbols = partial_proc.captured_symbols;
|
||||
|
||||
// unify the called function with the specialized signature, then specialize the function body
|
||||
let snapshot = env.subs.snapshot();
|
||||
let cache_snapshot = layout_cache.snapshot();
|
||||
|
||||
let _unified = roc_unify::unify::unify(env.subs, annotation, fn_var);
|
||||
let _unified = roc_unify::unify::unify(env.subs, partial_proc.annotation, fn_var);
|
||||
|
||||
// This will not hold for programs with type errors
|
||||
// let is_valid = matches!(unified, roc_unify::unify::Unified::Success(_));
|
||||
// debug_assert!(is_valid, "unificaton failure for {:?}", proc_name);
|
||||
|
||||
// if this is a closure, add the closure record argument
|
||||
let pattern_symbols = match captured_symbols {
|
||||
CapturedSymbols::None => pattern_symbols,
|
||||
CapturedSymbols::Captured([]) => pattern_symbols,
|
||||
let pattern_symbols = match partial_proc.captured_symbols {
|
||||
CapturedSymbols::None => partial_proc.pattern_symbols,
|
||||
CapturedSymbols::Captured([]) => partial_proc.pattern_symbols,
|
||||
CapturedSymbols::Captured(_) => {
|
||||
let mut temp = Vec::from_iter_in(pattern_symbols.iter().copied(), env.arena);
|
||||
let mut temp =
|
||||
Vec::from_iter_in(partial_proc.pattern_symbols.iter().copied(), env.arena);
|
||||
temp.push(Symbol::ARG_CLOSURE);
|
||||
temp.into_bump_slice()
|
||||
}
|
||||
|
@ -2023,12 +2088,13 @@ fn specialize_external<'a>(
|
|||
}
|
||||
};
|
||||
|
||||
let recursivity = if is_self_recursive {
|
||||
let recursivity = if partial_proc.is_self_recursive {
|
||||
SelfRecursive::SelfRecursive(JoinPointId(env.unique_symbol()))
|
||||
} else {
|
||||
SelfRecursive::NotSelfRecursive
|
||||
};
|
||||
|
||||
let body = partial_proc.body.clone();
|
||||
let mut specialized_body = from_can(env, fn_var, body, procs, layout_cache);
|
||||
|
||||
match specialized {
|
||||
|
@ -2419,13 +2485,13 @@ struct SpecializeFailure<'a> {
|
|||
|
||||
type SpecializeSuccess<'a> = (Proc<'a>, RawFunctionLayout<'a>);
|
||||
|
||||
fn specialize<'a>(
|
||||
fn specialize<'a, 'b>(
|
||||
env: &mut Env<'a, '_>,
|
||||
procs: &mut Procs<'a>,
|
||||
procs: &'b mut Procs<'a>,
|
||||
proc_name: Symbol,
|
||||
layout_cache: &mut LayoutCache<'a>,
|
||||
pending: PendingSpecialization,
|
||||
partial_proc: PartialProc<'a>,
|
||||
partial_proc_id: PartialProcId,
|
||||
) -> Result<SpecializeSuccess<'a>, SpecializeFailure<'a>> {
|
||||
let PendingSpecialization {
|
||||
solved_type,
|
||||
|
@ -2440,7 +2506,7 @@ fn specialize<'a>(
|
|||
layout_cache,
|
||||
&solved_type,
|
||||
host_exposed_aliases,
|
||||
partial_proc,
|
||||
partial_proc_id,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -2470,7 +2536,7 @@ fn specialize_solved_type<'a>(
|
|||
layout_cache: &mut LayoutCache<'a>,
|
||||
solved_type: &SolvedType,
|
||||
host_exposed_aliases: BumpMap<Symbol, SolvedType>,
|
||||
partial_proc: PartialProc<'a>,
|
||||
partial_proc_id: PartialProcId,
|
||||
) -> Result<SpecializeSuccess<'a>, SpecializeFailure<'a>> {
|
||||
specialize_variable_help(
|
||||
env,
|
||||
|
@ -2479,7 +2545,7 @@ fn specialize_solved_type<'a>(
|
|||
layout_cache,
|
||||
|env| introduce_solved_type_to_subs(env, solved_type),
|
||||
host_exposed_aliases,
|
||||
partial_proc,
|
||||
partial_proc_id,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -2490,7 +2556,7 @@ fn specialize_variable<'a>(
|
|||
layout_cache: &mut LayoutCache<'a>,
|
||||
fn_var: Variable,
|
||||
host_exposed_aliases: BumpMap<Symbol, SolvedType>,
|
||||
partial_proc: PartialProc<'a>,
|
||||
partial_proc_id: PartialProcId,
|
||||
) -> Result<SpecializeSuccess<'a>, SpecializeFailure<'a>> {
|
||||
specialize_variable_help(
|
||||
env,
|
||||
|
@ -2499,7 +2565,7 @@ fn specialize_variable<'a>(
|
|||
layout_cache,
|
||||
|_| fn_var,
|
||||
host_exposed_aliases,
|
||||
partial_proc,
|
||||
partial_proc_id,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -2510,7 +2576,7 @@ fn specialize_variable_help<'a, F>(
|
|||
layout_cache: &mut LayoutCache<'a>,
|
||||
fn_var_thunk: F,
|
||||
host_exposed_aliases: BumpMap<Symbol, SolvedType>,
|
||||
partial_proc: PartialProc<'a>,
|
||||
partial_proc_id: PartialProcId,
|
||||
) -> Result<SpecializeSuccess<'a>, SpecializeFailure<'a>>
|
||||
where
|
||||
F: FnOnce(&mut Env<'a, '_>) -> Variable,
|
||||
|
@ -2541,7 +2607,8 @@ where
|
|||
};
|
||||
|
||||
// make sure rigid variables in the annotation are converted to flex variables
|
||||
instantiate_rigids(env.subs, partial_proc.annotation);
|
||||
let annotation_var = procs.partial_procs.get_id(partial_proc_id).annotation;
|
||||
instantiate_rigids(env.subs, annotation_var);
|
||||
|
||||
let mut host_exposed_variables = Vec::with_capacity_in(host_exposed_aliases.len(), env.arena);
|
||||
|
||||
|
@ -2558,7 +2625,7 @@ where
|
|||
layout_cache,
|
||||
fn_var,
|
||||
&host_exposed_variables,
|
||||
partial_proc,
|
||||
partial_proc_id,
|
||||
);
|
||||
|
||||
match specialized {
|
||||
|
@ -2633,7 +2700,7 @@ fn specialize_naked_symbol<'a>(
|
|||
symbol: Symbol,
|
||||
) -> Stmt<'a> {
|
||||
if procs.is_module_thunk(symbol) {
|
||||
let partial_proc = procs.partial_procs.get(&symbol).unwrap();
|
||||
let partial_proc = procs.get_partial_proc(symbol).unwrap();
|
||||
let fn_var = partial_proc.annotation;
|
||||
|
||||
// This is a top-level declaration, which will code gen to a 0-arity thunk.
|
||||
|
@ -2847,42 +2914,9 @@ pub fn with_hole<'a>(
|
|||
}
|
||||
}
|
||||
LetNonRec(def, cont, _) => {
|
||||
if let roc_can::pattern::Pattern::Identifier(symbol) = &def.loc_pattern.value {
|
||||
if let Closure {
|
||||
function_type,
|
||||
return_type,
|
||||
recursive,
|
||||
arguments,
|
||||
loc_body: boxed_body,
|
||||
captured_symbols,
|
||||
..
|
||||
} = def.loc_expr.value
|
||||
{
|
||||
// Extract Procs, but discard the resulting Expr::Load.
|
||||
// That Load looks up the pointer, which we won't use here!
|
||||
|
||||
let loc_body = *boxed_body;
|
||||
|
||||
let is_self_recursive =
|
||||
!matches!(recursive, roc_can::expr::Recursive::NotRecursive);
|
||||
|
||||
// this should be a top-level declaration, and hence have no captured symbols
|
||||
// if we ever do hit this (and it's not a bug), we should make sure to put the
|
||||
// captured symbols into a CapturedSymbols and give it to PartialProc::from_named_function
|
||||
debug_assert!(captured_symbols.is_empty());
|
||||
|
||||
let partial_proc = PartialProc::from_named_function(
|
||||
env,
|
||||
layout_cache,
|
||||
function_type,
|
||||
arguments,
|
||||
loc_body,
|
||||
CapturedSymbols::None,
|
||||
is_self_recursive,
|
||||
return_type,
|
||||
);
|
||||
|
||||
procs.partial_procs.insert(*symbol, partial_proc);
|
||||
if let roc_can::pattern::Pattern::Identifier(symbol) = def.loc_pattern.value {
|
||||
if let Closure(closure_data) = def.loc_expr.value {
|
||||
register_noncapturing_closure(env, procs, layout_cache, symbol, closure_data);
|
||||
|
||||
return with_hole(
|
||||
env,
|
||||
|
@ -2894,9 +2928,6 @@ pub fn with_hole<'a>(
|
|||
hole,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if let roc_can::pattern::Pattern::Identifier(symbol) = def.loc_pattern.value {
|
||||
// special-case the form `let x = E in x`
|
||||
// not doing so will drop the `hole`
|
||||
match &cont.value {
|
||||
|
@ -3024,36 +3055,15 @@ pub fn with_hole<'a>(
|
|||
// because Roc is strict, only functions can be recursive!
|
||||
for def in defs.into_iter() {
|
||||
if let roc_can::pattern::Pattern::Identifier(symbol) = &def.loc_pattern.value {
|
||||
if let Closure {
|
||||
function_type,
|
||||
return_type,
|
||||
recursive,
|
||||
arguments,
|
||||
loc_body: boxed_body,
|
||||
..
|
||||
} = def.loc_expr.value
|
||||
{
|
||||
// Extract Procs, but discard the resulting Expr::Load.
|
||||
// That Load looks up the pointer, which we won't use here!
|
||||
|
||||
let loc_body = *boxed_body;
|
||||
|
||||
let is_self_recursive =
|
||||
!matches!(recursive, roc_can::expr::Recursive::NotRecursive);
|
||||
|
||||
let partial_proc = PartialProc::from_named_function(
|
||||
if let Closure(closure_data) = def.loc_expr.value {
|
||||
register_noncapturing_closure(
|
||||
env,
|
||||
procs,
|
||||
layout_cache,
|
||||
function_type,
|
||||
arguments,
|
||||
loc_body,
|
||||
CapturedSymbols::None,
|
||||
is_self_recursive,
|
||||
return_type,
|
||||
*symbol,
|
||||
closure_data,
|
||||
);
|
||||
|
||||
procs.partial_procs.insert(*symbol, partial_proc);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
@ -3749,7 +3759,7 @@ pub fn with_hole<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
Closure {
|
||||
Closure(ClosureData {
|
||||
function_type,
|
||||
return_type,
|
||||
name,
|
||||
|
@ -3757,7 +3767,7 @@ pub fn with_hole<'a>(
|
|||
captured_symbols,
|
||||
loc_body: boxed_body,
|
||||
..
|
||||
} => {
|
||||
}) => {
|
||||
let loc_body = *boxed_body;
|
||||
|
||||
let raw = layout_cache.raw_from_var(env.arena, function_type, env.subs);
|
||||
|
@ -3814,11 +3824,11 @@ pub fn with_hole<'a>(
|
|||
// if it's in there, it's a call by name, otherwise it's a call by pointer
|
||||
let is_known = |key| {
|
||||
// a proc in this module, or an imported symbol
|
||||
procs.partial_procs.contains_key(key) || env.is_imported_symbol(*key)
|
||||
procs.partial_procs.contains_key(key) || env.is_imported_symbol(key)
|
||||
};
|
||||
|
||||
match loc_expr.value {
|
||||
roc_can::expr::Expr::Var(proc_name) if is_known(&proc_name) => {
|
||||
roc_can::expr::Expr::Var(proc_name) if is_known(proc_name) => {
|
||||
// a call by a known name
|
||||
call_by_name(
|
||||
env,
|
||||
|
@ -4633,6 +4643,131 @@ fn sorted_field_symbols<'a>(
|
|||
field_symbols_temp
|
||||
}
|
||||
|
||||
/// Insert a closure that does capture symbols (because it is top-level) to the list of partial procs
|
||||
fn register_noncapturing_closure<'a>(
|
||||
env: &mut Env<'a, '_>,
|
||||
procs: &mut Procs<'a>,
|
||||
layout_cache: &mut LayoutCache<'a>,
|
||||
closure_name: Symbol,
|
||||
closure_data: ClosureData,
|
||||
) {
|
||||
let ClosureData {
|
||||
function_type,
|
||||
return_type,
|
||||
recursive,
|
||||
arguments,
|
||||
loc_body: boxed_body,
|
||||
captured_symbols,
|
||||
..
|
||||
} = closure_data;
|
||||
|
||||
// Extract Procs, but discard the resulting Expr::Load.
|
||||
// That Load looks up the pointer, which we won't use here!
|
||||
|
||||
let loc_body = *boxed_body;
|
||||
|
||||
let is_self_recursive = !matches!(recursive, roc_can::expr::Recursive::NotRecursive);
|
||||
|
||||
// this should be a top-level declaration, and hence have no captured symbols
|
||||
// if we ever do hit this (and it's not a bug), we should make sure to put the
|
||||
// captured symbols into a CapturedSymbols and give it to PartialProc::from_named_function
|
||||
debug_assert!(captured_symbols.is_empty());
|
||||
|
||||
let partial_proc = PartialProc::from_named_function(
|
||||
env,
|
||||
layout_cache,
|
||||
function_type,
|
||||
arguments,
|
||||
loc_body,
|
||||
CapturedSymbols::None,
|
||||
is_self_recursive,
|
||||
return_type,
|
||||
);
|
||||
|
||||
procs.partial_procs.insert(closure_name, partial_proc);
|
||||
}
|
||||
|
||||
/// Insert a closure that may capture symbols to the list of partial procs
|
||||
fn register_capturing_closure<'a>(
|
||||
env: &mut Env<'a, '_>,
|
||||
procs: &mut Procs<'a>,
|
||||
layout_cache: &mut LayoutCache<'a>,
|
||||
closure_name: Symbol,
|
||||
closure_data: ClosureData,
|
||||
) {
|
||||
// the function surrounding the closure definition may be specialized multiple times,
|
||||
// hence in theory this partial proc may be added multiple times. That would be wasteful
|
||||
// so we check whether this partial proc is already there.
|
||||
//
|
||||
// (the `gen_primitives::task_always_twice` test has this behavior)
|
||||
if !procs.partial_procs.contains_key(closure_name) {
|
||||
let ClosureData {
|
||||
function_type,
|
||||
return_type,
|
||||
closure_type,
|
||||
closure_ext_var,
|
||||
recursive,
|
||||
arguments,
|
||||
loc_body: boxed_body,
|
||||
captured_symbols,
|
||||
..
|
||||
} = closure_data;
|
||||
let loc_body = *boxed_body;
|
||||
|
||||
let is_self_recursive = !matches!(recursive, roc_can::expr::Recursive::NotRecursive);
|
||||
|
||||
// does this function capture any local values?
|
||||
let function_layout = layout_cache.raw_from_var(env.arena, function_type, env.subs);
|
||||
|
||||
let captured_symbols = match function_layout {
|
||||
Ok(RawFunctionLayout::Function(_, lambda_set, _)) => {
|
||||
if let Layout::Struct(&[]) = lambda_set.runtime_representation() {
|
||||
CapturedSymbols::None
|
||||
} else {
|
||||
let mut temp = Vec::from_iter_in(captured_symbols, env.arena);
|
||||
temp.sort();
|
||||
CapturedSymbols::Captured(temp.into_bump_slice())
|
||||
}
|
||||
}
|
||||
Ok(RawFunctionLayout::ZeroArgumentThunk(_)) => {
|
||||
// top-level thunks cannot capture any variables
|
||||
debug_assert!(
|
||||
captured_symbols.is_empty(),
|
||||
"{:?} with layout {:?} {:?} {:?}",
|
||||
&captured_symbols,
|
||||
function_layout,
|
||||
env.subs,
|
||||
(function_type, closure_type, closure_ext_var),
|
||||
);
|
||||
CapturedSymbols::None
|
||||
}
|
||||
Err(_) => {
|
||||
// just allow this. see https://github.com/rtfeldman/roc/issues/1585
|
||||
if captured_symbols.is_empty() {
|
||||
CapturedSymbols::None
|
||||
} else {
|
||||
let mut temp = Vec::from_iter_in(captured_symbols, env.arena);
|
||||
temp.sort();
|
||||
CapturedSymbols::Captured(temp.into_bump_slice())
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let partial_proc = PartialProc::from_named_function(
|
||||
env,
|
||||
layout_cache,
|
||||
function_type,
|
||||
arguments,
|
||||
loc_body,
|
||||
captured_symbols,
|
||||
is_self_recursive,
|
||||
return_type,
|
||||
);
|
||||
|
||||
procs.partial_procs.insert(closure_name, partial_proc);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_can<'a>(
|
||||
env: &mut Env<'a, '_>,
|
||||
variable: Variable,
|
||||
|
@ -4752,35 +4887,15 @@ pub fn from_can<'a>(
|
|||
// Now that we know for sure it's a closure, get an owned
|
||||
// version of these variant args so we can use them properly.
|
||||
match def.loc_expr.value {
|
||||
Closure {
|
||||
function_type,
|
||||
return_type,
|
||||
recursive,
|
||||
arguments,
|
||||
loc_body: boxed_body,
|
||||
..
|
||||
} => {
|
||||
// Extract Procs, but discard the resulting Expr::Load.
|
||||
// That Load looks up the pointer, which we won't use here!
|
||||
|
||||
let loc_body = *boxed_body;
|
||||
|
||||
let is_self_recursive =
|
||||
!matches!(recursive, roc_can::expr::Recursive::NotRecursive);
|
||||
|
||||
let partial_proc = PartialProc::from_named_function(
|
||||
Closure(closure_data) => {
|
||||
register_capturing_closure(
|
||||
env,
|
||||
procs,
|
||||
layout_cache,
|
||||
function_type,
|
||||
arguments,
|
||||
loc_body,
|
||||
CapturedSymbols::None,
|
||||
is_self_recursive,
|
||||
return_type,
|
||||
*symbol,
|
||||
closure_data,
|
||||
);
|
||||
|
||||
procs.partial_procs.insert(*symbol, partial_proc);
|
||||
|
||||
continue;
|
||||
}
|
||||
_ => unreachable!("recursive value is not a function"),
|
||||
|
@ -4793,90 +4908,12 @@ pub fn from_can<'a>(
|
|||
}
|
||||
LetNonRec(def, cont, outer_annotation) => {
|
||||
if let roc_can::pattern::Pattern::Identifier(symbol) = &def.loc_pattern.value {
|
||||
if let Closure { .. } = &def.loc_expr.value {
|
||||
// Now that we know for sure it's a closure, get an owned
|
||||
// version of these variant args so we can use them properly.
|
||||
match def.loc_expr.value {
|
||||
Closure {
|
||||
function_type,
|
||||
return_type,
|
||||
closure_type,
|
||||
closure_ext_var,
|
||||
recursive,
|
||||
arguments,
|
||||
loc_body: boxed_body,
|
||||
captured_symbols,
|
||||
..
|
||||
} => {
|
||||
// Extract Procs, but discard the resulting Expr::Load.
|
||||
// That Load looks up the pointer, which we won't use here!
|
||||
|
||||
let loc_body = *boxed_body;
|
||||
|
||||
let is_self_recursive =
|
||||
!matches!(recursive, roc_can::expr::Recursive::NotRecursive);
|
||||
|
||||
// does this function capture any local values?
|
||||
let function_layout =
|
||||
layout_cache.raw_from_var(env.arena, function_type, env.subs);
|
||||
|
||||
let captured_symbols = match function_layout {
|
||||
Ok(RawFunctionLayout::Function(_, lambda_set, _)) => {
|
||||
if let Layout::Struct(&[]) = lambda_set.runtime_representation()
|
||||
{
|
||||
CapturedSymbols::None
|
||||
} else {
|
||||
let mut temp =
|
||||
Vec::from_iter_in(captured_symbols, env.arena);
|
||||
temp.sort();
|
||||
CapturedSymbols::Captured(temp.into_bump_slice())
|
||||
}
|
||||
}
|
||||
Ok(RawFunctionLayout::ZeroArgumentThunk(_)) => {
|
||||
// top-level thunks cannot capture any variables
|
||||
debug_assert!(
|
||||
captured_symbols.is_empty(),
|
||||
"{:?} with layout {:?} {:?} {:?}",
|
||||
&captured_symbols,
|
||||
function_layout,
|
||||
env.subs,
|
||||
(function_type, closure_type, closure_ext_var),
|
||||
);
|
||||
CapturedSymbols::None
|
||||
}
|
||||
Err(_) => {
|
||||
// just allow this. see https://github.com/rtfeldman/roc/issues/1585
|
||||
if captured_symbols.is_empty() {
|
||||
CapturedSymbols::None
|
||||
} else {
|
||||
let mut temp =
|
||||
Vec::from_iter_in(captured_symbols, env.arena);
|
||||
temp.sort();
|
||||
CapturedSymbols::Captured(temp.into_bump_slice())
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let partial_proc = PartialProc::from_named_function(
|
||||
env,
|
||||
layout_cache,
|
||||
function_type,
|
||||
arguments,
|
||||
loc_body,
|
||||
captured_symbols,
|
||||
is_self_recursive,
|
||||
return_type,
|
||||
);
|
||||
|
||||
procs.partial_procs.insert(*symbol, partial_proc);
|
||||
roc_can::expr::Expr::Closure(closure_data) => {
|
||||
register_capturing_closure(env, procs, layout_cache, *symbol, closure_data);
|
||||
|
||||
return from_can(env, variable, cont.value, procs, layout_cache);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
match def.loc_expr.value {
|
||||
roc_can::expr::Expr::Var(original) => {
|
||||
// a variable is aliased, e.g.
|
||||
//
|
||||
|
@ -6012,7 +6049,7 @@ fn can_reuse_symbol<'a>(
|
|||
|
||||
if env.is_imported_symbol(symbol) {
|
||||
Imported(symbol)
|
||||
} else if procs.partial_procs.contains_key(&symbol) {
|
||||
} else if procs.partial_procs.contains_key(symbol) {
|
||||
LocalFunction(symbol)
|
||||
} else {
|
||||
Value(symbol)
|
||||
|
@ -6101,7 +6138,7 @@ fn reuse_function_symbol<'a>(
|
|||
result: Stmt<'a>,
|
||||
original: Symbol,
|
||||
) -> Stmt<'a> {
|
||||
match procs.partial_procs.get(&original) {
|
||||
match procs.get_partial_proc(original) {
|
||||
None => {
|
||||
match arg_var {
|
||||
Some(arg_var) if env.is_imported_symbol(original) => {
|
||||
|
@ -6170,7 +6207,7 @@ fn reuse_function_symbol<'a>(
|
|||
// and closures by unification. Here we record whether this function captures
|
||||
// anything.
|
||||
let captures = partial_proc.captured_symbols.captures();
|
||||
let captured = partial_proc.captured_symbols.clone();
|
||||
let captured = partial_proc.captured_symbols;
|
||||
|
||||
match res_layout {
|
||||
RawFunctionLayout::Function(_, lambda_set, _) => {
|
||||
|
@ -6674,7 +6711,7 @@ fn call_by_name_help<'a>(
|
|||
assign_to_symbols(env, procs, layout_cache, iter, result)
|
||||
}
|
||||
None => {
|
||||
let opt_partial_proc = procs.partial_procs.get(&proc_name);
|
||||
let opt_partial_proc = procs.partial_procs.symbol_to_id(proc_name);
|
||||
|
||||
/*
|
||||
debug_assert_eq!(
|
||||
|
@ -6691,9 +6728,6 @@ fn call_by_name_help<'a>(
|
|||
|
||||
match opt_partial_proc {
|
||||
Some(partial_proc) => {
|
||||
// TODO should pending_procs hold a Rc<Proc> to avoid this .clone()?
|
||||
let partial_proc = partial_proc.clone();
|
||||
|
||||
// Mark this proc as in-progress, so if we're dealing with
|
||||
// mutually recursive functions, we don't loop forever.
|
||||
// (We had a bug around this before this system existed!)
|
||||
|
@ -6773,10 +6807,11 @@ fn call_by_name_module_thunk<'a>(
|
|||
let inner_layout = *ret_layout;
|
||||
|
||||
// If we've already specialized this one, no further work is needed.
|
||||
if procs
|
||||
let already_specialized = procs
|
||||
.specialized
|
||||
.contains_key(&(proc_name, top_level_layout))
|
||||
{
|
||||
.contains_key(&(proc_name, top_level_layout));
|
||||
|
||||
if already_specialized {
|
||||
force_thunk(env, proc_name, inner_layout, assigned, hole)
|
||||
} else {
|
||||
let pending = PendingSpecialization::from_var(env.arena, env.subs, fn_var);
|
||||
|
@ -6811,13 +6846,10 @@ fn call_by_name_module_thunk<'a>(
|
|||
force_thunk(env, proc_name, inner_layout, assigned, hole)
|
||||
}
|
||||
None => {
|
||||
let opt_partial_proc = procs.partial_procs.get(&proc_name);
|
||||
let opt_partial_proc = procs.partial_procs.symbol_to_id(proc_name);
|
||||
|
||||
match opt_partial_proc {
|
||||
Some(partial_proc) => {
|
||||
// TODO should pending_procs hold a Rc<Proc> to avoid this .clone()?
|
||||
let partial_proc = partial_proc.clone();
|
||||
|
||||
// Mark this proc as in-progress, so if we're dealing with
|
||||
// mutually recursive functions, we don't loop forever.
|
||||
// (We had a bug around this before this system existed!)
|
||||
|
@ -6934,7 +6966,7 @@ fn call_specialized_proc<'a>(
|
|||
|
||||
match procs
|
||||
.partial_procs
|
||||
.get(&proc_name)
|
||||
.get_symbol(proc_name)
|
||||
.map(|pp| &pp.captured_symbols)
|
||||
{
|
||||
Some(&CapturedSymbols::Captured(captured_symbols)) => {
|
||||
|
|
|
@ -724,6 +724,14 @@ impl<'a, 'b> Env<'a, 'b> {
|
|||
}
|
||||
}
|
||||
|
||||
const fn round_up_to_alignment(width: u32, alignment: u32) -> u32 {
|
||||
if alignment != 0 && width % alignment > 0 {
|
||||
width + alignment - (width % alignment)
|
||||
} else {
|
||||
width
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Layout<'a> {
|
||||
fn new_help<'b>(
|
||||
env: &mut Env<'a, 'b>,
|
||||
|
@ -859,11 +867,7 @@ impl<'a> Layout<'a> {
|
|||
let width = self.stack_size_without_alignment(pointer_size);
|
||||
let alignment = self.alignment_bytes(pointer_size);
|
||||
|
||||
if alignment != 0 && width % alignment > 0 {
|
||||
width + alignment - (width % alignment)
|
||||
} else {
|
||||
width
|
||||
}
|
||||
round_up_to_alignment(width, alignment)
|
||||
}
|
||||
|
||||
fn stack_size_without_alignment(&self, pointer_size: u32) -> u32 {
|
||||
|
@ -885,6 +889,8 @@ impl<'a> Layout<'a> {
|
|||
|
||||
match variant {
|
||||
NonRecursive(fields) => {
|
||||
let tag_id_builtin = variant.tag_id_builtin();
|
||||
|
||||
fields
|
||||
.iter()
|
||||
.map(|tag_layout| {
|
||||
|
@ -894,9 +900,10 @@ impl<'a> Layout<'a> {
|
|||
.sum::<u32>()
|
||||
})
|
||||
.max()
|
||||
.map(|w| round_up_to_alignment(w, tag_id_builtin.alignment_bytes(pointer_size)))
|
||||
.unwrap_or_default()
|
||||
// the size of the tag_id
|
||||
+ variant.tag_id_builtin().stack_size(pointer_size)
|
||||
+ tag_id_builtin.stack_size(pointer_size)
|
||||
}
|
||||
|
||||
Recursive(_)
|
||||
|
@ -924,13 +931,28 @@ impl<'a> Layout<'a> {
|
|||
use UnionLayout::*;
|
||||
|
||||
match variant {
|
||||
NonRecursive(tags) => tags
|
||||
NonRecursive(tags) => {
|
||||
let max_alignment = tags
|
||||
.iter()
|
||||
.map(|x| x.iter())
|
||||
.flatten()
|
||||
.map(|x| x.alignment_bytes(pointer_size))
|
||||
.max()
|
||||
.unwrap_or(0),
|
||||
.flat_map(|layouts| {
|
||||
layouts
|
||||
.iter()
|
||||
.map(|layout| layout.alignment_bytes(pointer_size))
|
||||
})
|
||||
.max();
|
||||
|
||||
match max_alignment {
|
||||
Some(align) => {
|
||||
let tag_id_builtin = variant.tag_id_builtin();
|
||||
|
||||
round_up_to_alignment(
|
||||
align,
|
||||
tag_id_builtin.alignment_bytes(pointer_size),
|
||||
)
|
||||
}
|
||||
None => 0,
|
||||
}
|
||||
}
|
||||
Recursive(_)
|
||||
| NullableWrapped { .. }
|
||||
| NullableUnwrapped { .. }
|
||||
|
|
|
@ -14,11 +14,11 @@ keep_shadowed_builtins = []
|
|||
roc_collections = { path = "../collections" }
|
||||
roc_region = { path = "../region" }
|
||||
roc_module = { path = "../module" }
|
||||
bumpalo = { version = "3.6.1", features = ["collections"] }
|
||||
encode_unicode = "0.3"
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
encode_unicode = "0.3.6"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.5.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
quickcheck_macros = "0.8"
|
||||
pretty_assertions = "1.0.0"
|
||||
indoc = "1.0.3"
|
||||
quickcheck = "1.0.3"
|
||||
quickcheck_macros = "1.0.0"
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
use crate::ast::CommentOrNewline;
|
||||
use crate::ast::Spaceable;
|
||||
use crate::parser::{
|
||||
self, and, backtrackable, BadInputError, Col, Parser,
|
||||
Progress::{self, *},
|
||||
Row, State,
|
||||
self, and, backtrackable, BadInputError, Col, Parser, Progress::*, Row, State,
|
||||
};
|
||||
use bumpalo::collections::vec::Vec;
|
||||
use bumpalo::Bump;
|
||||
|
@ -181,109 +179,6 @@ where
|
|||
spaces_help_help(min_indent, space_problem, indent_problem)
|
||||
}
|
||||
|
||||
pub fn spaces_till_end_of_line<'a, E: 'a>(
|
||||
tab_problem: fn(Row, Col) -> E,
|
||||
) -> impl Parser<'a, Option<&'a str>, E> {
|
||||
move |_, mut state: State<'a>| {
|
||||
let mut bytes = state.bytes;
|
||||
let mut row = state.line;
|
||||
let mut col = state.column;
|
||||
|
||||
for c in bytes {
|
||||
match c {
|
||||
b' ' => {
|
||||
bytes = &bytes[1..];
|
||||
col += 1;
|
||||
}
|
||||
b'\n' => {
|
||||
bytes = &bytes[1..];
|
||||
row += 1;
|
||||
col = 0;
|
||||
|
||||
state.line = row;
|
||||
state.column = col;
|
||||
state.bytes = bytes;
|
||||
|
||||
return Ok((MadeProgress, None, state));
|
||||
}
|
||||
b'\r' => {
|
||||
bytes = &bytes[1..];
|
||||
}
|
||||
b'\t' => {
|
||||
return Err((
|
||||
MadeProgress,
|
||||
tab_problem(row, col),
|
||||
State {
|
||||
line: row,
|
||||
column: col,
|
||||
..state
|
||||
},
|
||||
))
|
||||
}
|
||||
b'#' => match chomp_line_comment(bytes) {
|
||||
Ok(comment) => {
|
||||
state.line += 1;
|
||||
state.column = 0;
|
||||
|
||||
let width = 1 + comment.len();
|
||||
if let Some(b'\n') = bytes.get(width) {
|
||||
state.bytes = &bytes[width + 1..];
|
||||
} else {
|
||||
state.bytes = &bytes[width..];
|
||||
}
|
||||
|
||||
return Ok((MadeProgress, Some(comment), state));
|
||||
}
|
||||
Err(_) => unreachable!("we check the first character is a #"),
|
||||
},
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
|
||||
if state.column == col {
|
||||
Ok((NoProgress, None, state))
|
||||
} else {
|
||||
Ok((
|
||||
MadeProgress,
|
||||
None,
|
||||
State {
|
||||
column: col,
|
||||
bytes,
|
||||
..state
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn chomp_line_comment(buffer: &[u8]) -> Result<&str, Progress> {
|
||||
if let Some(b'#') = buffer.get(0) {
|
||||
if (&buffer[1..]).starts_with(b"# ") {
|
||||
// this is a doc comment, not a line comment
|
||||
Err(NoProgress)
|
||||
} else {
|
||||
use encode_unicode::CharExt;
|
||||
|
||||
let mut chomped = 1;
|
||||
|
||||
while let Ok((ch, width)) = char::from_utf8_slice_start(&buffer[chomped..]) {
|
||||
if ch == '\n' {
|
||||
break;
|
||||
} else {
|
||||
chomped += width;
|
||||
}
|
||||
}
|
||||
|
||||
let comment_bytes = &buffer[1..chomped];
|
||||
let comment = unsafe { std::str::from_utf8_unchecked(comment_bytes) };
|
||||
|
||||
Ok(comment)
|
||||
}
|
||||
} else {
|
||||
Err(NoProgress)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn spaces_help_help<'a, E>(
|
||||
min_indent: u16,
|
||||
|
|
|
@ -416,9 +416,41 @@ mod test_parse {
|
|||
assert_parses_to(&string, Float(&string));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pos_inf_float() {
|
||||
assert_parses_to(
|
||||
"inf",
|
||||
Var {
|
||||
module_name: "",
|
||||
ident: "inf",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn neg_inf_float() {
|
||||
let arena = Bump::new();
|
||||
let loc_op = Located::new(0, 0, 0, 1, UnaryOp::Negate);
|
||||
let inf_expr = Var {
|
||||
module_name: "",
|
||||
ident: "inf",
|
||||
};
|
||||
let loc_inf_expr = Located::new(0, 0, 1, 4, inf_expr);
|
||||
assert_parses_to("-inf", UnaryOp(arena.alloc(loc_inf_expr), loc_op));
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn all_f64_values_parse(num: f64) {
|
||||
assert_parses_to(num.to_string().as_str(), Float(num.to_string().as_str()));
|
||||
let string = num.to_string();
|
||||
if string.contains(".") {
|
||||
assert_parses_to(&string, Float(&string));
|
||||
} else if num.is_nan() {
|
||||
assert_parses_to(&string, Expr::GlobalTag(&string));
|
||||
} else if num.is_finite() {
|
||||
// These are whole numbers. Add the `.0` back to make float.
|
||||
let float_string = format!("{}.0", string);
|
||||
assert_parses_to(&float_string, Float(&float_string));
|
||||
}
|
||||
}
|
||||
|
||||
// RECORD LITERALS
|
||||
|
|
|
@ -10,10 +10,3 @@ roc_collections = { path = "../collections" }
|
|||
roc_region = { path = "../region" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_parse = { path = "../parse" }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.5.1"
|
||||
maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
quickcheck_macros = "0.8"
|
||||
|
|
|
@ -16,18 +16,15 @@ roc_can = { path = "../can" }
|
|||
roc_solve = { path = "../solve" }
|
||||
roc_mono = { path = "../mono" }
|
||||
ven_pretty = { path = "../../vendor/pretty" }
|
||||
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!
|
||||
im = "15.0.0"
|
||||
im-rc = "15.0.0"
|
||||
distance = "0.4.0"
|
||||
bumpalo = { version = "3.6.1", features = ["collections"] }
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
|
||||
[dev-dependencies]
|
||||
roc_constrain = { path = "../constrain" }
|
||||
roc_builtins = { path = "../builtins" }
|
||||
roc_problem = { path = "../problem" }
|
||||
roc_parse = { path = "../parse" }
|
||||
pretty_assertions = "0.5.1"
|
||||
maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
quickcheck_macros = "0.8"
|
||||
pretty_assertions = "1.0.0"
|
||||
indoc = "1.0.3"
|
||||
|
|
|
@ -1805,8 +1805,10 @@ fn diff_record<'b>(
|
|||
fields2: SendMap<Lowercase, RecordField<ErrorType>>,
|
||||
ext2: TypeExt,
|
||||
) -> Diff<RocDocBuilder<'b>> {
|
||||
let to_overlap_docs =
|
||||
|(field, (t1, t2)): &(Lowercase, (RecordField<ErrorType>, RecordField<ErrorType>))| {
|
||||
let to_overlap_docs = |(field, (t1, t2)): (
|
||||
&Lowercase,
|
||||
&(RecordField<ErrorType>, RecordField<ErrorType>),
|
||||
)| {
|
||||
let diff = to_diff(
|
||||
alloc,
|
||||
Parens::Unnecessary,
|
||||
|
@ -1836,8 +1838,7 @@ fn diff_record<'b>(
|
|||
status: {
|
||||
match (&t1, &t2) {
|
||||
(RecordField::Demanded(_), RecordField::Optional(_))
|
||||
| (RecordField::Optional(_), RecordField::Demanded(_)) => match diff.status
|
||||
{
|
||||
| (RecordField::Optional(_), RecordField::Demanded(_)) => match diff.status {
|
||||
Status::Similar => {
|
||||
Status::Different(vec![Problem::OptionalRequiredMismatch(
|
||||
field.clone(),
|
||||
|
@ -1855,7 +1856,7 @@ fn diff_record<'b>(
|
|||
}
|
||||
};
|
||||
|
||||
let to_unknown_docs = |(field, tipe): &(Lowercase, RecordField<ErrorType>)| {
|
||||
let to_unknown_docs = |(field, tipe): (&Lowercase, &RecordField<ErrorType>)| {
|
||||
(
|
||||
field.clone(),
|
||||
alloc.string(field.as_str().to_string()),
|
||||
|
@ -1979,7 +1980,7 @@ fn diff_tag_union<'b>(
|
|||
status: diff.status,
|
||||
}
|
||||
};
|
||||
let to_unknown_docs = |(field, args): &(TagName, Vec<ErrorType>)| {
|
||||
let to_unknown_docs = |(field, args): (&TagName, &Vec<ErrorType>)| {
|
||||
(
|
||||
field.clone(),
|
||||
alloc.tag_name(field.clone()),
|
||||
|
|
|
@ -398,7 +398,7 @@ impl<'a> RocDocAllocator<'a> {
|
|||
debug_assert!(region.contains(&sub_region2));
|
||||
|
||||
// if true, the final line of the snippet will be some ^^^ that point to the region where
|
||||
// the problem is. Otherwise, the snippet will have a > on the lines that are in the regon
|
||||
// the problem is. Otherwise, the snippet will have a > on the lines that are in the region
|
||||
// where the problem is.
|
||||
let error_highlight_line = region.start_line == region.end_line;
|
||||
|
||||
|
@ -505,7 +505,7 @@ impl<'a> RocDocAllocator<'a> {
|
|||
}
|
||||
|
||||
// if true, the final line of the snippet will be some ^^^ that point to the region where
|
||||
// the problem is. Otherwise, the snippet will have a > on the lines that are in the regon
|
||||
// the problem is. Otherwise, the snippet will have a > on the lines that are in the region
|
||||
// where the problem is.
|
||||
let error_highlight_line = sub_region.start_line == region.end_line;
|
||||
|
||||
|
|
|
@ -19,10 +19,7 @@ roc_builtins = { path = "../builtins" }
|
|||
roc_problem = { path = "../problem" }
|
||||
roc_parse = { path = "../parse" }
|
||||
roc_solve = { path = "../solve" }
|
||||
pretty_assertions = "0.5.1"
|
||||
maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
tempfile = "3.1.0"
|
||||
quickcheck = "0.8"
|
||||
quickcheck_macros = "0.8"
|
||||
bumpalo = { version = "3.6.1", features = ["collections"] }
|
||||
pretty_assertions = "1.0.0"
|
||||
indoc = "1.0.3"
|
||||
tempfile = "3.2.0"
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
|
|
|
@ -21,7 +21,6 @@ roc_builtins = { path = "../builtins" }
|
|||
roc_parse = { path = "../parse" }
|
||||
roc_solve = { path = "../solve" }
|
||||
pretty_assertions = "0.5.1"
|
||||
maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
quickcheck_macros = "0.8"
|
||||
|
|
25
compiler/test_dev/Cargo.toml
Normal file
25
compiler/test_dev/Cargo.toml
Normal file
|
@ -0,0 +1,25 @@
|
|||
[package]
|
||||
name = "test_dev"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_can = { path = "../can" }
|
||||
roc_build = { path = "../build" }
|
||||
roc_parse = { path = "../parse" }
|
||||
roc_reporting = { path = "../reporting" }
|
||||
roc_load = { path = "../load" }
|
||||
roc_constrain = { path = "../constrain" }
|
||||
roc_std = { path = "../../roc_std" }
|
||||
roc_gen_dev = { path = "../gen_dev" }
|
||||
roc_mono = { path = "../mono" }
|
||||
roc_problem = { path = "../problem" }
|
||||
roc_builtins = { path = "../builtins" }
|
||||
indoc = "1.0.3"
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
tempfile = "3.2.0"
|
||||
libloading = "0.7.1"
|
||||
target-lexicon = "0.12.2"
|
871
compiler/test_dev/src/dev_num.rs
Normal file
871
compiler/test_dev/src/dev_num.rs
Normal file
|
@ -0,0 +1,871 @@
|
|||
#![cfg(all(test, any(target_os = "linux", target_os = "macos"), any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))]
|
||||
|
||||
use crate::assert_evals_to;
|
||||
use indoc::indoc;
|
||||
|
||||
#[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 f64_values() {
|
||||
assert_evals_to!("0.0", 0.0, f64);
|
||||
assert_evals_to!("-0.0", 0.0, f64);
|
||||
assert_evals_to!("1.0", 1.0, f64);
|
||||
assert_evals_to!("-1.0", -1.0, f64);
|
||||
assert_evals_to!("3.1415926535897932", 3.141_592_653_589_793, f64);
|
||||
assert_evals_to!(&format!("{:0.1}", f64::MIN), f64::MIN, f64);
|
||||
assert_evals_to!(&format!("{:0.1}", f64::MAX), f64::MAX, f64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_add_i64() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
1 + 2 + 3
|
||||
"#
|
||||
),
|
||||
6,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_add_f64() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
1.1 + 2.4 + 3
|
||||
"#
|
||||
),
|
||||
6.5,
|
||||
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 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
|
||||
|
||||
a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + 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 gen_int_eq() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
4 == 4
|
||||
"#
|
||||
),
|
||||
true,
|
||||
bool
|
||||
);
|
||||
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
3 == 4
|
||||
"#
|
||||
),
|
||||
false,
|
||||
bool
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_basic_fn() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
always42 : Num.Num (Num.Integer Num.Signed64) -> Num.Num (Num.Integer Num.Signed64)
|
||||
always42 = \_ -> 42
|
||||
|
||||
always42 5
|
||||
"#
|
||||
),
|
||||
42,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_wrap_add_nums() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
add2 = \num1, num2 -> num1 + num2
|
||||
|
||||
add2 4 5
|
||||
"#
|
||||
),
|
||||
9,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_wrap_add_nums_force_stack() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
add9 = \num1, num2, num3, num4, num5, num6, num7, num8, num9 -> num1 + num2 + num3 + num4 + num5 + num6 + num7 + num8 + num9
|
||||
|
||||
add9 1 2 3 4 5 6 7 8 9
|
||||
"#
|
||||
),
|
||||
45,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pow_int() {
|
||||
assert_evals_to!("Num.powInt 2 3", 8, i64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn acos() {
|
||||
assert_evals_to!("Num.acos 0.5", 1.0471975511965979, f64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn asin() {
|
||||
assert_evals_to!("Num.asin 0.5", 0.5235987755982989, f64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn atan() {
|
||||
assert_evals_to!("Num.atan 10", 1.4711276743037347, 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
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_fib_fn() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
fib = \n ->
|
||||
if n == 0 then
|
||||
0
|
||||
else if n == 1 then
|
||||
1
|
||||
else
|
||||
(fib (n - 1)) + (fib (n - 2))
|
||||
|
||||
fib 10
|
||||
"#
|
||||
),
|
||||
55,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_fast_fib_fn() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
fib = \n, a, b ->
|
||||
if n == 0 then
|
||||
a
|
||||
else
|
||||
fib (n - 1) b (a + b)
|
||||
fib 10 0 1
|
||||
"#
|
||||
),
|
||||
55,
|
||||
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 f64_round() {
|
||||
assert_evals_to!("Num.round 3.6", 4, i64);
|
||||
assert_evals_to!("Num.round 3.4", 3, i64);
|
||||
assert_evals_to!("Num.round 2.5", 3, i64);
|
||||
assert_evals_to!("Num.round -2.3", -2, i64);
|
||||
assert_evals_to!("Num.round -2.5", -3, 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 gen_float_eq() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// 1.0 == 1.0
|
||||
// "#
|
||||
// ),
|
||||
// true,
|
||||
// bool
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[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_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_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 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]
|
||||
// // #[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 max_i128() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// Num.maxI128
|
||||
// "#
|
||||
// ),
|
||||
// i128::MAX,
|
||||
// i128
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[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
|
||||
// );
|
||||
// }
|
933
compiler/test_dev/src/dev_records.rs
Normal file
933
compiler/test_dev/src/dev_records.rs
Normal file
|
@ -0,0 +1,933 @@
|
|||
#![cfg(all(test, any(target_os = "linux", target_os = "macos"), any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))]
|
||||
|
||||
use crate::assert_evals_to;
|
||||
use indoc::indoc;
|
||||
|
||||
#[test]
|
||||
fn basic_record() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
{ y: 17, x: 15, z: 19 }.x
|
||||
"#
|
||||
),
|
||||
15,
|
||||
i64
|
||||
);
|
||||
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
{ x: 15, y: 17, z: 19 }.y
|
||||
"#
|
||||
),
|
||||
17,
|
||||
i64
|
||||
);
|
||||
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
{ x: 15, y: 17, z: 19 }.z
|
||||
"#
|
||||
),
|
||||
19,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_record() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
{ x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.x
|
||||
"#
|
||||
),
|
||||
15,
|
||||
i64
|
||||
);
|
||||
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
{ x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.y.a
|
||||
"#
|
||||
),
|
||||
12,
|
||||
i64
|
||||
);
|
||||
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
{ x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.y.b
|
||||
"#
|
||||
),
|
||||
15,
|
||||
i64
|
||||
);
|
||||
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
{ x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.y.c
|
||||
"#
|
||||
),
|
||||
2,
|
||||
i64
|
||||
);
|
||||
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
{ x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.z
|
||||
"#
|
||||
),
|
||||
19,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn f64_record() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
rec = { y: 17.2, x: 15.1, z: 19.3 }
|
||||
|
||||
rec.x
|
||||
"#
|
||||
),
|
||||
15.1,
|
||||
f64
|
||||
);
|
||||
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
rec = { y: 17.2, x: 15.1, z: 19.3 }
|
||||
|
||||
rec.y
|
||||
"#
|
||||
),
|
||||
17.2,
|
||||
f64
|
||||
);
|
||||
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
rec = { y: 17.2, x: 15.1, z: 19.3 }
|
||||
|
||||
rec.z
|
||||
"#
|
||||
),
|
||||
19.3,
|
||||
f64
|
||||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn fn_record() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// getRec = \x -> { y: 17, x, z: 19 }
|
||||
|
||||
// (getRec 15).x
|
||||
// "#
|
||||
// ),
|
||||
// 15,
|
||||
// i64
|
||||
// );
|
||||
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// rec = { x: 15, y: 17, z: 19 }
|
||||
|
||||
// rec.y
|
||||
// "#
|
||||
// ),
|
||||
// 17,
|
||||
// i64
|
||||
// );
|
||||
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// rec = { x: 15, y: 17, z: 19 }
|
||||
|
||||
// rec.z
|
||||
// "#
|
||||
// ),
|
||||
// 19,
|
||||
// i64
|
||||
// );
|
||||
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// rec = { x: 15, y: 17, z: 19 }
|
||||
|
||||
// rec.z + rec.x
|
||||
// "#
|
||||
// ),
|
||||
// 34,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn def_record() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
rec = { y: 17, x: 15, z: 19 }
|
||||
|
||||
rec.x
|
||||
"#
|
||||
),
|
||||
15,
|
||||
i64
|
||||
);
|
||||
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
rec = { x: 15, y: 17, z: 19 }
|
||||
|
||||
rec.y
|
||||
"#
|
||||
),
|
||||
17,
|
||||
i64
|
||||
);
|
||||
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
rec = { x: 15, y: 17, z: 19 }
|
||||
|
||||
rec.z
|
||||
"#
|
||||
),
|
||||
19,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn when_on_record() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when { x: 0x2 } is
|
||||
{ x } -> x + 3
|
||||
"#
|
||||
),
|
||||
5,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn when_record_with_guard_pattern() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when { x: 0x2, y: 3.14 } is
|
||||
{ x: var } -> var + 3
|
||||
"#
|
||||
),
|
||||
5,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn let_with_record_pattern() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
{ x } = { x: 0x2, y: 3.14 }
|
||||
|
||||
x
|
||||
"#
|
||||
),
|
||||
2,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_guard_pattern() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when { x: 0x2, y: 3.14 } is
|
||||
{ x: 0x4 } -> 5
|
||||
{ x } -> x + 3
|
||||
"#
|
||||
),
|
||||
5,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn twice_record_access() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
x = {a: 0x2, b: 0x3 }
|
||||
|
||||
x.a + x.b
|
||||
"#
|
||||
),
|
||||
5,
|
||||
i64
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn empty_record() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
v = {}
|
||||
|
||||
v
|
||||
"#
|
||||
),
|
||||
(),
|
||||
()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn i64_record1_literal() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
{ x: 3 }
|
||||
"#
|
||||
),
|
||||
3,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn i64_record2_literal() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { x: 3, y: 5 }
|
||||
// "#
|
||||
// ),
|
||||
// (3, 5),
|
||||
// (i64, i64)
|
||||
// );
|
||||
// }
|
||||
|
||||
// // #[test]
|
||||
// // fn i64_record3_literal() {
|
||||
// // assert_evals_to!(
|
||||
// // indoc!(
|
||||
// // r#"
|
||||
// // { x: 3, y: 5, z: 17 }
|
||||
// // "#
|
||||
// // ),
|
||||
// // (3, 5, 17),
|
||||
// // (i64, i64, i64)
|
||||
// // );
|
||||
// // }
|
||||
|
||||
// #[test]
|
||||
// fn f64_record2_literal() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { x: 3.1, y: 5.1 }
|
||||
// "#
|
||||
// ),
|
||||
// (3.1, 5.1),
|
||||
// (f64, f64)
|
||||
// );
|
||||
// }
|
||||
|
||||
// // #[test]
|
||||
// // fn f64_record3_literal() {
|
||||
// // assert_evals_to!(
|
||||
// // indoc!(
|
||||
// // r#"
|
||||
// // { x: 3.1, y: 5.1, z: 17.1 }
|
||||
// // "#
|
||||
// // ),
|
||||
// // (3.1, 5.1, 17.1),
|
||||
// // (f64, f64, f64)
|
||||
// // );
|
||||
// // }
|
||||
|
||||
// // #[test]
|
||||
// // fn bool_record4_literal() {
|
||||
// // assert_evals_to!(
|
||||
// // indoc!(
|
||||
// // r#"
|
||||
// // record : { a : Bool, b : Bool, c : Bool, d : Bool }
|
||||
// // record = { a: True, b: True, c : True, d : Bool }
|
||||
|
||||
// // record
|
||||
// // "#
|
||||
// // ),
|
||||
// // (true, false, false, true),
|
||||
// // (bool, bool, bool, bool)
|
||||
// // );
|
||||
// // }
|
||||
|
||||
// #[test]
|
||||
// fn i64_record1_literal() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { a: 3 }
|
||||
// "#
|
||||
// ),
|
||||
// 3,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// // #[test]
|
||||
// // fn i64_record9_literal() {
|
||||
// // assert_evals_to!(
|
||||
// // indoc!(
|
||||
// // r#"
|
||||
// // { a: 3, b: 5, c: 17, d: 1, e: 9, f: 12, g: 13, h: 14, i: 15 }
|
||||
// // "#
|
||||
// // ),
|
||||
// // (3, 5, 17, 1, 9, 12, 13, 14, 15),
|
||||
// // (i64, i64, i64, i64, i64, i64, i64, i64, i64)
|
||||
// // );
|
||||
// // }
|
||||
|
||||
// // #[test]
|
||||
// // fn f64_record3_literal() {
|
||||
// // assert_evals_to!(
|
||||
// // indoc!(
|
||||
// // r#"
|
||||
// // { x: 3.1, y: 5.1, z: 17.1 }
|
||||
// // "#
|
||||
// // ),
|
||||
// // (3.1, 5.1, 17.1),
|
||||
// // (f64, f64, f64)
|
||||
// // );
|
||||
// // }
|
||||
|
||||
// #[test]
|
||||
// fn bool_literal() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// x : Bool
|
||||
// x = True
|
||||
|
||||
// x
|
||||
// "#
|
||||
// ),
|
||||
// true,
|
||||
// bool
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn optional_field_when_use_default() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// app "test" provides [ main ] to "./platform"
|
||||
|
||||
// f = \r ->
|
||||
// when r is
|
||||
// { x: Blue, y ? 3 } -> y
|
||||
// { x: Red, y ? 5 } -> y
|
||||
|
||||
// main =
|
||||
// a = f { x: Blue, y: 7 }
|
||||
// b = f { x: Blue }
|
||||
// c = f { x: Red, y: 11 }
|
||||
// d = f { x: Red }
|
||||
|
||||
// a * b * c * d
|
||||
// "#
|
||||
// ),
|
||||
// 3 * 5 * 7 * 11,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn optional_field_when_use_default_nested() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// f = \r ->
|
||||
// when r is
|
||||
// { x: Blue, y ? 3 } -> y
|
||||
// { x: Red, y ? 5 } -> y
|
||||
|
||||
// a = f { x: Blue, y: 7 }
|
||||
// b = f { x: Blue }
|
||||
// c = f { x: Red, y: 11 }
|
||||
// d = f { x: Red }
|
||||
|
||||
// a * b * c * d
|
||||
// "#
|
||||
// ),
|
||||
// 3 * 5 * 7 * 11,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn optional_field_when_no_use_default() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// app "test" provides [ main ] to "./platform"
|
||||
|
||||
// f = \r ->
|
||||
// { x ? 10, y } = r
|
||||
// x + y
|
||||
|
||||
// main =
|
||||
// f { x: 4, y: 9 }
|
||||
// "#
|
||||
// ),
|
||||
// 13,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn optional_field_when_no_use_default_nested() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// f = \r ->
|
||||
// { x ? 10, y } = r
|
||||
// x + y
|
||||
|
||||
// f { x: 4, y: 9 }
|
||||
// "#
|
||||
// ),
|
||||
// 13,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn optional_field_let_use_default() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// app "test" provides [ main ] to "./platform"
|
||||
|
||||
// f = \r ->
|
||||
// { x ? 10, y } = r
|
||||
// x + y
|
||||
|
||||
// main =
|
||||
// f { y: 9 }
|
||||
// "#
|
||||
// ),
|
||||
// 19,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn optional_field_let_no_use_default() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// app "test" provides [ main ] to "./platform"
|
||||
|
||||
// f = \r ->
|
||||
// { x ? 10, y } = r
|
||||
// x + y
|
||||
|
||||
// main =
|
||||
// f { x: 4, y: 9 }
|
||||
// "#
|
||||
// ),
|
||||
// 13,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn optional_field_let_no_use_default_nested() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// f = \r ->
|
||||
// { x ? 10, y } = r
|
||||
// x + y
|
||||
|
||||
// f { x: 4, y: 9 }
|
||||
// "#
|
||||
// ),
|
||||
// 13,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn optional_field_function_use_default() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// f = \{ x ? 10, y } -> x + y
|
||||
|
||||
// f { y: 9 }
|
||||
// "#
|
||||
// ),
|
||||
// 19,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// #[ignore]
|
||||
// fn optional_field_function_no_use_default() {
|
||||
// // blocked on https://github.com/rtfeldman/roc/issues/786
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// app "test" provides [ main ] to "./platform"
|
||||
|
||||
// f = \{ x ? 10, y } -> x + y
|
||||
|
||||
// main =
|
||||
// f { x: 4, y: 9 }
|
||||
// "#
|
||||
// ),
|
||||
// 13,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// #[ignore]
|
||||
// fn optional_field_function_no_use_default_nested() {
|
||||
// // blocked on https://github.com/rtfeldman/roc/issues/786
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// f = \{ x ? 10, y } -> x + y
|
||||
|
||||
// f { x: 4, y: 9 }
|
||||
// "#
|
||||
// ),
|
||||
// 13,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn optional_field_singleton_record() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when { x : 4 } is
|
||||
// { x ? 3 } -> x
|
||||
// "#
|
||||
// ),
|
||||
// 4,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn optional_field_empty_record() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when { } is
|
||||
// { x ? 3 } -> x
|
||||
// "#
|
||||
// ),
|
||||
// 3,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn return_record_2() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { x: 3, y: 5 }
|
||||
// "#
|
||||
// ),
|
||||
// [3, 5],
|
||||
// [i64; 2]
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn return_record_3() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { x: 3, y: 5, z: 4 }
|
||||
// "#
|
||||
// ),
|
||||
// (3, 5, 4),
|
||||
// (i64, i64, i64)
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn return_record_4() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { a: 3, b: 5, c: 4, d: 2 }
|
||||
// "#
|
||||
// ),
|
||||
// [3, 5, 4, 2],
|
||||
// [i64; 4]
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn return_record_5() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { a: 3, b: 5, c: 4, d: 2, e: 1 }
|
||||
// "#
|
||||
// ),
|
||||
// [3, 5, 4, 2, 1],
|
||||
// [i64; 5]
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn return_record_6() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { a: 3, b: 5, c: 4, d: 2, e: 1, f: 7 }
|
||||
// "#
|
||||
// ),
|
||||
// [3, 5, 4, 2, 1, 7],
|
||||
// [i64; 6]
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn return_record_7() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { a: 3, b: 5, c: 4, d: 2, e: 1, f: 7, g: 8 }
|
||||
// "#
|
||||
// ),
|
||||
// [3, 5, 4, 2, 1, 7, 8],
|
||||
// [i64; 7]
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn return_record_float_int() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { a: 3.14, b: 0x1 }
|
||||
// "#
|
||||
// ),
|
||||
// (3.14, 0x1),
|
||||
// (f64, i64)
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn return_record_int_float() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { a: 0x1, b: 3.14 }
|
||||
// "#
|
||||
// ),
|
||||
// (0x1, 3.14),
|
||||
// (i64, f64)
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn return_record_float_float() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { a: 6.28, b: 3.14 }
|
||||
// "#
|
||||
// ),
|
||||
// (6.28, 3.14),
|
||||
// (f64, f64)
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn return_record_float_float_float() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { a: 6.28, b: 3.14, c: 0.1 }
|
||||
// "#
|
||||
// ),
|
||||
// (6.28, 3.14, 0.1),
|
||||
// (f64, f64, f64)
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn return_nested_record() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { flag: 0x0, payload: { a: 6.28, b: 3.14, c: 0.1 } }
|
||||
// "#
|
||||
// ),
|
||||
// (0x0, (6.28, 3.14, 0.1)),
|
||||
// (i64, (f64, f64, f64))
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn accessor() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// .foo { foo: 4 } + .foo { bar: 6.28, foo: 3 }
|
||||
// "#
|
||||
// ),
|
||||
// 7,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn accessor_single_element_record() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// .foo { foo: 4 }
|
||||
// "#
|
||||
// ),
|
||||
// 4,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn update_record() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// rec = { foo: 42, bar: 6 }
|
||||
|
||||
// { rec & foo: rec.foo + 1 }
|
||||
// "#
|
||||
// ),
|
||||
// (6, 43),
|
||||
// (i64, i64)
|
||||
// );
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn update_single_element_record() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
rec = { foo: 42}
|
||||
|
||||
{ rec & foo: rec.foo + 1 }
|
||||
"#
|
||||
),
|
||||
43,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn booleans_in_record() {
|
||||
// assert_evals_to!(
|
||||
// indoc!("{ x: 1 == 1, y: 1 == 1 }"),
|
||||
// (true, true),
|
||||
// (bool, bool)
|
||||
// );
|
||||
// assert_evals_to!(
|
||||
// indoc!("{ x: 1 != 1, y: 1 == 1 }"),
|
||||
// (false, true),
|
||||
// (bool, bool)
|
||||
// );
|
||||
// assert_evals_to!(
|
||||
// indoc!("{ x: 1 == 1, y: 1 != 1 }"),
|
||||
// (true, false),
|
||||
// (bool, bool)
|
||||
// );
|
||||
// assert_evals_to!(
|
||||
// indoc!("{ x: 1 != 1, y: 1 != 1 }"),
|
||||
// (false, false),
|
||||
// (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
|
||||
// );
|
||||
// }
|
950
compiler/test_dev/src/dev_str.rs
Normal file
950
compiler/test_dev/src/dev_str.rs
Normal file
|
@ -0,0 +1,950 @@
|
|||
#![cfg(all(test, any(target_os = "linux", target_os = "macos"), any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))]
|
||||
|
||||
//use indoc::indoc;
|
||||
use crate::assert_evals_to;
|
||||
|
||||
// use roc_std::{RocList, RocStr};
|
||||
// #[test]
|
||||
// fn str_split_bigger_delimiter_small_str() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// List.len (Str.split "hello" "JJJJ there")
|
||||
// "#
|
||||
// ),
|
||||
// 1,
|
||||
// i64
|
||||
// );
|
||||
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when List.first (Str.split "JJJ" "JJJJ there") is
|
||||
// Ok str ->
|
||||
// Str.countGraphemes str
|
||||
|
||||
// _ ->
|
||||
// -1
|
||||
|
||||
// "#
|
||||
// ),
|
||||
// 3,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_split_str_concat_repeated() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when List.first (Str.split "JJJJJ" "JJJJ there") is
|
||||
// Ok str ->
|
||||
// str
|
||||
// |> Str.concat str
|
||||
// |> Str.concat str
|
||||
// |> Str.concat str
|
||||
// |> Str.concat str
|
||||
|
||||
// _ ->
|
||||
// "Not Str!"
|
||||
|
||||
// "#
|
||||
// ),
|
||||
// RocStr::from_slice(b"JJJJJJJJJJJJJJJJJJJJJJJJJ"),
|
||||
// RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_split_small_str_bigger_delimiter() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when
|
||||
// List.first
|
||||
// (Str.split "JJJ" "0123456789abcdefghi")
|
||||
// is
|
||||
// Ok str -> str
|
||||
// _ -> ""
|
||||
// "#
|
||||
// ),
|
||||
// RocStr::from_slice(b"JJJ"),
|
||||
// RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_split_big_str_small_delimiter() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// Str.split "01234567789abcdefghi?01234567789abcdefghi" "?"
|
||||
// "#
|
||||
// ),
|
||||
// RocList::from_slice(&[
|
||||
// RocStr::from_slice(b"01234567789abcdefghi"),
|
||||
// RocStr::from_slice(b"01234567789abcdefghi")
|
||||
// ]),
|
||||
// RocList<RocStr>
|
||||
// );
|
||||
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// Str.split "01234567789abcdefghi 3ch 01234567789abcdefghi" "3ch"
|
||||
// "#
|
||||
// ),
|
||||
// RocList::from_slice(&[
|
||||
// RocStr::from_slice(b"01234567789abcdefghi "),
|
||||
// RocStr::from_slice(b" 01234567789abcdefghi")
|
||||
// ]),
|
||||
// RocList<RocStr>
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_split_small_str_small_delimiter() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// Str.split "J!J!J" "!"
|
||||
// "#
|
||||
// ),
|
||||
// RocList::from_slice(&[
|
||||
// RocStr::from_slice(b"J"),
|
||||
// RocStr::from_slice(b"J"),
|
||||
// RocStr::from_slice(b"J")
|
||||
// ]),
|
||||
// RocList<RocStr>
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_split_bigger_delimiter_big_strs() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// Str.split
|
||||
// "string to split is shorter"
|
||||
// "than the delimiter which happens to be very very long"
|
||||
// "#
|
||||
// ),
|
||||
// RocList::from_slice(&[RocStr::from_slice(b"string to split is shorter")]),
|
||||
// RocList<RocStr>
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_split_empty_strs() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// Str.split "" ""
|
||||
// "#
|
||||
// ),
|
||||
// RocList::from_slice(&[RocStr::from_slice(b"")]),
|
||||
// RocList<RocStr>
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_split_minimal_example() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// Str.split "a," ","
|
||||
// "#
|
||||
// ),
|
||||
// RocList::from_slice(&[RocStr::from_slice(b"a"), RocStr::from_slice(b"")]),
|
||||
// RocList<RocStr>
|
||||
// )
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_split_small_str_big_delimiter() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// Str.split
|
||||
// "1---- ---- ---- ---- ----2---- ---- ---- ---- ----"
|
||||
// "---- ---- ---- ---- ----"
|
||||
// |> List.len
|
||||
// "#
|
||||
// ),
|
||||
// 3,
|
||||
// i64
|
||||
// );
|
||||
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// Str.split
|
||||
// "1---- ---- ---- ---- ----2---- ---- ---- ---- ----"
|
||||
// "---- ---- ---- ---- ----"
|
||||
// "#
|
||||
// ),
|
||||
// RocList::from_slice(&[
|
||||
// RocStr::from_slice(b"1"),
|
||||
// RocStr::from_slice(b"2"),
|
||||
// RocStr::from_slice(b"")
|
||||
// ]),
|
||||
// RocList<RocStr>
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_split_small_str_20_char_delimiter() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// Str.split
|
||||
// "3|-- -- -- -- -- -- |4|-- -- -- -- -- -- |"
|
||||
// "|-- -- -- -- -- -- |"
|
||||
// "#
|
||||
// ),
|
||||
// RocList::from_slice(&[
|
||||
// RocStr::from_slice(b"3"),
|
||||
// RocStr::from_slice(b"4"),
|
||||
// RocStr::from_slice(b"")
|
||||
// ]),
|
||||
// RocList<RocStr>
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_concat_big_to_big() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// Str.concat
|
||||
// "First string that is fairly long. Longer strings make for different errors. "
|
||||
// "Second string that is also fairly long. Two long strings test things that might not appear with short strings."
|
||||
// "#
|
||||
// ),
|
||||
// RocStr::from_slice(b"First string that is fairly long. Longer strings make for different errors. Second string that is also fairly long. Two long strings test things that might not appear with short strings."),
|
||||
// RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn small_str_literal() {
|
||||
assert_evals_to!(
|
||||
"\"JJJJJJJJJJJJJJJ\"",
|
||||
[
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0b1000_1111
|
||||
],
|
||||
[u8; 16]
|
||||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn small_str_zeroed_literal() {
|
||||
// // Verifies that we zero out unused bytes in the string.
|
||||
// // This is important so that string equality tests don't randomly
|
||||
// // fail due to unused memory being there!
|
||||
// assert_evals_to!(
|
||||
// "\"J\"",
|
||||
// [
|
||||
// 0x4a,
|
||||
// 0x00,
|
||||
// 0x00,
|
||||
// 0x00,
|
||||
// 0x00,
|
||||
// 0x00,
|
||||
// 0x00,
|
||||
// 0x00,
|
||||
// 0x00,
|
||||
// 0x00,
|
||||
// 0x00,
|
||||
// 0x00,
|
||||
// 0x00,
|
||||
// 0x00,
|
||||
// 0x00,
|
||||
// 0b1000_0001
|
||||
// ],
|
||||
// [u8; 16]
|
||||
// );
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn small_str_concat_empty_first_arg() {
|
||||
assert_evals_to!(
|
||||
r#"Str.concat "" "JJJJJJJJJJJJJJJ""#,
|
||||
[
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0b1000_1111
|
||||
],
|
||||
[u8; 16]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn small_str_concat_empty_second_arg() {
|
||||
assert_evals_to!(
|
||||
r#"Str.concat "JJJJJJJJJJJJJJJ" """#,
|
||||
[
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0b1000_1111
|
||||
],
|
||||
[u8; 16]
|
||||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn small_str_concat_small_to_big() {
|
||||
// assert_evals_to!(
|
||||
// r#"Str.concat "abc" " this is longer than 15 chars""#,
|
||||
// RocStr::from_slice(b"abc this is longer than 15 chars"),
|
||||
// RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn small_str_concat_small_to_small_staying_small() {
|
||||
assert_evals_to!(
|
||||
r#"Str.concat "J" "JJJJJJJJJJJJJJ""#,
|
||||
[
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0x4a,
|
||||
0b1000_1111
|
||||
],
|
||||
[u8; 16]
|
||||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn small_str_concat_small_to_small_overflow_to_big() {
|
||||
// assert_evals_to!(
|
||||
// r#"Str.concat "abcdefghijklm" "nopqrstuvwxyz""#,
|
||||
// RocStr::from_slice(b"abcdefghijklmnopqrstuvwxyz"),
|
||||
// RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_concat_empty() {
|
||||
// assert_evals_to!(r#"Str.concat "" """#, RocStr::default(), RocStr);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn small_str_is_empty() {
|
||||
// assert_evals_to!(r#"Str.isEmpty "abc""#, false, bool);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn big_str_is_empty() {
|
||||
// assert_evals_to!(
|
||||
// r#"Str.isEmpty "this is more than 15 chars long""#,
|
||||
// false,
|
||||
// bool
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn empty_str_is_empty() {
|
||||
// assert_evals_to!(r#"Str.isEmpty """#, true, bool);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_starts_with() {
|
||||
// assert_evals_to!(r#"Str.startsWith "hello world" "hell""#, true, bool);
|
||||
// assert_evals_to!(r#"Str.startsWith "hello world" """#, true, bool);
|
||||
// assert_evals_to!(r#"Str.startsWith "nope" "hello world""#, false, bool);
|
||||
// assert_evals_to!(r#"Str.startsWith "hell" "hello world""#, false, bool);
|
||||
// assert_evals_to!(r#"Str.startsWith "" "hello world""#, false, bool);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_starts_with_code_point() {
|
||||
// assert_evals_to!(
|
||||
// &format!(r#"Str.startsWithCodePt "foobar" {}"#, 'f' as u32),
|
||||
// true,
|
||||
// bool
|
||||
// );
|
||||
// assert_evals_to!(
|
||||
// &format!(r#"Str.startsWithCodePt "zoobar" {}"#, 'f' as u32),
|
||||
// false,
|
||||
// bool
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_ends_with() {
|
||||
// assert_evals_to!(r#"Str.endsWith "hello world" "world""#, true, bool);
|
||||
// assert_evals_to!(r#"Str.endsWith "nope" "hello world""#, false, bool);
|
||||
// assert_evals_to!(r#"Str.endsWith "" "hello world""#, false, bool);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_count_graphemes_small_str() {
|
||||
// assert_evals_to!(r#"Str.countGraphemes "å🤔""#, 2, usize);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_count_graphemes_three_js() {
|
||||
// assert_evals_to!(r#"Str.countGraphemes "JJJ""#, 3, usize);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_count_graphemes_big_str() {
|
||||
// assert_evals_to!(
|
||||
// r#"Str.countGraphemes "6🤔å🤔e¥🤔çppkd🙃1jdal🦯asdfa∆ltråø˚waia8918.,🏅jjc""#,
|
||||
// 45,
|
||||
// usize
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_starts_with_same_big_str() {
|
||||
// assert_evals_to!(
|
||||
// r#"Str.startsWith "123456789123456789" "123456789123456789""#,
|
||||
// true,
|
||||
// bool
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_starts_with_different_big_str() {
|
||||
// assert_evals_to!(
|
||||
// r#"Str.startsWith "12345678912345678910" "123456789123456789""#,
|
||||
// true,
|
||||
// bool
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_starts_with_same_small_str() {
|
||||
// assert_evals_to!(r#"Str.startsWith "1234" "1234""#, true, bool);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_starts_with_different_small_str() {
|
||||
// assert_evals_to!(r#"Str.startsWith "1234" "12""#, true, bool);
|
||||
// }
|
||||
// #[test]
|
||||
// fn str_starts_with_false_small_str() {
|
||||
// assert_evals_to!(r#"Str.startsWith "1234" "23""#, false, bool);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_from_int() {
|
||||
// assert_evals_to!(
|
||||
// r#"Str.fromInt 1234"#,
|
||||
// roc_std::RocStr::from_slice("1234".as_bytes()),
|
||||
// roc_std::RocStr
|
||||
// );
|
||||
// assert_evals_to!(
|
||||
// r#"Str.fromInt 0"#,
|
||||
// roc_std::RocStr::from_slice("0".as_bytes()),
|
||||
// roc_std::RocStr
|
||||
// );
|
||||
// assert_evals_to!(
|
||||
// r#"Str.fromInt -1"#,
|
||||
// roc_std::RocStr::from_slice("-1".as_bytes()),
|
||||
// roc_std::RocStr
|
||||
// );
|
||||
|
||||
// let max = format!("{}", i64::MAX);
|
||||
// assert_evals_to!(
|
||||
// r#"Str.fromInt Num.maxInt"#,
|
||||
// RocStr::from_slice(max.as_bytes()),
|
||||
// RocStr
|
||||
// );
|
||||
|
||||
// let min = format!("{}", i64::MIN);
|
||||
// assert_evals_to!(
|
||||
// r#"Str.fromInt Num.minInt"#,
|
||||
// RocStr::from_slice(min.as_bytes()),
|
||||
// RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_from_utf8_pass_single_ascii() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when Str.fromUtf8 [ 97 ] is
|
||||
// Ok val -> val
|
||||
// Err _ -> ""
|
||||
// "#
|
||||
// ),
|
||||
// roc_std::RocStr::from_slice("a".as_bytes()),
|
||||
// roc_std::RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_from_utf8_pass_many_ascii() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when Str.fromUtf8 [ 97, 98, 99, 0x7E ] is
|
||||
// Ok val -> val
|
||||
// Err _ -> ""
|
||||
// "#
|
||||
// ),
|
||||
// roc_std::RocStr::from_slice("abc~".as_bytes()),
|
||||
// roc_std::RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_from_utf8_pass_single_unicode() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when Str.fromUtf8 [ 0xE2, 0x88, 0x86 ] is
|
||||
// Ok val -> val
|
||||
// Err _ -> ""
|
||||
// "#
|
||||
// ),
|
||||
// roc_std::RocStr::from_slice("∆".as_bytes()),
|
||||
// roc_std::RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_from_utf8_pass_many_unicode() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when Str.fromUtf8 [ 0xE2, 0x88, 0x86, 0xC5, 0x93, 0xC2, 0xAC ] is
|
||||
// Ok val -> val
|
||||
// Err _ -> ""
|
||||
// "#
|
||||
// ),
|
||||
// roc_std::RocStr::from_slice("∆œ¬".as_bytes()),
|
||||
// roc_std::RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_from_utf8_pass_single_grapheme() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when Str.fromUtf8 [ 0xF0, 0x9F, 0x92, 0x96 ] is
|
||||
// Ok val -> val
|
||||
// Err _ -> ""
|
||||
// "#
|
||||
// ),
|
||||
// roc_std::RocStr::from_slice("💖".as_bytes()),
|
||||
// roc_std::RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_from_utf8_pass_many_grapheme() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when Str.fromUtf8 [ 0xF0, 0x9F, 0x92, 0x96, 0xF0, 0x9F, 0xA4, 0xA0, 0xF0, 0x9F, 0x9A, 0x80 ] is
|
||||
// Ok val -> val
|
||||
// Err _ -> ""
|
||||
// "#
|
||||
// ),
|
||||
// roc_std::RocStr::from_slice("💖🤠🚀".as_bytes()),
|
||||
// roc_std::RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_from_utf8_pass_all() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when Str.fromUtf8 [ 0xF0, 0x9F, 0x92, 0x96, 98, 0xE2, 0x88, 0x86 ] is
|
||||
// Ok val -> val
|
||||
// Err _ -> ""
|
||||
// "#
|
||||
// ),
|
||||
// roc_std::RocStr::from_slice("💖b∆".as_bytes()),
|
||||
// roc_std::RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_from_utf8_fail_invalid_start_byte() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when Str.fromUtf8 [ 97, 98, 0x80, 99 ] is
|
||||
// Err (BadUtf8 InvalidStartByte byteIndex) ->
|
||||
// if byteIndex == 2 then
|
||||
// "a"
|
||||
// else
|
||||
// "b"
|
||||
// _ -> ""
|
||||
// "#
|
||||
// ),
|
||||
// roc_std::RocStr::from_slice("a".as_bytes()),
|
||||
// roc_std::RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_from_utf8_fail_unexpected_end_of_sequence() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when Str.fromUtf8 [ 97, 98, 99, 0xC2 ] is
|
||||
// Err (BadUtf8 UnexpectedEndOfSequence byteIndex) ->
|
||||
// if byteIndex == 3 then
|
||||
// "a"
|
||||
// else
|
||||
// "b"
|
||||
// _ -> ""
|
||||
// "#
|
||||
// ),
|
||||
// roc_std::RocStr::from_slice("a".as_bytes()),
|
||||
// roc_std::RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_from_utf8_fail_expected_continuation() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when Str.fromUtf8 [ 97, 98, 99, 0xC2, 0x00 ] is
|
||||
// Err (BadUtf8 ExpectedContinuation byteIndex) ->
|
||||
// if byteIndex == 3 then
|
||||
// "a"
|
||||
// else
|
||||
// "b"
|
||||
// _ -> ""
|
||||
// "#
|
||||
// ),
|
||||
// roc_std::RocStr::from_slice("a".as_bytes()),
|
||||
// roc_std::RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_from_utf8_fail_overlong_encoding() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when Str.fromUtf8 [ 97, 0xF0, 0x80, 0x80, 0x80 ] is
|
||||
// Err (BadUtf8 OverlongEncoding byteIndex) ->
|
||||
// if byteIndex == 1 then
|
||||
// "a"
|
||||
// else
|
||||
// "b"
|
||||
// _ -> ""
|
||||
// "#
|
||||
// ),
|
||||
// roc_std::RocStr::from_slice("a".as_bytes()),
|
||||
// roc_std::RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_from_utf8_fail_codepoint_too_large() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when Str.fromUtf8 [ 97, 0xF4, 0x90, 0x80, 0x80 ] is
|
||||
// Err (BadUtf8 CodepointTooLarge byteIndex) ->
|
||||
// if byteIndex == 1 then
|
||||
// "a"
|
||||
// else
|
||||
// "b"
|
||||
// _ -> ""
|
||||
// "#
|
||||
// ),
|
||||
// roc_std::RocStr::from_slice("a".as_bytes()),
|
||||
// roc_std::RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_from_utf8_fail_surrogate_half() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when Str.fromUtf8 [ 97, 98, 0xED, 0xA0, 0x80 ] is
|
||||
// Err (BadUtf8 EncodesSurrogateHalf byteIndex) ->
|
||||
// if byteIndex == 2 then
|
||||
// "a"
|
||||
// else
|
||||
// "b"
|
||||
// _ -> ""
|
||||
// "#
|
||||
// ),
|
||||
// roc_std::RocStr::from_slice("a".as_bytes()),
|
||||
// roc_std::RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_equality() {
|
||||
// assert_evals_to!(r#""a" == "a""#, true, bool);
|
||||
// assert_evals_to!(
|
||||
// r#""loremipsumdolarsitamet" == "loremipsumdolarsitamet""#,
|
||||
// true,
|
||||
// bool
|
||||
// );
|
||||
// assert_evals_to!(r#""a" != "b""#, true, bool);
|
||||
// assert_evals_to!(r#""a" == "b""#, false, bool);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_clone() {
|
||||
// use roc_std::RocStr;
|
||||
// let long = RocStr::from_slice("loremipsumdolarsitamet".as_bytes());
|
||||
// let short = RocStr::from_slice("x".as_bytes());
|
||||
// let empty = RocStr::from_slice("".as_bytes());
|
||||
|
||||
// debug_assert_eq!(long.clone(), long);
|
||||
// debug_assert_eq!(short.clone(), short);
|
||||
// debug_assert_eq!(empty.clone(), empty);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn nested_recursive_literal() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// Expr : [ Add Expr Expr, Val I64, Var I64 ]
|
||||
|
||||
// expr : Expr
|
||||
// expr = Add (Add (Val 3) (Val 1)) (Add (Val 1) (Var 1))
|
||||
|
||||
// printExpr : Expr -> Str
|
||||
// printExpr = \e ->
|
||||
// when e is
|
||||
// Add a b ->
|
||||
// "Add ("
|
||||
// |> Str.concat (printExpr a)
|
||||
// |> Str.concat ") ("
|
||||
// |> Str.concat (printExpr b)
|
||||
// |> Str.concat ")"
|
||||
// Val v -> "Val " |> Str.concat (Str.fromInt v)
|
||||
// Var v -> "Var " |> Str.concat (Str.fromInt v)
|
||||
|
||||
// printExpr expr
|
||||
// "#
|
||||
// ),
|
||||
// RocStr::from_slice(b"Add (Add (Val 3) (Val 1)) (Add (Val 1) (Var 1))"),
|
||||
// RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_join_comma_small() {
|
||||
// assert_evals_to!(
|
||||
// r#"Str.joinWith ["1", "2"] ", " "#,
|
||||
// RocStr::from("1, 2"),
|
||||
// RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_join_comma_big() {
|
||||
// assert_evals_to!(
|
||||
// r#"Str.joinWith ["10000000", "2000000", "30000000"] ", " "#,
|
||||
// RocStr::from("10000000, 2000000, 30000000"),
|
||||
// RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_join_comma_single() {
|
||||
// assert_evals_to!(r#"Str.joinWith ["1"] ", " "#, RocStr::from("1"), RocStr);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_from_float() {
|
||||
// assert_evals_to!(r#"Str.fromFloat 3.14"#, RocStr::from("3.14"), RocStr);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_to_utf8() {
|
||||
// assert_evals_to!(
|
||||
// r#"Str.toUtf8 "hello""#,
|
||||
// RocList::from_slice(&[104, 101, 108, 108, 111]),
|
||||
// RocList<u8>
|
||||
// );
|
||||
// assert_evals_to!(
|
||||
// r#"Str.toUtf8 "this is a long string""#,
|
||||
// RocList::from_slice(&[
|
||||
// 116, 104, 105, 115, 32, 105, 115, 32, 97, 32, 108, 111, 110, 103, 32, 115, 116,
|
||||
// 114, 105, 110, 103
|
||||
// ]),
|
||||
// RocList<u8>
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_from_utf8_range() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// bytes = Str.toUtf8 "hello"
|
||||
// when Str.fromUtf8Range bytes { count: 5, start: 0 } is
|
||||
// Ok utf8String -> utf8String
|
||||
// _ -> ""
|
||||
// "#
|
||||
// ),
|
||||
// RocStr::from("hello"),
|
||||
// RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_from_utf8_range_slice() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// bytes = Str.toUtf8 "hello"
|
||||
// when Str.fromUtf8Range bytes { count: 4, start: 1 } is
|
||||
// Ok utf8String -> utf8String
|
||||
// _ -> ""
|
||||
// "#
|
||||
// ),
|
||||
// RocStr::from("ello"),
|
||||
// RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_from_utf8_range_slice_not_end() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// bytes = Str.toUtf8 "hello"
|
||||
// when Str.fromUtf8Range bytes { count: 3, start: 1 } is
|
||||
// Ok utf8String -> utf8String
|
||||
// _ -> ""
|
||||
// "#
|
||||
// ),
|
||||
// RocStr::from("ell"),
|
||||
// RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_from_utf8_range_order_does_not_matter() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// bytes = Str.toUtf8 "hello"
|
||||
// when Str.fromUtf8Range bytes { start: 1, count: 3 } is
|
||||
// Ok utf8String -> utf8String
|
||||
// _ -> ""
|
||||
// "#
|
||||
// ),
|
||||
// RocStr::from("ell"),
|
||||
// RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_from_utf8_range_out_of_bounds_start_value() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// bytes = Str.toUtf8 "hello"
|
||||
// when Str.fromUtf8Range bytes { start: 7, count: 3 } is
|
||||
// Ok _ -> ""
|
||||
// Err (BadUtf8 _ _) -> ""
|
||||
// Err OutOfBounds -> "out of bounds"
|
||||
// "#
|
||||
// ),
|
||||
// RocStr::from("out of bounds"),
|
||||
// RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_from_utf8_range_count_too_high() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// bytes = Str.toUtf8 "hello"
|
||||
// when Str.fromUtf8Range bytes { start: 0, count: 6 } is
|
||||
// Ok _ -> ""
|
||||
// Err (BadUtf8 _ _) -> ""
|
||||
// Err OutOfBounds -> "out of bounds"
|
||||
// "#
|
||||
// ),
|
||||
// RocStr::from("out of bounds"),
|
||||
// RocStr
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn str_from_utf8_range_count_too_high_for_start() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// bytes = Str.toUtf8 "hello"
|
||||
// when Str.fromUtf8Range bytes { start: 4, count: 3 } is
|
||||
// Ok _ -> ""
|
||||
// Err (BadUtf8 _ _) -> ""
|
||||
// Err OutOfBounds -> "out of bounds"
|
||||
// "#
|
||||
// ),
|
||||
// RocStr::from("out of bounds"),
|
||||
// RocStr
|
||||
// );
|
||||
// }
|
|
@ -20,8 +20,8 @@ fn promote_expr_to_module(src: &str) -> String {
|
|||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn helper<'a>(
|
||||
arena: &'a bumpalo::Bump,
|
||||
pub fn helper(
|
||||
arena: &bumpalo::Bump,
|
||||
src: &str,
|
||||
stdlib: roc_builtins::std::StdLib,
|
||||
_leak: bool,
|
||||
|
@ -205,7 +205,7 @@ pub fn helper<'a>(
|
|||
|
||||
// std::fs::copy(&path, "/tmp/libapp.so").unwrap();
|
||||
|
||||
let lib = Library::new(path).expect("failed to load shared library");
|
||||
let lib = unsafe { Library::new(path) }.expect("failed to load shared library");
|
||||
|
||||
(main_fn_name, delayed_errors, lib)
|
||||
}
|
4
compiler/test_dev/src/lib.rs
Normal file
4
compiler/test_dev/src/lib.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
pub mod dev_num;
|
||||
pub mod dev_records;
|
||||
pub mod dev_str;
|
||||
mod helpers;
|
|
@ -24,25 +24,21 @@ roc_parse = { path = "../parse" , features = []}
|
|||
roc_build = { path = "../build" }
|
||||
roc_std = { path = "../../roc_std" }
|
||||
roc_gen_wasm = { path = "../gen_wasm" }
|
||||
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.6.1", features = ["collections"] }
|
||||
im = "15.0.0"
|
||||
im-rc = "15.0.0"
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
either = "1.6.1"
|
||||
indoc = "0.3.3"
|
||||
libc = "0.2"
|
||||
libc = "0.2.106"
|
||||
inkwell = { path = "../../vendor/inkwell" }
|
||||
target-lexicon = "0.12.2"
|
||||
libloading = "0.6"
|
||||
wasmer = "2.0.0"
|
||||
libloading = "0.7.1"
|
||||
wasmer = { version = "2.0.0", default-features = false, features = ["default-cranelift", "default-universal"] }
|
||||
wasmer-wasi = "2.0.0"
|
||||
tempfile = "3.1.0"
|
||||
tempfile = "3.2.0"
|
||||
|
||||
[dev-dependencies]
|
||||
maplit = "1.0.1"
|
||||
quickcheck = "0.8"
|
||||
quickcheck_macros = "0.8"
|
||||
tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded"] }
|
||||
bumpalo = { version = "3.6.1", features = ["collections"] }
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
indoc = "1.0.3"
|
||||
roc_parse = { path = "../parse" , features = ["keep_shadowed_builtins"]}
|
||||
|
||||
[features]
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue